Every character earns experience for what they do in the wasteland: killing zombies, finishing quests, crafting. XP adds up to a level between 1 and 50. Other resources can gate content by level, and the HUD can show progress towards the next one.
How XP is stored
- A character's total XP is stored in
metadata.xp. It is capped at the XP needed for the max level. metadata.levelis worked out from the total. It is written whenever XP changes and recalculated on every login.- Because only the total is stored, retuning the curve re-levels everyone consistently the next time they log in.
- XP changes only apply to online players.
The level curve
The curve lives in shared/levels.lua, which is loaded on both server and client.
| Key | Default | What it does |
|---|---|---|
maxLevel | 50 | Highest level |
base | 250 | XP needed to go from level 1 to level 2 |
growth | 1.10 | Each level needs this much more XP than the one before |
The XP needed to go from level L to L + 1 is:
math.floor(base * growth ^ (L - 1) + 0.5)The total XP at which a level starts is the sum of every step before it. With the defaults:
| Level | Total XP to reach it | XP to the next level |
|---|---|---|
| 1 | 0 | 250 |
| 2 | 250 | 275 |
| 5 | 1,161 | 366 |
| 10 | 3,396 | 589 |
| 15 | 6,994 | 949 |
| 20 | 12,790 | 1,529 |
| 25 | 22,125 | 2,462 |
| 30 | 37,158 | 3,966 |
| 40 | 100,362 | 10,286 |
| 49 | 240,043 | 24,254 |
| 50 | 264,297 | max level |
To make levelling faster, lower base or growth. A growth of 1.0 makes every level cost the same.
Level functions
shared/levels.lua returns a Levels table with these helpers. The whole table is available from the server export GetLevels().
| Function | Returns |
|---|---|
Levels.totalFor(level) | The total XP at which that level starts |
Levels.cap() | The most XP a character can hold (the total for the max level) |
Levels.levelFor(xp) | The level for a total XP |
Levels.progress(xp) | { xp, level, into, needed, max } |
In the progress table, into is how much XP the character has earned inside their current level, needed is how much that level takes in total, and max is true once they reach the max level (then into and needed are 0).
Exports
| Export | Arguments | Returns |
|---|---|---|
AddXP(source, amount, reason?) | source, amount, reason | changed, applied |
RemoveXP(source, amount, reason?) | source, amount, reason | changed, applied |
SetXP(source, xp, reason?) | source, xp, reason | changed |
GetXP(source) | source | Total XP |
GetLevel(source) | source | Level |
GetLevelProgress(source) | source | { xp, level, into, needed, max } |
applied is the amount that was actually added or removed after clamping, for example when a player is close to the cap.
The player object has Functions.AddXP(amount, reason?), Functions.RemoveXP(amount, reason?), Functions.GetXP() and Functions.GetLevel(). The client export GetLevelProgress() returns the progress table for the local player.
Admins can use /xp add|remove|set <player> <amount>.
Events
When XP changes, the client receives two small encore:client:onSetMetaData updates (xp and level) instead of a full PlayerData push. Then:
| Side | Event | Payload |
|---|---|---|
| Server | encore:server:xpChanged | source, info |
| Server | encore:server:levelChanged | source, level, previousLevel (only when the level moved) |
| Client (net) | encore:client:xpChanged | info |
info is { xp, level, into, needed, max, delta, reason, previousLevel }.
Sharing XP with a party
For rewards that should be split with nearby party members, use the library helper instead of AddXP:
encore.party.shareXP(source, 40, 'Cleared a nest')See Skills and party.
Examples
Award XP for a kill:
exports['en-core']:AddXP(source, 25, 'Zombie kill')Reward a level-up:
AddEventHandler('encore:server:levelChanged', function(source, level, previous)
if level > previous and level % 10 == 0 then
exports['en-core']:AddMoney(source, 'bank', level * 100, ('Reached level %d'):format(level))
end
end)Gate an action by level:
if exports['en-core']:GetLevel(source) < 15 then
encore.notify(source, { description = 'You need level 15 to use this bench.', type = 'error' })
return
endShow a progress bar on the client:
RegisterNetEvent('encore:client:xpChanged', function(info)
if info.max then
print(('Level %d (max)'):format(info.level))
else
print(('Level %d: %d / %d XP (%+d, %s)'):format(info.level, info.into, info.needed, info.delta, info.reason or ''))
end
end)