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, idtrader_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 = trueand fires the server eventencore:content:ready. The flag is reset tofalsewhen en-core restarts. - Saves are live in memory immediately and written to the
encore_contenttable in the background. - Rows that cannot be decoded are skipped with a console warning.
Exports
| Export | Arguments | Returns |
|---|---|---|
IsContentReady() | none | boolean |
GetContent(kind) | kind | table<id, data>, or nil until loaded |
GetContentEntry(kind, id) | kind, id | data or nil |
SaveContent(kind, id?, data, author?) | kind, id, data, author | true, id or false, error |
DeleteContent(kind, id) | kind, id | boolean |
SaveContent rules:
kindandidmay contain letters, digits,_,-,.and:, up to 80 characters.- Pass
nilas 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 withvec3orvec4when you read them. authoris stored in theupdated_bycolumn. It is useful for tracking who placed what.
Broadcasts
| Side | Event | Payload |
|---|---|---|
| Server | encore:content:ready | none |
| Server | encore:content:changed | kind, id, data (data is nil after a delete) |
| Client (net, all clients) | encore:client:contentChanged | kind, 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:
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)
endSpawn every trader once the registry is ready, and react to changes:
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:
exports['en-core']:DeleteContent('trader', 'trader_3fa1')