(Updated!) Level and Experience system -- V1.6.4

Started by Mr Shiek, July 22, 2019, 02:08:50 AM

Previous topic - Next topic
July 22, 2019, 02:08:50 AM Last Edit: January 17, 2021, 02:31:51 AM by Kamigousu
See my comment near the bottom for the updated script! Thank you!


Hey everybody,

This script was originally made by froggy77 (http://forum.solarus-games.org/index.php/topic,520.msg7809.html) and they recently asked for help with updating the script to work with Solarus 1.6. I took some time and got it working with the latest version and so I thought I would re-upload the script for the community. I hope this is okay; I didn't think posting it in the original forum would give it as much visibility and the original post contains some outdated information that is better left out. I did message the original author but after a few days without reply I decided I would post it here, with due credit. Not sure if messages even notify people on here.

Here is the script in it's entirety with all the necessary extra functions and code included at the bottom and below the code is a download for the .lua file:

-- LEVEL (LVL) and EXPERIENCE (EXP) counters shown on the game screen.

-- LVL AND EXP  version 1.6
-- from a script (for rupees) by christopho
-- Modified by froGgy for a Zelda-like (ZL) project.
-- v 1.0:  First version posted. It is just a draft.
-- Modified by Kamigousu on 18/07/19.
-- v 1.6:  Updated for use with Solarus 1.6; basic with lots of potential, mostly just froggy77's original draft.

--Additional code and notes are included at the bottom for convenience. Please read them before continuing.

local lvl_and_exp = {}

function lvl_and_exp:new(game)

  local object = {}
  setmetatable(object, self)
  self.__index = self

  object:initialize(game)

  return object
end

function lvl_and_exp:initialize(game)

  self.game = game
  self.surface = sol.surface.create(112, 24)
  self.digits_text_for_lvl = sol.text_surface.create{
    font = "green_digits",
    horizontal_alignment = "left",
  }
  self.digits_text_for_exp = sol.text_surface.create{
    font = "white_digits",
    horizontal_alignment = "left",
  }
  self.digits_text_for_exp_to_levelup = sol.text_surface.create{
    font = "white_digits",
    horizontal_alignment = "left",
  }
  self.digits_text_for_lvl:set_text(game:get_level())
  self.digits_text_for_exp:set_text(game:get_exp())
  self.lvl_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.exp_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.slash_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.current_lvl_displayed = self.game:get_level()
  self.current_exp_displayed = self.game:get_exp()
  self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  self.t_exp_to_levelup = {100, 200, 300, 400, 500, 600, 700, 800}
  self.max_level = #self.t_exp_to_levelup
  self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.current_lvl_displayed])
  self:check()
  self:rebuild_surface()
end

function lvl_and_exp:check()

  local need_rebuild = false
  local current_level = self.game:get_level()
  local current_exp = self.game:get_exp()
  local exp_to_levelup = self.t_exp_to_levelup[current_level]
  if exp_to_levelup == nil then
self.current_exp_displayed = self.t_exp_to_levelup[self.max_level]
self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  exp_to_levelup = self.t_exp_to_levelup[self.max_level]
  end
  local difference = 0

-- Current LVL.
if current_level <= self.max_level + 1 then
  if current_level ~= self.current_lvl_displayed then
need_rebuild = true
local increment
if current_level > self.current_lvl_displayed then
  increment = 1
else
  increment = -1
end
self.current_lvl_displayed = self.current_lvl_displayed + increment
-- Play a sound if we have just reached the final value.
if self.current_lvl_displayed == current_level then
  if increment == 1 then
sol.audio.play_sound("victory")
sol.audio.play_sound("treasure")
  else
sol.audio.play_sound("switch")
sol.audio.play_sound("hero_falls")
  end
end
  end
end

-- Current XP.
if current_level <= self.max_level then
  if current_exp ~= self.current_exp_displayed then
need_rebuild = true
local increment
if current_exp > self.current_exp_displayed then
  increment = 1
else
  increment = -1
