Every character on an EndCore server has a PlayerData table. It holds who they are, their money, job, group, position and a metadata table with survival stats, XP and state flags. en-core loads it from the database when a character logs in, keeps it valid, sends it to the player's client, and saves it back.
To read or change it from another resource, use the Player API.
Characters and slots
Characters belong to a Rockstar license. One license can own several characters, and en-multicharacter shows the selection screen.
Key in config/server.lua | Default | What it does |
|---|---|---|
characters.defaultNumberOfCharacters | 3 | Character slots per license |
characters.playersNumberOfCharacters | {} | Per-license override, keyed by the full identifier |
-- config/server.lua
characters = {
playersNumberOfCharacters = {
['license:0123456789abcdef'] = 5,
},
defaultNumberOfCharacters = 3,
},PlayerData shape
PlayerData = {
source = 1,
citizenid = 'ENCAB12CD34',
license = 'license:...',
name = 'John Doe',
money = { cash = 500, bank = 1000 },
charinfo = {
firstname = 'John', lastname = 'Doe', birthdate = '1990-01-01',
gender = 0, nationality = 'USA', phone = '555-0123', account = 'US...',
},
job = { name, label, grade, payment, isboss, onduty, type },
jobs = { survivor = 0, scavenger = 1 },
group = { id, name, tag, grade, rank, isleader }, -- or nil
metadata = { ... },
position = { x, y, z, heading },
}| Field | Type | What it holds |
|---|---|---|
source | number | The player's server ID while online |
citizenid | string | The character's unique ID |
license | string | The Rockstar license that owns the character |
name | string | "First Last", set from charinfo when the character is created |
money | table | One balance per money type. See Money and cash. |
charinfo | table | Identity details, below |
job | table | The active job. See Jobs. |
jobs | table | Every job the character has held, mapped to its grade |
group | table or nil | The character's group summary. Attached at login, not stored on the character row. See Groups. |
metadata | table | Survival stats, XP and state flags, below |
position | table | The last saved position and heading |
Items are not part of PlayerData. Inventory belongs to en-inventory.
charinfo
| Key | Default if missing | Notes |
|---|---|---|
firstname | 'John' | |
lastname | 'Doe' | |
birthdate | '1990-01-01' | |
gender | 0 | 0 male, 1 female |
nationality | 'USA' | |
phone | generated | From identifierFormat.phoneNumber() |
account | generated | From identifierFormat.accountNumber() |
backstory | none | Optional |
Identifiers
Identifier formats are functions in config/server.lua, so you can change them.
| Function | Default output | Notes |
|---|---|---|
player.identifierFormat.citizenid() | ENC plus 8 random letters and digits | Retried against the database up to 25 times until unique |
player.identifierFormat.phoneNumber() | 555-0000 to 555-9999 | Used when charinfo.phone is missing |
player.identifierFormat.accountNumber() | US plus 14 digits | Used when charinfo.account is missing |
-- config/server.lua, inside player = { ... }
identifierFormat = {
citizenid = function()
local chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
local out = { 'WL' }
for i = 1, 8 do
local n = math.random(#chars)
out[i + 1] = chars:sub(n, n)
end
return table.concat(out)
end,
-- phoneNumber and accountNumber as before
},Existing characters keep their old citizenids. Keep the ID short enough for the citizenid VARCHAR(50) column.
How data is checked on load
Every time a character loads, en-core cleans up its data so other resources can rely on it:
- Missing
charinfofields get the defaults in the table above. - A job that no longer exists in
shared/jobs.luafalls back tosurvivorgrade 0. A savedonduty = falseis kept. - Old
gangandgangsdata is removed. EndCore uses groups instead. - An invalid position is replaced with
defaultSpawn. - Any metadata key from
player.defaultMetadatathat is missing is filled in with its default. metadata.levelis recalculated frommetadata.xp.
Metadata
| Key | Default | Notes |
|---|---|---|
health | 200 | Reported by the client every 10 s when it changes, clamped 0–200 |
armor | 0 | Same as health, clamped 0–100 |
hunger | 100 | 0–100. Drains every survival tick. |
thirst | 100 | 0–100. Drains every survival tick. |
stamina | 100 | Stored only; en-core does not simulate it |
stress | 0 | Stored only; en-core does not simulate it |
radiation | 0 | 0–1000 by default (survival.radiation.max) |
infection | 0 | 0–100 by default (survival.infection.max) |
immunity | 100 | 0–100 by default (survival.immunity.max) |
temperature | 98.6 | Body temperature in °F. The setter clamps it to 70–120. |
xp | 0 | Total XP |
level | worked out | Derived from xp. See XP and levels. |
isdead | false | Set by the death and revive flow |
inlaststand | false | Set by the last stand event |
receivedStarterItems | not set | true after the starter kit is given |
cashSynced | not set | Cash item bookkeeping |
cashPending | not set | Cash owed as items that did not fit |
The survival stats are explained on Survival system, and the cash keys on Money and cash.
Adding your own metadata
Your resources can store their own values in metadata; they are saved with the character. To give every character a default, add the key to player.defaultMetadata. It is filled in on the next load for any character that does not have it yet.
-- config/server.lua, inside player = { ... }
defaultMetadata = {
health = 200,
armor = 0,
hunger = 100,
thirst = 100,
stamina = 100,
stress = 0,
radiation = 0,
infection = 0,
immunity = 100,
temperature = 98.6,
xp = 0,
isdead = false,
inlaststand = false,
zombiekills = 0, -- your own key
},Reading and writing metadata
| Where | Call | Notes |
|---|---|---|
| Server, player object | player.Functions.GetMetadata(key) | |
| Server, player object | player.Functions.SetMetadata(key, value, save?) | Sends the full PlayerData to the client |
| Server, player object | player.Functions.SetMetadataBulk(values) | One client update for several keys |
| Server export | GetPlayerMetadata(source, key?) | Cheap. Returns the whole table when key is nil. |
| Server export | SetPlayerMetadata(source, key, value) | Cheap. Sends only the changed key. |
| Client export | GetMetadata(key?) | Returns a stored false correctly |
Every write fires encore:server:onSetMetaData(source, key, value) on the server and encore:client:onSetMetaData(key, value) on the client.
New characters
Key in config/shared.lua | Default | What it does |
|---|---|---|
defaultSpawn | vec4(-258.211, -293.077, 21.6114, 206.0) | Where new characters appear, and the fallback for an invalid saved position |
defaultMoney | { cash = 500, bank = 1000 } | Starting balances |
starterItems | 2 water, 3 bread, 1 radio | Given once per character |
Starter items are given when the character loads, through the library's inventory interface. Each entry has a name, an amount and optional metadata, which can be a table or a function(source) that returns one. Once given, metadata.receivedStarterItems is set to true, so the kit is never given twice. If no inventory is running, the kit waits for a later login.
-- config/shared.lua
starterItems = {
{
name = 'water',
amount = 2,
metadata = function(source)
return { durability = 100, description = 'Clean water - essential for survival' }
end,
},
{ name = 'bandage', amount = 2, metadata = {} },
{ name = 'flashlight', amount = 1, metadata = {} },
},Items must exist in en-inventory. See Add an item.
On the client
The client gets the full PlayerData on login (encore:client:onPlayerLoaded) and every time a player function changes it (encore:client:playerDataUpdate). Smaller changes arrive as encore:client:onSetMetaData(key, value). The export GetPlayerData() always returns the latest copy.
Examples
Count zombie kills in your own metadata key:
-- server
local function addKill(source)
local kills = exports['en-core']:GetPlayerMetadata(source, 'zombiekills') or 0
exports['en-core']:SetPlayerMetadata(source, 'zombiekills', kills + 1)
endRead a character's identity:
-- server
local player = exports['en-core']:GetPlayer(source)
if player then
local info = player.PlayerData.charinfo
print(player.PlayerData.citizenid, info.firstname, info.lastname, info.phone)
endShow survival stats on the client:
-- client
local data = exports['en-core']:GetPlayerData()
if data then
local meta = data.metadata
print(('Hunger %d, thirst %d, radiation %d'):format(meta.hunger, meta.thirst, meta.radiation))
end