• How to create an Level/Experience (XP) system
    6 replies, posted
Just writing this for future reference if anyone asks how they make a levelling system & because I didn't like how [URL="http://facepunch.com/showthread.php?t=1390740&p=44719547"]this [/URL]was being written This is just meant to be a beginners guide kinda thing, similar to [url]http://maurits.tv/data/garrysmod/wiki/wiki.garrysmod.com/index69ca.html[/url] Also, this is adapted from [url]https://github.com/Chessnut/NutScript/blob/3c4f0f797e667eeb07b06509cb75196bed5345ef/gamemode/libs/sh_currency.lua[/url] So if it seems similar to either that's why. As a side note, I know using SetNWInt isn't efficient since its sent all the time, regardless of change(IIRC) but once again, its just to get started, feel free to modify as you please. So after that, let's create a Levelling system! Before we start writing our file, this tutorial is going to take place in side 1 shared file, so in your project make sure that it's included both on the server & client. [lua] local playerMeta = FindMetaTable("Player") LevelCurve = {} [/lua] So first of all, we're getting the meta table for the players, so we can add our own methods to it. I would explain what this means, but it has already been explained beautifully here: [url]http://facepunch.com/showthread.php?t=1388843&p=44648538&viewfull=1#post44648538[/url] We're also initializing our global table named LevelCurve, which will store all of our levels & the xp needed to get them. [lua] local BaseCurve = 30 local Difficulty = 1.6 hook.Add( "Initialize", "GenerateLevelCurve", function() for i=0, 100 do LevelCurve[i] = math.Round(BaseCurve*math.pow(i-1,Difficulty)) end end ) [/lua] Next we're inserting some data into our global table, by using a hook that is run as soon as the server loads up called Initialize. In the hook, is the function to insert the data. This is done by, looping through 0 to 100, which are the levels, and are also being used as the keys for our table. [lua] math.Round(BaseCurve*math.pow(i-1,Difficulty)) [/lua] Next is the value, which is the actual XP needed for the level that you want. In math terms it is: [lua] y = a*(x^z) [/lua] y is the level a is the base xp to get to level 2 from 1 (In our case it is 30) x is the previous level z is the ratio(?, not too sure, I just remember that this is what gives us the difficulty of the curve), which in out case is 1.6 So when we enter our variables which is here: [lua] local BaseCurve = 30 local Difficulty = 1.6 [/lua] It will look like this: [t]http://i.gyazo.com/6e60f102a6e2c4da4c5cb61db45edb95.png[/t] So, it gradually gets harder to level up. You can use any formulae you like to make the XP levels, if you really wanted to you could manually make the levels yourself (not recommended) like so; [lua] hook.Add( "Initialize", "GenerateLevelCurve", function() LevelCurve[1] = 100 LevelCurve[2] = 200 ... LevelCurve[100] = 10000 end ) [/lua] Next we move onto using the meta table to add some new functions. [lua] function playerMeta:GetXP() return self:GetNetworkedInt("xp") or 0 end function playerMeta:GetLevel() local xp = self:GetXP() for i=1, 100 do if xp < LevelCurve[i] and xp > LevelCurve[i - 1] then return (i - 1) end end end function playerMeta:IsLevel(amount) return (self:GetLevel() - amount) >= 0 end //I don't think you would need this, but just incase function playerMeta:IsLevelExact(amount) return (self:GetLevel() == amount) end [/lua] Because this is a shared file, these will be available on server and client. The first function is GetXP, all this basically does, is get the XP from the server stored about the player and returns it. Example of usage: [lua] for k,v in pairs(player.GetAll()) do print(v:GetName() .. " has " .. v:GetXP() .. "xp") end [/lua] This will print everyone connected to the server's XP. The second function is GetLevel, this is basically the same as GetXP however it has a little bit of logic to work out what level they are. The logic behind it is basically it gets the player's XP, then loops through the of XP, and looks for where their XP lands in between. For example if we had 100xp we would be Level 2, because our XP is; Over 91 (which is the requirement for level 2) AND Less than 174 (which is the requirement for level 3) Example of usage: [lua] for k,v in pairs(player.GetAll()) do print(v:GetName() .. " is Level " .. v:GetLevel()) end [/lua] This will print everyone connected to the server's level. The third & forth function is IsLevel() & IsLevelExact all they do is return whether they're over a certain level (true or false) or if you use Exact variant, it returns true or false, if they are EXACTLY the level you specify. Example of usage: [lua] for k,v in pairs(player.GetAll()) do if v:IsLevelExact(10) then print(v:GetName() .. " is Level " .. v:GetLevel()) elseif v:IsLevel(11) then print(v:GetName() .. " is higher than level 10") else print(v:GetName() .. " is not higher than level 10") end end [/lua] Prints a message depending on whether they're level 10 or not. That ends the shared portion, now for the serverside bit. [lua] -- Serverside since the networking happens here too. if (SERVER) then -- Sets the player's xp to a specific value. function playerMeta:SetXP(amount) self:SetNetworkedInt("xp", amount) end -- Sets the player's level to a specific value. (By getting the XP for it, then adding 1) function playerMeta:SetLevel(amount) self:SetNetworkedInt("xp", LevelCurve[amount] + 1) end -- Quick function to set the xp to the current amount plus an amount specified. function playerMeta:GiveXP(amount) self:SetXP(self:GetXP() + amount) end -- Takes away a certain amount by inverting the amount specified. function playerMeta:TakeXP(amount) self:GiveXP(-amount) end end [/lua] TODO: Explain that shit^^ So I hope this helps, here's the full code: [lua] //Adapted from [url]https://github.com/Chessnut/NutScript/blob/3c4f0f797e667eeb07b06509cb75196bed5345ef/gamemode/libs/sh_currency.lua[/url] local playerMeta = FindMetaTable("Player") LevelCurve = {} local BaseCurve = 30 local Difficulty = 0.6 hook.Add( "Initialize", "GenerateLevelCurve", function() for i=0, 100 do LevelCurve[i] = math.Round(BaseCurve*math.pow(i-1,Difficulty)) end end ) function playerMeta:GetXP() return self:GetNetworkedInt("xp") or 0 end function playerMeta:GetLevel() for i=1, 100 do if self:GetXP() < LevelCurve[i] and self:GetXP() > LevelCurve[i - 1] then return (i - 1) end end end function playerMeta:IsLevel(amount) return (self:GetLevel() - amount) >= 0 end //I don't think you would need this, but just incase function playerMeta:IsLevelExact(amount) return (self:GetLevel() == amount) end -- Serverside since the networking happens here too. if (SERVER) then -- Sets the player's xp to a specific value. function playerMeta:SetXP(amount) self:SetNetworkedInt("xp", amount) end -- Sets the player's level to a specific value. (By getting the XP for it, then adding 1) function playerMeta:SetLevel(amount) self:SetNetworkedInt("xp", LevelCurve[amount] + 1) end -- Quick function to set the xp to the current amount plus an amount specified. function playerMeta:GiveXP(amount) self:SetXP(self:GetXP() + amount) end -- Takes away a certain amount by inverting the amount specified. function playerMeta:TakeXP(amount) self:GiveXP(-amount) end end [/lua] [B]Extension[/B] - You can add a check in the GiveXP that before you give them XP get their level, then give them the XP, and compare their old level to their new level. If it's higher, then give them a notification or something like "Level up!". - Add a save method (Since this isn't actually saved, and will be lost when the user disconnects from the server) - Network it using the net library instead of using GetNWInt/SetNWInt If you need any help, post here/add me on steam. Criticism / changes are welcome! (Side note, this hasn't been tested b
Much better than other examples people give. Generate the xp to level list right away instead of generating it each time you want to know the xp!
Looks really good! Hopefully beginners will be able to learn from it, really good explanations aswell
wow thanks, the level curve is so awesome but that means that if i change the difficulty value the curve would change making it easier to level right? [editline]5th May 2014[/editline] this is great but since [i] means 1 to 100 (?) doesnt that mean that [CODE]function playerMeta:GetLevel() local xp = self:GetXP() for i=1, 100 do if xp < LevelCurve[i] and xp > LevelCurve[i - 1] then return (i - 1) end end end[/CODE] theyre checking if your level is lower than 1 to 100 which isnt possible? this confuses me a little on how it works to get our level since i'm coding the notification and reward system but i have to check the level change like you mentioned at the end
Since this is essentially a tutorial thread for xp, here are some other xp functions I helped a guy with a long time ago: [url]https://dl.dropboxusercontent.com/u/26074909/tutoring/levels.lua.html[/url] [url]https://dl.dropboxusercontent.com/u/26074909/tutoring/levels2.lua.html[/url] [url]https://dl.dropboxusercontent.com/u/26074909/tutoring/levels3.lua.html[/url] Just one more way to generate a table of increasing values; the math is easily changed in the recursive function to dramatically change as it increases.
[QUOTE=jellofacey;44726740]wow thanks, the level curve is so awesome but that means that if i change the difficulty value the curve would change making it easier to level right? [editline]5th May 2014[/editline] this is great but since [i] means 1 to 100 (?) doesnt that mean that [CODE]function playerMeta:GetLevel() local xp = self:GetXP() for i=1, 100 do if xp < LevelCurve[i] and xp > LevelCurve[i - 1] then return (i - 1) end end end[/CODE] theyre checking if your level is lower than 1 to 100 which isnt possible? this confuses me a little on how it works to get our level since i'm coding the notification and reward system but i have to check the level change like you mentioned at the end[/QUOTE] Yes by changing the difficulty variable higher, makes it harder to level up, if you lower it, it makes it easier. No, it uses a loop to find where your XP number falls, read above where it mentions the example of 100xp As for your notification; you would need to do it in the SetXP method, since we're not actually storing their level. [lua] -- Sets the player's xp to a specific value. function playerMeta:SetXP(amount) local currentlevel = self:GetLevel() self:SetNetworkedInt("xp", amount) if currentlevel != self:GetLevel() then print("yay we leveled up from Level "..currentlevel.." to Level"..self:GetLevel()) //run your notification system method/function here end end [/lua] What we're basically doing here, is before we give them any XP, we store their level, e.g we have 100 xp which means we're level 2. So our currentlevel variable would be 2. We then give the user the XP like before, say we'll give them 100xp, which means we're level 3 now, so after we give them the XP, we check whether the number has changed with an if statment, if it has then, run your notification. Sorry its not as descriptive, but I'm typing this from my phone, forgive me.
Very informative and well explained. Good as always:eng101:
Sorry, you need to Log In to post a reply to this thread.