end
self.current_exp_displayed = self.current_exp_displayed + increment
self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  end

-- Level up
  if self.current_exp_displayed >= exp_to_levelup then
self.game:set_value("current_level", current_level + 1)
difference = current_exp - exp_to_levelup
self.game:set_value("current_exp", difference)
current_exp = self.game:get_value("current_exp")
self.current_exp_displayed = 0
self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  end
end

  -- Redraw the surface only if something has changed.
  if need_rebuild then
    self:rebuild_surface()
  end

  -- Schedule the next check.
  sol.timer.start(self.game, 40, function()
    self:check()
  end)
end

function lvl_and_exp:rebuild_surface()

  self.surface:clear()

  -- LVL (icon).
  self.lvl_icon_img:draw_region(0, 0, 12, 12, self.surface)

  -- XP (icon).
  self.exp_icon_img:draw_region(12, 0, 12, 12, self.surface, 27, 0)
 
  -- SLASH (icon).
  self.slash_icon_img:draw_region(24, 0, 8, 12, self.surface, 35 + (8 * self.current_exp_displayed_length), 4)

    -- Current LVL (counter).
  if self.current_lvl_displayed > self.max_level then
    self.digits_text_for_lvl:set_font("green_digits")
  else
    self.digits_text_for_lvl:set_font("white_digits")
  end
  self.digits_text_for_lvl:set_text(self.current_lvl_displayed)
  self.digits_text_for_lvl:draw(self.surface, 12, 6)
 
  -- Current XP (counter).
  if self.current_lvl_displayed <= self.max_level then
    self.digits_text_for_exp:set_font("white_digits")
    self.digits_text_for_exp:set_text(self.current_exp_displayed)
    self.digits_text_for_exp_to_levelup:set_font("white_digits")
self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.current_lvl_displayed])
  else
    self.digits_text_for_exp:set_font("green_digits")
    self.digits_text_for_exp:set_text(self.current_exp_displayed)
    self.digits_text_for_exp_to_levelup:set_font("green_digits")
self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.max_level])
  end
  self.digits_text_for_exp:draw(self.surface, 40, 6)
  self.digits_text_for_exp_to_levelup:draw(self.surface, 40 + (8 * self.current_exp_displayed_length), 12)
end

function lvl_and_exp:set_dst_position(x, y)
  self.dst_x = x
  self.dst_y = y
end

function lvl_and_exp:on_draw(dst_surface)

  local x, y = self.dst_x, self.dst_y
  local width, height = dst_surface:get_size()
  if x < 0 then
    x = width + x
  end
  if y < 0 then
    y = height + y
  end

  self.surface:draw(dst_surface, x, y)
end

return lvl_and_exp



