EndCore Framework

XP and levels

How EndCore stores character XP, the 50-level curve and its maths, and the exports and events for awarding XP.

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.level is 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.

KeyDefaultWhat it does
maxLevel50Highest level
base250XP needed to go from level 1 to level 2
growth1.10Each level needs this much more XP than the one before

The XP needed to go from level L to L + 1 is:

lua
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:

LevelTotal XP to reach itXP to the next level
10250
2250275
51,161366
103,396589
156,994949
2012,7901,529
2522,1252,462
3037,1583,966
40100,36210,286
49240,04324,254
50264,297max 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().

FunctionReturns
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

ExportArgumentsReturns
AddXP(source, amount, reason?)source, amount, reasonchanged, applied
RemoveXP(source, amount, reason?)source, amount, reasonchanged, applied
SetXP(source, xp, reason?)source, xp, reasonchanged
GetXP(source)sourceTotal XP
GetLevel(source)sourceLevel
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:

SideEventPayload
Serverencore:server:xpChangedsource, info
Serverencore:server:levelChangedsource, level, previousLevel (only when the level moved)
Client (net)encore:client:xpChangedinfo

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:

lua
encore.party.shareXP(source, 40, 'Cleared a nest')

See Skills and party.

Examples

Award XP for a kill:

lua
exports['en-core']:AddXP(source, 25, 'Zombie kill')

Reward a level-up:

lua
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:

lua
if exports['en-core']:GetLevel(source) < 15 then
    encore.notify(source, { description = 'You need level 15 to use this bench.', type = 'error' })
    return
end

Show a progress bar on the client:

lua
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)