Server
Last updated on
The character contract on the server — players, money, metadata, jobs and gangs, characters and accounts.
local character = OPX.Api.Get('character')From server code inside opx_infinity.
- An identifier is a
Playertable, a player id (number) or a citizen id (string). Only a citizen id reaches an offline character. - Yields: reads or writes the database; run it in a coroutine (
CreateThread). Resultis{ ok = true, value = ... }or{ ok = false, error = code, detail = ... }.- Money: types come from
MONEY.TYPESinconfig/shared.lua(EDDIES,BANKshipped). The mutators work on loaded characters only, answerok, code(codea locale key) and do not yield; the autosave writes the change. Every successful change sendsopx:net:character:moneyto the owner, raises the internal money event and writes an audit line. - Jobs and gangs: a character holds any number of each, one grade per membership; one of each is primary (
PlayerData.job,PlayerData.gang). Functions that write membership rows yield even for a loaded character. With an offline character's citizen id, the change is written to the row. - Self-service character functions take the caller's
sourceand act on that account only. Somebody else's character answerscharacter.notFound(same as a missing one) and the attempt is audited. - Staff functions do no permission check: call them only after your own ACL check (the
adminmodule does).
GetPlayer
The loaded character on a player id.
character.GetPlayer(source)Parameters
- source:
number
Returns
Player?— nil for a connection with no character
local player = character.GetPlayer(source)
if player then print(player.PlayerData.citizenId) endGetPlayerByCitizenId
Loaded characters only.
character.GetPlayerByCitizenId(citizenId)Parameters
- citizenId:
string
Returns
Player?
GetPlayerByUserId
Loaded characters only.
character.GetPlayerByUserId(userId)Parameters
- userId:
string
Returns
Player?
GetPlayers
Every loaded character. Evicts slots whose account changed.
character.GetPlayers()Returns
Player[]
GetPlayerCount
Count without building a table.
character.GetPlayerCount()Returns
integer
ResolvePlayer
Turns any identifier into a loaded Player.
character.ResolvePlayer(identifier)Parameters
- identifier:
Player | number | string
Returns
Player?
GetCharacter
Yields when offline.
character.GetCharacter(citizenId)Parameters
- citizenId:
string
Returns
Result—{ player, offline = false }or{ entity, offline = true }(the stored row)
PlaceCharacter
Kill-then-respawn placement. Yields.
character.PlaceCharacter(player, target)Parameters
- player:
Player - target?:
{ x, y, z, heading?, bucket? }- Omitted: the stored position, then
DEFAULT_SPAWN.
- Omitted: the stored position, then
Returns
boolean,string?—ok, reason
CreateThread(function()
local ok, why = character.PlaceCharacter(player, { x = 0.0, y = 0.0, z = 50.0, heading = 90.0 })
if not ok then print(why) end
end)Save
Writes the row now. Yields.
character.Save(identifier, loggedOut)Parameters
- identifier:
Player | number | string - loggedOut?:
boolean—truestampslast_logged_out
Returns
Result
Logout
Unloads the character and dispatches its save on a thread. The player stays connected, in a private bucket.
character.Logout(source)Parameters
- source:
number
AddMoney
Runs hook money:beforeAdd.
character.AddMoney(identifier, moneyType, amount, reason)Parameters
- identifier:
Player | number | string - moneyType:
string - amount:
number— rounded; must be positive and finite - reason?:
string
Returns
boolean,string—ok, code
local ok, code = character.AddMoney(source, 'BANK', 250, 'delivery')
if not ok then print(code) endRemoveMoney
Refuses (money.insufficient) rather than going below zero, unless the type is in MONEY.ALLOW_NEGATIVE. Runs money:beforeRemove. The hook may yield: afterwards the player must still be loaded and the balance is checked again, so one balance cannot pay twice.
character.RemoveMoney(identifier, moneyType, amount, reason)Parameters
- identifier:
Player | number | string - moneyType:
string - amount:
number - reason?:
string
Returns
boolean,string—ok, code
SetMoney
Sets the balance outright; the only way to reach zero. Runs money:beforeSet.
character.SetMoney(identifier, moneyType, amount, reason)Parameters
- identifier:
Player | number | string - moneyType:
string - amount:
number - reason?:
string
Returns
boolean,string—ok, code
GetMoney
character.GetMoney(identifier, moneyType)Parameters
- identifier:
Player | number | string - moneyType?:
string— nil returns the whole money table
Returns
integer | table | nil— nil when not loaded
FormatMoney
OPX.Locale.Money: the language's thousands separator and the currency word from the catalogue (4,500 €$, 500 €$ (bank); French 4 500 €$, 500 €$ (banque)). Every dealership, shop, crafting, hauling and paycheck amount goes through it.
character.FormatMoney(amount, moneyType)Parameters
- amount:
number - moneyType?:
string— defaultMONEY.DEFAULT
Returns
string
IsMoneyType
Whether the type exists on this server.
character.IsMoneyType(moneyType)Returns
boolean
AddMoneyOffline
Adds money whether or not the character is being played. Yields.
- Online: same as
AddMoney(offline = false). - Offline: runs hook
money:beforeAddOffline, writes the row directly, auditsmoney.addOfflineand publishesopx:on:character:moneywithoffline = true.
character.AddMoneyOffline(citizenId, moneyType, amount, reason)Parameters
- citizenId:
string - moneyType:
string - amount:
number - reason?:
string
Returns
Result—{ balance, offline }; errorscharacter.notFound,money.badType,money.badAmount,money.vetoed,error.unavailable(row being written, or boot failed)
GetMetadata
character.GetMetadata(identifier, key)Parameters
- identifier:
Player | number | string - key?:
string— nil returns the whole table
Returns
any— nil when not loaded
SetMetadata
Loaded characters only. Sends the new PlayerData to the owner.
character.SetMetadata(identifier, key, value)Parameters
- identifier:
Player | number | string - key:
string - value:
any
Returns
boolean
ReadMetadata
A character's whole metadata, online or not. Yields for an offline one (the row is read).
character.ReadMetadata(citizenId)Returns
Result—{ metadata, offline }(a copy); errorcharacter.notFound
WriteMetadata
Writes one metadata key of a character, online or not, held in the offline ledger like AddMoneyOffline. Yields. Online, it is written in memory through SetMetadata; offline, one key of the row (JSON_SET / JSON_REMOVE on a quoted path).
character.WriteMetadata(citizenId, key, value, check)Parameters
- citizenId:
string - key:
string—[A-Za-z0-9_.-], up to 128 bytes - value:
any— plain data, ornilto remove the key - check?:
function—check(metadata)with the metadata as it stands at the write; a string answer refuses with that code
Returns
Result—{ offline }; errorscharacter.notFound,error.badRequest,error.unavailable, orcheck's code
SetName
Write-once: refused with character.nameSet if a name exists. Each half is checked against CHARACTERS.NAME. Yields (saves at once).
character.SetName(identifier, firstName, lastName)Parameters
- identifier:
Player | number | string - firstName, lastName:
string
Returns
Result
PublicView
Copies, no metadata. What the GetPlayerData export and opx:on:character:loaded carry.
character.PublicView(player)Parameters
- player:
Player
Returns
table—{ source, citizenId, userId, firstName, lastName, money, job, gang, jobs, gangs }
SetBodyFamily
Write-once (character.bodySet); the same value again is OK. Called by the appearance module. Yields.
character.SetBodyFamily(identifier, family)Parameters
- identifier:
Player | number | string - family:
'female' | 'male'
Returns
Result
GetJob
The job definition from JOBS.
character.GetJob(name)Returns
table?
GetGang
The gang definition from GANGS.
character.GetGang(name)Returns
table?
SetJob
Joins (or regrades) and makes primary. Duty resets to the job's defaultDuty. Runs hook job:beforeSet. Yields.
character.SetJob(identifier, name, grade)Parameters
- identifier:
Player | number | string - name:
string - grade:
integer
Returns
Result— the job table
CreateThread(function()
local res = character.SetJob(citizenId, 'ncpd', 0)
if not res.ok then print(res.error) end
end)SetGang
Same as SetJob, for gangs. Runs hook gang:beforeSet. Yields.
character.SetGang(identifier, name, grade)Returns
Result
SetJobDuty
Refused (job.noDuty) for a defaultDuty job. Toasts the owner. Yields only when offline.
character.SetJobDuty(identifier, onDuty)Parameters
- identifier:
Player | number | string - onDuty:
boolean
Returns
Result— the new duty
HasJob
Primary job only, loaded only. Never yields.
character.HasJob(identifier, name, onDutyOnly, minGrade)Parameters
- identifier:
Player | number | string - name:
string - onDutyOnly?:
boolean - minGrade?:
integer
Returns
boolean
HasGang
Primary gang only, loaded only.
character.HasGang(identifier, name, minGrade)Returns
boolean
AddPlayerToJob
Adds a membership without changing the primary job. Yields.
character.AddPlayerToJob(identifier, name, grade)Returns
Result
AddPlayerToGang
Yields.
character.AddPlayerToGang(identifier, name, grade)Returns
Result
RemovePlayerFromJob
If it was primary, the character falls back to PLAYER.DEFAULT_JOB. Yields.
character.RemovePlayerFromJob(identifier, name)Returns
Result
RemovePlayerFromGang
Falls back to PLAYER.DEFAULT_GANG. Yields.
character.RemovePlayerFromGang(identifier, name)Returns
Result
SetPlayerPrimaryJob
Refused (job.notMember) unless already a member. Yields.
character.SetPlayerPrimaryJob(identifier, name)Returns
Result
SetPlayerPrimaryGang
Refused (gang.notMember). Yields.
character.SetPlayerPrimaryGang(identifier, name)Returns
Result
GetPlayersByJob
Loaded characters whose primary job matches.
character.GetPlayersByJob(name, onDutyOnly)Returns
Player[]
GetPlayersByGang
Loaded characters whose primary gang matches.
character.GetPlayersByGang(name)Returns
Player[]
GetGroupMembers
Up to 200 members, online or not. Yields.
character.GetGroupMembers(groupType, name)Parameters
- groupType:
'job' | 'gang' - name:
string
Returns
Result—{ citizenId, grade, name }[]
ListGroups
Every job or gang this server defines, as a list: each with its grades as a list ordered by level. A copy. Never yields.
character.ListGroups(groupType)Parameters
- groupType:
'job' | 'gang'
Returns
table[]?—{ name, label, type, defaultDuty, grades = { { level, name, payment, isBoss } } };nilfor another group type
ListCharacters
Self-service. Yields.
character.ListCharacters(source)Returns
Result—{ characters, slots, active }; each summary{ citizenId, cid, firstName, lastName, gender, job, gang, lastLoggedOut }
EnterSession
The whole entry: loads the locked character, or creates one. Called by the runtime on connect. Yields.
character.EnterSession(source)Returns
Result
SelectCharacter
Loads and enters one of the caller's characters, in the world. 1 s cooldown. Yields.
character.SelectCharacter(source, citizenId)Returns
Result— thePlayer
SwitchTo
What /opx.select runs: moves the lock, then relogs or disconnects per CHARACTERS.SWITCH. Yields.
character.SwitchTo(source, citizenId)Returns
Result
NewCharacter
What /opx.create runs: clears the lock and disconnects. Yields.
character.NewCharacter(source)Returns
Result—{ used, slots }
CreateCharacter
Inserts an empty character (no name, no body) on the caller's account. Does not load it. 3 s cooldown. Yields.
character.CreateCharacter(source)Returns
Result
DeleteCharacter
Soft-deletes one of the caller's own characters. Honours SELF_DELETE. 3 s cooldown. Yields.
character.DeleteCharacter(source, citizenId)Returns
Result
ListCharactersFor
Staff. Any account's characters; summaries also carry createdAt. No ownership check. Yields.
character.ListCharactersFor(userId)Returns
Result
FindCharacters
Staff. Searches every account. 25 per page. Yields.
character.FindCharacters(query)Parameters
- query:
tablemode:'recent'(last played first) or'search'term?:string— 3–48 bytes, against name, account name, citizen idseenAt?,cursor?— from the previous page
Returns
Result—{ characters, mode, more, cursor, seenAt }
local page = character.FindCharacters({ mode = 'search', term = 'vik' })RenameCharacter
Staff. Renames any character, online or offline. Same name rules as SetName. Yields.
character.RenameCharacter(citizenId, firstName, lastName, source)Parameters
- citizenId:
string - firstName, lastName:
string - source?:
number
Returns
Result
RemoveCharacter
Staff. Soft-deletes any character. Ignores SELF_DELETE. Yields.
character.RemoveCharacter(citizenId, source)Parameters
- citizenId:
string - source?:
number
Returns
Result
Deleting a character
A delete stamps deleted_at and keeps the row, but every module that owns per-character data (clothes, needs, containers, vehicles…) really deletes its rows, and so does every CASCADE_TABLES entry. There is no undo. A character being played is disconnected.