Developer API 2.0
Everything on Developer API still works. This page covers what 2.0 adds.
Character status
Characters passed to exports, hooks and events also carry:
{
status = 'active', -- 'active' | 'dead' | 'trashed' | 'pending' | 'denied'
dead = { at = 1761580000, reason = 'Shot during the bank job' }, -- or nil
trash = { at = 1761580000, purgeAt = 1762184800 }, -- or nil
approval = { state = 'pending', at = 1761580000, reason = nil }, -- or nil
nameRequest = { firstname = 'Jon', lastname = 'Carter', reason = '...', state = 'pending', note = nil, at = 0 }, -- or nil
fields = { ... }, -- card fields from RegisterCardField
scene = 'Mission Row PD', -- label of the scene location, or nil
}
Only active characters that are not disabled can be played.
Server exports
| Export | Returns | Description |
|---|---|---|
GetCharacterStatus(charId) | status, details | Status string and { dead, trash, approval } |
SetCharacterDead(charId, reason | false) | boolean, reason? | CK a character (memorial card). false reverts it |
TrashCharacter(charId) | boolean, reason? | Moves a character to the trash (permanent delete when the trash is off) |
RestoreCharacter(charId) | boolean, reason? | Takes a character out of the trash |
ApproveCharacter(charId) | boolean, reason? | Approves a pending or denied character |
DenyCharacter(charId, reason?) | boolean, reason? | Denies a pending character |
RenameCharacter(charId, firstname, lastname) | boolean, reason? | Renames a character, also while it is online |
ApproveNameRequest(charId) / DenyNameRequest(charId, note?) | boolean, reason? | Handles a player's name change request |
GetPendingQueue() | table[] | Pending approvals and name requests |
AddNote(target, text, author?, charId?) | boolean | Staff note on an account |
GetNotes(target) | table[] | Notes of an account |
AddAudit(target, charId?, action, details?) | boolean | Writes your own entry into the account history |
GetAudit(target, limit?) | table[] | Account history, newest first |
RegisterCardField(data) / RemoveCardField(id) | boolean | Extra rows on the character card |
GetParty(src) / CreateParty(src) / JoinParty(src, code) / LeaveParty(src) | Party of a player | |
GetSeason() | string | nil | Active season name |
OpenAdminPanel(src) | boolean | Opens the admin panel for a player. No permission check, do your own |
target is a server id, a license, discord:<id> or fivem:<id>. The last two need the player to have joined once.
AddSlots and the other slot exports accept the same targets.
RegisterCardField
exports['koja-multicharacter']:RegisterCardField({
id = 'arrests',
label = 'Arrests',
icon = 'gavel',
order = 10,
badge = false,
tone = nil,
resolve = function(character, src)
local count = MySQL.scalar.await('SELECT COUNT(*) FROM mdt_arrests WHERE charid = ?', { character.charId })
if count == 0 then return nil end
return count, 'last 30 days'
end,
})
| Field | Description |
|---|---|
icon | Lucide icon name, same list as RegisterSpawn |
order | Lower = higher on the card |
badge | true also shows the value as a tag on the slot list |
tone | 'accent', 'danger' or 'muted' |
resolve | Returns value, sub?, { label?, icon?, tone? }?. nil hides the field for that character |
resolve runs every time the character list loads, so keep it fast. Fields are removed when your resource stops.
Hooks
| Hook | Arguments | Return |
|---|---|---|
beforeTrash | src, character | false, reason cancels |
afterRestore | src, character | |
beforeNameRequest | src, character, { firstname, lastname, reason } | false, reason cancels |
afterRename | charId, firstname, lastname | |
beforePartyJoin | src, code, members | false, reason cancels |
sceneLocation | src, character | { label, ped = vec4(...), hour?, weather? }. The first hook that returns a table wins |
sceneVehicle | src, character | { model, props? } forces a car, false hides it, nil keeps the default |
buildScene | src, character | A table merged into context.data of client scene hooks |
exports['koja-multicharacter']:RegisterHook('sceneLocation', function(src, character)
local house = MyHousing.GetMainHouse(character.charId)
if house then
return { label = house.label, ped = house.frontDoor, hour = 20 }
end
end)
exports['koja-multicharacter']:RegisterHook('buildScene', function(src, character)
local pet = MyPets.GetActive(character.charId)
return pet and { pet = pet.model } or nil
end)
Server events
| Event | Arguments |
|---|---|
koja-multicharacter:server:characterTrashed | charId |
koja-multicharacter:server:characterRestored | charId |
koja-multicharacter:server:characterDead | charId, reason |
koja-multicharacter:server:characterRevived | charId |
koja-multicharacter:server:characterSubmitted | src, character |
koja-multicharacter:server:characterApproved | charId |
koja-multicharacter:server:characterDenied | charId, reason |
koja-multicharacter:server:nameRequested | src, character, request |
koja-multicharacter:server:characterRenamed | charId, firstname, lastname |
koja-multicharacter:server:partyChanged | code, members |
Client exports
| Export | Description |
|---|---|
RegisterSceneHook(fn) / RemoveSceneHook() | Runs fn(ped, context) whenever a character settles in the selection scene. Return a function to clean up when the ped is removed |
GetSceneInfo() | { slot, location, coords, season, ped, vehicle } while the selection is open |
RegisterArrival(name, fn) | Adds an arrival cutscene. Set Config.Arrival.Mode = name to use it |
IsArrivalPlaying() / SkipArrival() | |
PlaySound(name) | Plays a sound from Config.Audio.Sounds |
SetMusic(path | nil) | Crossfades the music to a file in the resource ('sounds/x.mp3') or stops it |
Scene hook context: { slot, charId, ped, base = vec4, location, season, status, data }.
exports['koja-multicharacter']:RegisterSceneHook(function(ped, context)
if not context.data.pet then return end
local model = joaat(context.data.pet)
RequestModel(model)
while not HasModelLoaded(model) do Wait(0) end
local spot = GetOffsetFromEntityInWorldCoords(ped, 0.9, 0.4, -1.0)
local pet = CreatePed(28, model, spot.x, spot.y, spot.z, context.base.w, false, true)
FreezeEntityPosition(pet, true)
return function() DeleteEntity(pet) end
end)
Arrival context: { coords, character, ped, sound(name), title(), sleep(ms), camera(vec3, fov), track(entity), skipped() }.
sleep returns false once the player skips. Entities passed to track and cameras made with camera are removed when the cutscene ends, and the player is put on coords afterwards.