--[[********************************NOTES and CODE********************************


--The segment of code below must go into your initial_game.lua in the initialise_new_savegame(game) function. Do not set the level to 0 or it will create a bug (it is my understanding that it is a bug). Also, you will need to add these values to any current savegame file you try to run the script on. Otherwise the script will not load properly

  --Initialise player experience system.
    game:set_level(1)      --Initialise the player level value for new game.
  game:set_exp(0)       --Initialise the player experience value for new game.


--The following must be put into your hud_config script, in the table with the rest of your hud elements; unless you are putting the exp and lvl display in an inventory screen or some other menu, in which case you will have to require it in the proper lua file.

  --Level and Experience Counter
  {
    menu_script = ("scripts/hud/lvl_and_xp"),
    x = 210,    --X and Y can be changed to wherever you would like the exp counter displayed.
    y = 16,
  },


Finally, you must also add the functions game:get_level/exp(), set_level/exp(), and add_level/exp().  In a seperate file named game.lua (or wherever you have a game metatable available) add the following functions. (This doesn't necessarily need to be in it's own file, but for the sake of organisation and tidy code, I find that defining all of my game_meta functions in one place makes things easier. (I do believe that is something I picked up from reading and learning from so many of the Solarus Team's scripts.)

--The following line can be omitted if you all ready have a file where you get the metatable.
local game_meta = sol.main.get_metatable("game")


function game_meta:get_level()
  return self:get_value("current_level")
end

function game_meta:set_level(level)
  return self:set_value("current_level", level)
end

function game_meta:add_level(level)
  local level = level
  local c_level = self:get_level()
  local level_up = c_level + level
  return self:set_value("current_level", level_up)
end

function game_meta:get_exp()
  return self:get_value("current_exp")
end

function game_meta:set_exp(exp)
  return self:set_value("current_exp", exp)
end

function game_meta:add_exp(exp)
  local exp = exp
  local c_exp = self:get_exp()
  local exp_up = c_exp + exp
  return self:set_value("current_exp", exp_up)
end

You may require this file in your main.lua somehow or use multi_events to call it. Whatever you prefer.
--]]


You will also need to put the lvl_and_exp_icon.png (see attachments at bottom) into your "sprites/hud" folder.
You can also modify the values of the t_exp_to_levelup table to change the experience required to level up. The first index is the experience required to get from level 1 to level 2 and so on.

Hopefully my explanation was clear. I only know basic French or I would write this up in French as well. I have a few other features for it that I have been testing on my own project, one of which is an algorithm to populate the experience table. I may upload some of these changes in the future if they work out well.

Thanks again, froggy77, for this awesome script! I'm all ready putting it to good use!
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
a.k.a. Kamigousu, Xejk, Sr Xejk

Awesome stuff. I'd like to try and develop a game using level ups at some point, perhaps when at least one of my basic projects is completed.
Could also be used for making a Secret of Mana fangame. My dream is to one day have a Secret of Mana resource pack. Interestingly, Zefk posted an Allied AI script a while back, for having AI control additional characters to help the hero, just like in Secret of Mana. Combining it with this would start to get several of the systems in place.

July 22, 2019, 07:56:38 PM #2 Last Edit: July 28, 2019, 01:28:08 AM by froggy77

@Kamigousu
wow it is a very good job.
I have a headache reading again the script after several years without scripting in Lua. I'm glad someone keeps making the script use.
Thank you for the update.

Please, could you delete my nickname in the topic name? It is not because it is misspelled ;) but because it's just embarrassing.


@CopperKatana
Sorry, it is not adapted to have more than on hero. But, it would be a good improvement.

Quote from: froggy77 on July 22, 2019, 07:56:38 PM

@Kamigousu
wow it is a very good job.
I have a headache reading again the script after several years without scripting in Lua. I'm glad someone keeps making the script use.
Thank you for the update.

Please, could you delete my nickname in the topic name? It is not because it is misspelled ;) but because it's just embarrassing.

Of course, I'm very sorry for the misspelling. I have fixed the post and removed your name from the title. I only put it there because I did not want to try to steal credit for creating this script. I did not mean to embarrass you.

In regards to the allied experience and levelling, I don't think it would be hard to adapt this script for allied characters. I would have to do A lot of testing so I probably won't work on that for a little while. All though it might be interesting to apply an enemy level and experience system so it may be sooner than later. I will have to keep in touch with Blueblue3D and post any scripts hat may come from the matter.
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
a.k.a. Kamigousu, Xejk, Sr Xejk

my edit to the thing


-- LEVEL (LVL) and EXPERIENCE (EXP) counters shown on the game screen.

-- LVL AND EXP  version 1.6
-- from a script (for rupees) by christopho
-- Modified by froGgy for a Zelda-like (ZL) project.
-- v 1.0:  First version posted. It is just a draft.
-- Modified by Kamigousu on 18/07/19.
-- v 1.6:  Updated for use with Solarus 1.6; basic with lots of potential, mostly just froggy44's original draft.
-- v 1.6.1: D MAS: Fixed game over preventing exp gain, changed delay to 20ms, slight example edits

--Additional code and notes are included at the bottom for convenience. Please read them before continuing.

local lvl_and_exp = {}

function lvl_and_exp:new(game)

  local object = {}
  setmetatable(object, self)
  self.__index = self

  object:initialize(game)

  return object
