EndCore Framework

Content registry

How en-core stores world content that admins place in game, such as traders and benches, and broadcasts changes to resources.

Admins can place much of an EndCore world while standing in it: traders, quest NPCs, crafting benches, garages and zones. The content registry is where those placements are saved. It survives restarts and tells every resource and client the moment something changes, so a new trader appears without a restart.

How it works

  • Content is stored as kind, then id, then data. For example kind trader, id trader_3fa1, data { label, coords, ... }.
  • en-core only stores and broadcasts. It does not know what a trader is. The resource that owns a kind reads its entries and validates them.
  • Everything is loaded into memory when en-core starts. When loading finishes, en-core sets GlobalState.encoreContentReady = true and fires the server event encore:content:ready. The flag is reset to false when en-core restarts.
  • Saves are live in memory immediately and written to the encore_content table in the background.
  • Rows that cannot be decoded are skipped with a console warning.

Exports

ExportArgumentsReturns
IsContentReady()noneboolean
GetContent(kind)kindtable<id, data>, or nil until loaded
GetContentEntry(kind, id)kind, iddata or nil
SaveContent(kind, id?, data, author?)kind, id, data, authortrue, id or false, error
DeleteContent(kind, id)kind, idboolean

SaveContent rules:

  • kind and id may contain letters, digits, _, -, . and :, up to 80 characters.
  • Pass nil as the id to generate one in the form <kind>_<hex>.
  • Data is stripped to JSON-safe values. Vectors become { x, y, z } or { x, y, z, w } tables, so convert them back with vec3 or vec4 when you read them.
  • author is stored in the updated_by column. It is useful for tracking who placed what.

Broadcasts

SideEventPayload
Serverencore:content:readynone
Serverencore:content:changedkind, id, data (data is nil after a delete)
Client (net, all clients)encore:client:contentChangedkind, id, data (nil after a delete)

Clients can fetch every entry of a kind with the callback encore:content:get(kind). The library wraps all of this for you, including waiting for the registry to be ready. See Content API.

Examples

Save a trader an admin just placed:

lua
local coords = GetEntityCoords(GetPlayerPed(source))
local heading = GetEntityHeading(GetPlayerPed(source))

local ok, id = exports['en-core']:SaveContent('trader', nil, {
    label = 'Scrap Dealer',
    coords = vec4(coords.x, coords.y, coords.z, heading),
}, GetPlayerName(source))

if not ok then
    print('could not save trader: ' .. id)
end

Spawn every trader once the registry is ready, and react to changes:

lua
local function spawnTrader(id, data)
    local c = data.coords
    -- create your ped at vec4(c.x, c.y, c.z, c.w)
end

local function loadAll()
    for id, data in pairs(exports['en-core']:GetContent('trader') or {}) do
        spawnTrader(id, data)
    end
end

if exports['en-core']:IsContentReady() then
    loadAll()
end
AddEventHandler('encore:content:ready', loadAll)

AddEventHandler('encore:content:changed', function(kind, id, data)
    if kind ~= 'trader' then return end
    if data then
        spawnTrader(id, data)
    else
        -- remove the trader with this id
    end
end)

Remove an entry:

lua
exports['en-core']:DeleteContent('trader', 'trader_3fa1')