EndCore Framework

Utilities

Logging, string, maths and table helpers, the waitFor polling helper, and the export-bridging helpers used by compatibility layers.

The util module loads first on both server and client. It holds the small helpers the rest of the library is built on: prefixed logging, string and table helpers, a polling helper, and two functions for answering exports on behalf of another resource.

Logging

Every message is prefixed with your resource name in brackets. The message is a string.format pattern, and any extra arguments are format arguments.

FunctionPrefix colourUse
encore.print(message, ...)CyanGeneral information
encore.warn(message, ...)YellowSomething went wrong but the resource carries on
encore.warnOnce(key, message, ...)YellowWarn only the first time a given key is seen, for conditions that would otherwise repeat every tick
lua
encore.print('Loaded %d loot tables', encore.table.size(LootTables))
encore.warn('Unknown loot table "%s"', name)

-- inside a loop that runs every frame
encore.warnOnce('missing-model:' .. model, 'Model %s does not exist', model)
Tip

Because the message is a format pattern, a stray % in it breaks the call. Never pass player-authored text as the message. Pass it as an argument instead: encore.warn('Bad name: %s', name).

Strings

FunctionReturnsNotes
encore.string.trim(str)stringStrips leading and trailing whitespace
encore.string.capitalize(str)stringFirst letter upper case, the rest lower case
encore.string.split(str, delimiter)string[]Splits on a literal delimiter, not a Lua pattern, so . and % are safe. Empty pieces are kept
lua
encore.string.trim('  Scrap Dealer ')        -- 'Scrap Dealer'
encore.string.capitalize('rADIO')            -- 'Radio'
encore.string.split('group:12', ':')         -- { 'group', '12' }
encore.string.split('a..b', '.')             -- { 'a', '', 'b' }

Maths

FunctionReturnsNotes
encore.math.round(num, decimals?)numberRounds half up to decimals places (default 0)
encore.math.clamp(num, low, high)numberKeeps num between low and high
lua
encore.math.round(12.3456, 2)          -- 12.35
encore.math.clamp(amount, 1, 100)      -- never below 1 or above 100

Tables

FunctionReturnsNotes
encore.table.size(tbl)numberCounts every key, not just the array part
encore.table.contains(tbl, value)booleanSearches all values with pairs
encore.table.mapBySubfield(tbl, field)tableRe-keys entries by one of their fields. Entries without the field are skipped
encore.table.clone(value)copyDeep copy. Handles cycles and repeated references, and keeps metatables
encore.array.contains(arr, value)booleanSearches indexes 1..#arr only
lua
local byName = encore.table.mapBySubfield({
    { name = 'bandage', heal = 10 },
    { name = 'medkit',  heal = 50 },
}, 'name')
-- byName.medkit.heal == 50

local defaults = encore.table.clone(Config.defaultLoadout)  -- safe to modify

Polling with waitFor

encore.waitFor(fn, timeout?, interval?) calls fn until it returns something other than nil, then returns that value.

ParameterDefaultMeaning
fnrequiredCalled at least once. Return nil to keep waiting
timeout10000Milliseconds before giving up. Returns nil on timeout
intervalevery frameMilliseconds between checks

waitFor yields, so call it from a thread, event handler or callback, never from the main chunk of a file.

Note

false is not nil. If fn returns false, the wait ends and waitFor returns false. Return nil when you mean "not yet".

lua
local netId = encore.waitFor(function()
    local id = NetworkGetNetworkIdFromEntity(vehicle)
    return id ~= 0 and id or nil
end, 5000, 100)

if not netId then return encore.warn('vehicle never networked') end

Export bridging

These two functions are what EndCore's QBCore, Qbox and ESX compatibility layers are built on. You only need them if you are writing a bridge or an adapter.

encore.provideExport

encore.provideExport(resource, name, fn) answers exports[resource]:name(...) on behalf of another resource name.

In FiveM, exports('Foo', fn) is shorthand for listening on the event __cfx_export_<self>_Foo. Nothing stops a resource from listening for a name it doesn't own, so provideExport listens on __cfx_export_<resource>_<name> and hands back fn.

Warning

A bridge must not also call exports() for the same names. Two answers for one export name is a race, and wrapping a name the host already exports can make it call itself.

encore.shouldBridge

encore.shouldBridge(resource) returns true when it is safe to impersonate resource. It returns false when:

  • the convar encore:disablebridge is 'true', or
  • the real resource is started or starting. In that case it prints <resource> is running; skipping its compatibility bridge.
lua
-- my-bridge/server.lua
if not encore.shouldBridge('some_legacy_core') then return end

encore.provideExport('some_legacy_core', 'GetPlayerMoney', function(source)
    return exports['en-core']:GetMoney(source, 'cash')
end)

See Compatibility bridges for the bridges en-core ships and how to turn them off.