Koja Scripts

Developer API

Koja Multicharacter server and client exports, hooks that can cancel actions, RegisterSpawn, events and state bags.

Developer API

Every export is called as exports['koja-multicharacter']:Name(...).

Character object

Hooks, events and exports pass characters in this shape:

{
    charId = 'char1:2a8f...',   -- ESX identifier or QB citizenid
    slot = 1,
    firstname = 'John',
    lastname = 'Carter',
    dateofbirth = '1994-05-12',
    age = 31,
    gender = 'male',           -- 'male' | 'female'
    nationality = 'American',
    height = 182,
    job = { name = 'police', label = 'LSPD', grade = 'Officer' },
    gang = nil,                -- QB: { name, label, grade }
    cash = 500, bank = 12000,
    playtime = 7200,           -- seconds
    lastPlayed = 1761580000, createdAt = 1761000000,
    disabled = false,
    backstory = '...',
}

Server exports

ExportReturnsDescription
GetCharacter(src)character | nilThe character the player is playing, nil while choosing
GetCharacters(srcOrLicense)character[]All characters of a player or license
GetState(src)stringconnecting, selecting, loading, creating, spawning, playing or offline
IsInSelection(src)booleanTrue while the player is in any multicharacter screen
GetPlaytime(src)numberPlaytime of the current character in seconds (stored + live)
GetLicense(src)stringThe license used as the account key
GetSlots(srcOrLicense)numberSlot count (override, ACE tier or default)
SetSlots(srcOrLicense, slots)booleanSets a slot override
AddSlots(srcOrLicense, amount)booleanAdds slots on top of the current count, for example after a Tebex purchase
ResetSlots(srcOrLicense)booleanRemoves the override
SetCharacterDisabled(charId, state)booleanLocks or unlocks a character. A locked character online is sent back to the selection
DeleteCharacter(charId)booleanDeletes a character, also offline
Logout(src, force?)boolean, reason?Sends the player back to the selection. Without force the beforeLogout hooks can cancel it
OpenSelection(src)booleanSame as Logout(src, true)
RegisterSpawn(data)boolean, reason?Adds a spawn point, removed when your resource stops
RemoveSpawn(id)booleanRemoves a spawn point
GetSpawns()table[]Registered spawn points
RegisterHook(name, fn)idRegisters a hook, removed when your resource stops
RemoveHook(id)booleanRemoves a hook

RegisterSpawn

exports['koja-multicharacter']:RegisterSpawn({
    id = 'vespucci_pd',
    label = 'Vespucci PD',
    description = 'Beach precinct',
    icon = 'shield',
    category = 'job',                -- last | public | job | gang | property | any string
    categoryLabel = 'Workplace',     -- optional
    coords = vec4(-1108.2, -845.1, 19.3, 128.0),
    jobs = { police = 2 },           -- job -> minimum grade, or a list { 'police', 'sheriff' }
    gangs = nil,                     -- same format, QB gangs
    cooldown = 300,                  -- seconds before this character can use it again
    hideLocked = false,              -- true hides it instead of greying it out
    newCharacters = false,           -- false: never offered to brand new characters
    canUse = function(src, character)
        return true                  -- or false, 'reason shown in the UI'
    end,
})

Icons the UI ships: anchor, building, building-2, bus, car, castle, church, crosshair, factory, flame, fuel, gavel, home, hospital, hotel, house, landmark, map-pin, mountain, navigation, plane, shield, shield-alert, shopping-bag, ship, siren, skull, store, tent, train-front, trees, umbrella, warehouse, wrench. Anything else shows map-pin.

Hooks

Hooks run in registration order. A before* hook that returns false, 'reason' cancels the action and the reason is shown to the player. after* hooks cannot cancel.

HookArgumentsCan cancel
beforeCreatesrc, identityyes
afterCreatesrc, characterno
beforeSelectsrc, characteryes
afterSelectsrc, characterno
beforeDeletesrc, characteryes
afterDeletesrc, characterno
beforeSpawnsrc, character, spawn, isNewyes. With koja-spawnselector it runs for every listed location and locks the rejected ones
afterSpawnsrc, character, spawn, isNewno
beforeLogoutsrc, characteryes, ignored when forced
afterLogoutsrc, characterno
buildSpawnssrc, character, isNewreturns a list of spawn tables (same fields as RegisterSpawn)
exports['koja-multicharacter']:RegisterHook('beforeCreate', function(src, identity)
    if identity.lastname:lower() == 'santa' then
        return false, 'Santa is not available this year.'
    end
end)

exports['koja-multicharacter']:RegisterHook('buildSpawns', function(src, character, isNew)
    local spawns = {}
    for _, house in ipairs(MyHousing.GetOwned(character.charId)) do
        spawns[#spawns + 1] = {
            id = 'house_' .. house.id,
            label = house.label,
            icon = 'house',
            category = 'property',
            coords = house.entrance,
        }
    end
    return spawns
end)

exports['koja-multicharacter']:RegisterHook('afterSpawn', function(src, character, spawn, isNew)
    if isNew then
        KojaLib.Server.addMoney(src, 2500, 'bank', 'starter bonus')
    end
end)

Server events

EventArguments
koja-multicharacter:server:characterCreatedsrc, character
koja-multicharacter:server:characterSelectedsrc, character
koja-multicharacter:server:characterDeletedsrc, character
koja-multicharacter:server:playerSpawnedsrc, character, spawn, isNew
koja-multicharacter:server:characterLogoutsrc, character
AddEventHandler('koja-multicharacter:server:playerSpawned', function(src, character, spawn, isNew)
    print(('%s %s spawned at %s'):format(character.firstname, character.lastname, spawn.label))
end)

Client exports

ExportReturnsDescription
GetState()stringidle, selection, loading, creating, spawning or playing
IsInSelection()booleanTrue in any multicharacter screen
IsPlaying()booleanTrue once spawned
GetCharacter(){ firstname, lastname }Name of the current character
CanRelog()boolean, reason?Whether /relog would work right now
Relog()boolean, reason?Runs the same flow as /relog
SetRelogBlocked(reason | false)Blocks switching characters for your resource until you pass false
IsSpawnProtected()booleanSpawn protection is active
StopSpawnProtection()Ends spawn protection now
exports['koja-multicharacter']:SetRelogBlocked('You are handcuffed')

exports['koja-multicharacter']:SetRelogBlocked(false)

Client events

EventArguments
koja-multicharacter:client:selectionOpened
koja-multicharacter:client:spawnedcharacter, isNew
koja-multicharacter:client:logout

Use selectionOpened and spawned to hide and show your HUD.

State bags

KeyWhereValue
kojaInSelectionPlayer(src).state, replicatedtrue while choosing
kojaCharacterPlayer(src).state, replicated{ charId, slot, firstname, lastname, gender } while playing
kojaMulticharacterStateLocalPlayer.state, client onlyclient state string
local character = Player(source).state.kojaCharacter