end

  function lvl_and_exp:on_started()

    -- This function is called when the HUD starts or
    -- was disabled and gets enabled again.
    -- Unlike other HUD elements, the timers were canceled because they
    -- are attached to the menu and not to the game
    -- (this is because the hearts are also used in the savegame menu).

    -- After game-over don't show gradually getting the life back.
    self.current_lvl_displayed = self.game:get_level()
self.current_exp_displayed = self.game:get_exp()
    self:check()
    self:rebuild_surface()
  end

function lvl_and_exp:initialize(game)

  self.game = game
  self.surface = sol.surface.create(112, 12)
  self.digits_text_for_lvl = sol.text_surface.create{
    font = "green_digits",
    horizontal_alignment = "right",
  }
  self.digits_text_for_exp = sol.text_surface.create{
    font = "white_digits",
    horizontal_alignment = "right",
  }
  self.digits_text_for_exp_to_levelup = sol.text_surface.create{
    font = "white_digits",
    horizontal_alignment = "right",
  }
  self.digits_text_for_lvl:set_text(game:get_level())
  self.digits_text_for_exp:set_text(game:get_exp())
  self.lvl_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.exp_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.slash_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.current_lvl_displayed = self.game:get_level()
  self.current_exp_displayed = self.game:get_exp()
  self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  self.t_exp_to_levelup = {30, 100, 250, 500, 800, 1400, 2000, 2800}
  self.max_level = #self.t_exp_to_levelup
  self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.current_lvl_displayed])
  self:check()
  self:rebuild_surface()
end

function lvl_and_exp:check()

  local need_rebuild = false
  local current_level = self.game:get_level()
  local current_exp = self.game:get_exp()
  local exp_to_levelup = self.t_exp_to_levelup[current_level]
  if exp_to_levelup == nil then
self.current_exp_displayed = self.t_exp_to_levelup[self.max_level]
self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  exp_to_levelup = self.t_exp_to_levelup[self.max_level]
  end
  local difference = 0

-- Current LVL.
if current_level <= self.max_level + 1 then
  if current_level ~= self.current_lvl_displayed then
need_rebuild = true
local increment
if current_level > self.current_lvl_displayed then
  increment = 1
else
  increment = -1
end
self.current_lvl_displayed = self.current_lvl_displayed + increment
-- Play a sound if we have just reached the final value.
if self.current_lvl_displayed == current_level then
  if increment == 1 then
sol.audio.play_sound("victory")
sol.audio.play_sound("treasure")
  else
sol.audio.play_sound("switch")
sol.audio.play_sound("hero_falls")
  end
end
  end
end

-- Current XP.
if current_level <= self.max_level then
  if current_exp ~= self.current_exp_displayed then
need_rebuild = true
local increment
if current_exp > self.current_exp_displayed then
  increment = 1
else
  increment = -1
end
self.current_exp_displayed = self.current_exp_displayed + increment
self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  end

-- Level up
  if self.current_exp_displayed >= exp_to_levelup then
self.game:set_value("current_level", current_level + 1)
difference = current_exp - exp_to_levelup
self.game:set_value("current_exp", difference)
current_exp = self.game:get_value("current_exp")
self.current_exp_displayed = 0
self.current_exp_displayed_length = string.len(self.current_exp_displayed)

  end
end

  -- Redraw the surface only if something has changed.
  if need_rebuild then
    self:rebuild_surface()
  end

  -- Schedule the next check.
  sol.timer.start(self.game, 20, function()
    self:check()
  end)
end

