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#
| Export | Returns | Description |
|---|
GetCharacter(src) | character | nil | The character the player is playing, nil while choosing |
GetCharacters(srcOrLicense) | character[] | All characters of a player or license |
GetState(src) | string | connecting, selecting, loading, creating, spawning, playing or offline |
IsInSelection(src) | boolean | True while the player is in any multicharacter screen |
GetPlaytime(src) | number | Playtime of the current character in seconds (stored + live) |
GetLicense(src) | string | The license used as the account key |
GetSlots(srcOrLicense) | number | Slot count (override, ACE tier or default) |
SetSlots(srcOrLicense, slots) | boolean | Sets a slot override |
AddSlots(srcOrLicense, amount) | boolean | Adds slots on top of the current count, for example after a Tebex purchase |
ResetSlots(srcOrLicense) | boolean | Removes the override |
SetCharacterDisabled(charId, state) | boolean | Locks or unlocks a character. A locked character online is sent back to the selection |
DeleteCharacter(charId) | boolean | Deletes 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) | boolean | Same as Logout(src, true) |
RegisterSpawn(data) | boolean, reason? | Adds a spawn point, removed when your resource stops |
RemoveSpawn(id) | boolean | Removes a spawn point |
GetSpawns() | table[] | Registered spawn points |
RegisterHook(name, fn) | id | Registers a hook, removed when your resource stops |
RemoveHook(id) | boolean | Removes 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.
| Hook | Arguments | Can cancel |
|---|
beforeCreate | src, identity | yes |
afterCreate | src, character | no |
beforeSelect | src, character | yes |
afterSelect | src, character | no |
beforeDelete | src, character | yes |
afterDelete | src, character | no |
beforeSpawn | src, character, spawn, isNew | yes. With koja-spawnselector it runs for every listed location and locks the rejected ones |
afterSpawn | src, character, spawn, isNew | no |
beforeLogout | src, character | yes, ignored when forced |
afterLogout | src, character | no |
buildSpawns | src, character, isNew | returns 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#
| Event | Arguments |
|---|
koja-multicharacter:server:characterCreated | src, character |
koja-multicharacter:server:characterSelected | src, character |
koja-multicharacter:server:characterDeleted | src, character |
koja-multicharacter:server:playerSpawned | src, character, spawn, isNew |
koja-multicharacter:server:characterLogout | src, 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#
| Export | Returns | Description |
|---|
GetState() | string | idle, selection, loading, creating, spawning or playing |
IsInSelection() | boolean | True in any multicharacter screen |
IsPlaying() | boolean | True 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() | boolean | Spawn protection is active |
StopSpawnProtection() | | Ends spawn protection now |
exports['koja-multicharacter']:SetRelogBlocked('You are handcuffed')
exports['koja-multicharacter']:SetRelogBlocked(false)
Client events#
| Event | Arguments |
|---|
koja-multicharacter:client:selectionOpened | |
koja-multicharacter:client:spawned | character, isNew |
koja-multicharacter:client:logout | |
Use selectionOpened and spawned to hide and show your HUD.
State bags#
| Key | Where | Value |
|---|
kojaInSelection | Player(src).state, replicated | true while choosing |
kojaCharacter | Player(src).state, replicated | { charId, slot, firstname, lastname, gender } while playing |
kojaMulticharacterState | LocalPlayer.state, client only | client state string |
local character = Player(source).state.kojaCharacter