function lvl_and_exp:rebuild_surface()

  self.surface:clear()

  -- LVL (icon).
  self.lvl_icon_img:draw_region(0, 0, 12, 12, self.surface)

  -- XP (icon).
  self.exp_icon_img:draw_region(12, 0, 12, 12, self.surface, 24, 0)

  -- SLASH (icon).
  self.slash_icon_img:draw_region(24, 0, 12, 12, self.surface, 64, 0)

    -- Current LVL (counter).
  if self.current_lvl_displayed > self.max_level then
    self.digits_text_for_lvl:set_font("green_digits")
  else
    self.digits_text_for_lvl:set_font("white_digits")
  end
  self.digits_text_for_lvl:set_text(self.current_lvl_displayed)
  self.digits_text_for_lvl:draw(self.surface, 24, 6)

  -- Current XP (counter).
  if self.current_lvl_displayed <= self.max_level then
    self.digits_text_for_exp:set_font("white_digits")
    self.digits_text_for_exp:set_text(self.current_exp_displayed)
    self.digits_text_for_exp_to_levelup:set_font("white_digits")
self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.current_lvl_displayed])
  else
    self.digits_text_for_exp:set_font("green_digits")
    self.digits_text_for_exp:set_text(self.current_exp_displayed)
    self.digits_text_for_exp_to_levelup:set_font("green_digits")
self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.max_level])
  end
  self.digits_text_for_exp:draw(self.surface, 64, 6)
  self.digits_text_for_exp_to_levelup:draw(self.surface, 104, 6)

end

function lvl_and_exp:set_dst_position(x, y)
  self.dst_x = x
  self.dst_y = y
end

function lvl_and_exp:on_draw(dst_surface)

  local x, y = self.dst_x, self.dst_y
  local width, height = dst_surface:get_size()
  if x < 0 then
    x = width + x
  end
  if y < 0 then
    y = height + y
  end

  self.surface:draw(dst_surface, x, y)
end

return lvl_and_exp



--[[********************************NOTES and CODE********************************


--The segment of code below must go into your initial_game.lua in the initialise_new_savegame(game) function. Do not set the level to 0 or it will create a bug (it is my understanding that it is a bug). Also, you will need to add these values to any current savegame file you try to run the script on. Otherwise the script will not load properly

  --Initialise player experience system.
    game:set_level(1)      --Initialise the player level value for new game.
  game:set_exp(0)       --Initialise the player experience value for new game.


--The following must be put into your hud_config script, in the table with the rest of your hud elements; unless you are putting the exp and lvl display in an inventory screen or some other menu, in which case you will have to require it in the proper lua file.

  --Level and Experience Counter
  {
    menu_script = ("scripts/hud/lvl_and_exp"),
    x = -120,    --X and Y can be changed to wherever you would like the exp counter displayed.
    y = 16,
  },


Finally, you must also add the functions game:get_level/exp(), set_level/exp(), and add_level/exp().  In a seperate file named game.lua (or wherever you have a game metatable available) add the following functions. (This doesn't necessarily need to be in it's own file, but for the sake of organisation and tidy code, I find that defining all of my game_meta functions in one place makes things easier. (I do believe that is something I picked up from reading and learning from so many of the Solarus Team's scripts.)

--The following line can be omitted if you all ready have a file where you get the metatable.
local game_meta = sol.main.get_metatable("game")


function game_meta:get_level()
  return self:get_value("current_level")
end

function game_meta:set_level(level)
  return self:set_value("current_level", level)
end

function game_meta:add_level(level)
  local level = level
  local c_level = self:get_level()
  local level_up = c_level + level
  return self:set_value("current_level", level_up)
end

function game_meta:get_exp()
  return self:get_value("current_exp")
end

function game_meta:set_exp(exp)
  return self:set_value("current_exp", exp)
end

function game_meta:add_exp(exp)
  local exp = exp
  local c_exp = self:get_exp()
  local exp_up = c_exp + exp
  return self:set_value("current_exp", exp_up)
end

You may require this file in your main.lua somehow or use multi_events to call it. Whatever you prefer.
--]]


exactly what i changed
"scripts/hud/lvl_and_xp" -> "scripts/hud/lvl_and_exp"
x = 210 -> -112
the former is more consistent with typing, and the latter makes it right-aligned, i also made the text right-aligned as i believe it looks better, i also attached my garbage looking image which looks slightly better spacing-wise

possible future update to specify image dimensions
i remember the old days of not knowing wtf to put in this box

January 17, 2021, 02:38:55 AM #5 Last Edit: February 16, 2021, 12:21:46 AM by Kamigousu
Here is the new script with some typos corrected, several bugs worked out, and a few things just tidied up.

Thank you D MAS for bringing the corrected issues to my attention. I found a few more issues that bugged me while I was reworking this script so those fixes are in here as well. For instance, once you reach the maximum level, the game will record your experience as being maxed out rather than recording it as 0. It could be improved more but for now I think it will work for most people's purposes.


-- LEVEL (LVL) and EXPERIENCE (EXP) counters shown on the game screen.

-- LVL AND EXP  version 1.6
-- from a script (for rupees) by christopho
-- Modified by froGgy for a Zelda-like (ZL) project.
-- v 1.0:  First version posted. It is just a draft.
-- Modified by Kamigousu on 18/07/19.
-- v 1.6:  Updated for use with Solarus 1.6; basic with lots of potential, mostly just froggy77's original draft.
--Modified by D MAS and Kamigousu on 16/01/21.
--v 1.6.4: Updated for clarity and ease of use.

--Additional code and notes are included at the bottom for convenience. Please read them before continuing.

local lvl_and_exp = {}

function lvl_and_exp:new(game)

  local object = {}
  setmetatable(object, self)
  self.__index = self

  object:initialize(game)

  return object
end

function lvl_and_exp:initialize(game)

  self.game = game
  self.surface = sol.surface.create(112, 24)
  self.digits_text_for_lvl = sol.text_surface.create{
    font = "green_digits",
    horizontal_alignment = "left",
  }
  self.digits_text_for_exp = sol.text_surface.create{
    font = "white_digits",
    horizontal_alignment = "left",
  }
  self.digits_text_for_exp_to_levelup = sol.text_surface.create{
    font = "white_digits",
    horizontal_alignment = "left",
  }

  --Set the initial level and experience to 1 and 0 respectively,
  --if they are not all ready set.
  if not self.game:get_level() or self.game:get_level() == 0 then self.game:set_level(1) end
  if not self.game:get_exp() then self.game:set_exp(0) end
 
  self.digits_text_for_lvl:set_text(game:get_level())
  self.digits_text_for_exp:set_text(game:get_exp())
  self.lvl_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.exp_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.slash_icon_img = sol.surface.create("hud/lvl_and_exp_icon.png")
  self.current_lvl_displayed = self.game:get_level()
  self.current_exp_displayed = self.game:get_exp()
  self.current_exp_displayed_length = string.len(self.current_exp_displayed)

  --Here are two options for setting up the exp_to_levelup table.
  --By default, option 2 is enabled.

  --The first will set up a very basic level up table that requires you
  --to fill in all the fields for each level up. You may stop at whatever
  --level you wish; that level will be the max level. That code is as follows:

  --self.t_exp_to_levelup = {100, 200, 300, 400, 500, 600, 700, 800}
  --self.max_level = #self.t_exp_to_levelup

--============================================

  --The second will set up a basic level up table that will fill in the experience
  --required for each level based on the total number of levels you would like.
  --This requires you to define the max level yourself and input the experience
  --required to level up to level 2 into the exp_to_levelup table.

  self.max_level = 20
  self.t_exp_to_levelup = {150}
  for key, value in ipairs(self.t_exp_to_levelup) do
    local next_key = key + 1
    if next_key <= self.max_level - 1 then
      self.t_exp_to_levelup[next_key] = math.ceil((self.t_exp_to_levelup[1] * next_key) + ((value * next_key) / 100))
      --Uncomment the print lines below to see the level and required experience to levelup in the console.
      --print(#self.t_exp_to_levelup)
      --print(self.t_exp_to_levelup[next_key])
    else
      break
    end
  end

  self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.current_lvl_displayed])
  self:rebuild_surface()
  self:check()
end

--Whenenever a game over is completed, the timer for this counter
--must be restarted.
function lvl_and_exp:on_started()
  self:rebuild_surface()
  self:check()
end

function lvl_and_exp:check()

  local need_rebuild = false
  local current_level = self.game:get_level()
  local current_exp = self.game:get_exp()
  local exp_to_levelup = self.t_exp_to_levelup[current_level]
  if exp_to_levelup == nil then
self.current_exp_displayed = self.t_exp_to_levelup[self.max_level - 1]
self.current_exp_displayed_length = string.len(self.current_exp_displayed)
   exp_to_levelup = self.t_exp_to_levelup[self.max_level - 1]
  end
  local difference = 0

-- Current LVL.
if current_level <= self.max_level then
  if current_level ~= self.current_lvl_displayed then
  need_rebuild = true
  local increment
  if current_level > self.current_lvl_displayed then
    increment = 1
  else
    increment = -1
  end
  self.current_lvl_displayed = self.current_lvl_displayed + increment
  -- Play a sound if we have just reached the final value.
  if self.current_lvl_displayed == current_level then
    if increment == 1 then
    sol.audio.play_sound("victory")
    sol.audio.play_sound("treasure")
    else
    sol.audio.play_sound("switch")
    sol.audio.play_sound("hero_falls")
    end
  end
  end
end

-- Current XP.
if current_level <= self.max_level - 1 then
  if current_exp ~= self.current_exp_displayed then
need_rebuild = true
local increment
    local difference = math.abs(current_exp - self.current_exp_displayed)
  if current_exp > self.current_exp_displayed then
        if difference >= 1000 then
      increment = 321
        elseif difference>= 100 then
          increment = 32
        else if difference >= 10 then
          increment = 3
        else
          increment = 1
        end
  else
        if difference >= 1000 then
      increment = -321
        elseif difference >= 100 then
          increment = -32
        else if difference >= 10 then
          increment = -3
        else
          increment = -1
        end
  end
  self.current_exp_displayed = self.current_exp_displayed + increment
  self.current_exp_displayed_length = string.len(self.current_exp_displayed)
  end

-- Level up
  if self.current_exp_displayed >= exp_to_levelup then
self.game:add_level(1)
      if self.game:get_level() ~= self.max_level then
    difference = current_exp - exp_to_levelup
    self.game:set_value("current_exp", difference)
    current_exp = self.game:get_value("current_exp")
    self.current_exp_displayed = 0
    self.current_exp_displayed_length = string.len(self.current_exp_displayed)
      end
  end
end

  -- Redraw the surface only if something has changed.
  if need_rebuild then
    self:rebuild_surface()
  end

  -- Schedule the next check.
  local timer = sol.timer.start(self.game, 10, function()
    self:check()
  end)
  timer:set_suspended_with_map()
end

function lvl_and_exp:rebuild_surface()

  self.surface:clear()

  -- LVL (icon).
  self.lvl_icon_img:draw_region(0, 0, 12, 12, self.surface)

  -- XP (icon).
  self.exp_icon_img:draw_region(12, 0, 12, 12, self.surface, 27, 0)
 
  -- SLASH (icon).
  self.slash_icon_img:draw_region(24, 0, 8, 12, self.surface, 40 + (8 * self.current_exp_displayed_length), 4)

    -- Current LVL (counter).
  if self.current_lvl_displayed == self.max_level then
    self.digits_text_for_lvl:set_font("green_digits")
  else
    self.digits_text_for_lvl:set_font("white_digits")
  end
  self.digits_text_for_lvl:set_text(self.current_lvl_displayed)
  self.digits_text_for_lvl:draw(self.surface, 12, 6)
 
  -- Current XP (counter).
  if self.current_lvl_displayed < self.max_level then
    self.digits_text_for_exp:set_font("white_digits")
    self.digits_text_for_exp:set_text(self.current_exp_displayed)
    self.digits_text_for_exp_to_levelup:set_font("white_digits")
  self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.current_lvl_displayed])
  else
    self.digits_text_for_exp:set_font("green_digits")
    self.digits_text_for_exp:set_text(self.current_exp_displayed)
    self.digits_text_for_exp_to_levelup:set_font("green_digits")
  self.digits_text_for_exp_to_levelup:set_text(self.t_exp_to_levelup[self.max_level - 1])
  end
  self.digits_text_for_exp:draw(self.surface, 40, 6)
  self.digits_text_for_exp_to_levelup:draw(self.surface, 48 + (8 * self.current_exp_displayed_length), 12)
end

function lvl_and_exp:set_dst_position(x, y)
  self.dst_x = x
  self.dst_y = y
end

function lvl_and_exp:on_draw(dst_surface)

  local x, y = self.dst_x, self.dst_y
  local width, height = dst_surface:get_size()
  if x < 0 then
    x = width + x
  end
  if y < 0 then
    y = height + y
  end

  self.surface:draw(dst_surface, x, y)
end

return lvl_and_exp



--[[********************************NOTES and CODE********************************

This script assumes you have the green and white digits fonts from the ALttP resource pack.
It also assumes you have the "victory", "treasure", "switch", and "hero_falls" sounds from that resource pack.
You may change these in the script to customise the sounds and digits for your game.


You must have a file named lvl_and_exp_icon.png in the sprites/hud folder or you must change the file name in this script to correspond with your own custom file.
If you use the file provided in the top comment on the forum page for this script (http://forum.solarus-games.org/index.php/topic,1362.msg8050.html#msg8050)
then you will not need to modify the script. If you use your own custom file, you will, more likely than not, need to modify this script at lines 178, 181, and 184
to draw the new image properly.
You DO NOT need to create sprites for this script to run. You only need to have the png image named correctly and in the proper folder.


The following must be put into your hud_config script, in the table with the rest of your hud elements;
unless you are putting the exp and lvl display in an inventory screen or some other menu, in which case you will have to require it in the proper lua file.

  --Level and Experience Counter
  {
    menu_script = ("scripts/hud/lvl_and_exp"),
    x = 210,    --X and Y can be changed to wherever you would like the exp counter displayed.
    y = 16,
  },


You must also add the functions game:get_level/exp(), set_level/exp(), and add_level/exp() to your game. 
In a seperate file named game.lua (or wherever you have a game metatable available) add the following:

--The line below can be omitted if you are putting the following function in a file with the metatable all ready defined.
local game_meta = sol.main.get_metatable("game")


function game_meta:get_level()
  return self:get_value("current_level")
end

function game_meta:set_level(level)
  return self:set_value("current_level", level)
end

function game_meta:add_level(level)
  local level = level
  local c_level = self:get_level()
  local level_up = c_level + level
  return self:set_value("current_level", level_up)
end

function game_meta:get_exp()
  return self:get_value("current_exp")
end

function game_meta:set_exp(exp)
  return self:set_value("current_exp", exp)
end

function game_meta:add_exp(exp)
  local exp = exp
  local c_exp = self:get_exp()
  local exp_up = c_exp + exp
  return self:set_value("current_exp", exp_up)
end

If you are using the standard features.lua script, it is recommended to require the file that holds the code above in that script.
--]]


I did not change the placement or spacing for the level and experience text but those changes are documented in the script posted by D MAS. Also, I feel that aesthetic choices like that are best left up to the developer using the system. The script here is meant to be easily mutable.

Please feel free to reply with comments or questions or come to the discord to ask/comment. The image file for the icons can still be found in the top comment of this thread or you can use D MAS' file (see comment above) that they were kind enough to share.

Thanks again Froggy77 and D MAS!

EDIT: Added a few more features like pausing with the map and adjusted spacing for the exp and separating slash. It also now counts up exp faster if there is a large amount of experience to add. Thank you Max for providing a nice example of how to do that; my initial idea was a tad backwards.

EDIT2:  Small fix to the increment portion of the check function.
Build a man a fire, he will be warm for the night. Set a man on fire, he will be warm for the rest of his life.
a.k.a. Kamigousu, Xejk, Sr Xejk