OPX//77
opx_infinitycharacter

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 Player table, 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).
  • Result is { ok = true, value = ... } or { ok = false, error = code, detail = ... }.
  • Money: types come from MONEY.TYPES in config/shared.lua (EDDIES, BANK shipped). The mutators work on loaded characters only, answer ok, code (code a locale key) and do not yield; the autosave writes the change. Every successful change sends opx:net:character:money to 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 source and act on that account only. Somebody else's character answers character.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 admin module does).

GetPlayer

The loaded character on a player id.

character.GetPlayer(source)

Parameters

  • source: number

Returns

  • Player? — nil for a connection with no character
Example
local player = character.GetPlayer(source)
if player then print(player.PlayerData.citizenId) end

GetPlayerByCitizenId

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.

Returns

  • boolean, string? — ok, reason
Example
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 — true stamps last_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
Example
local ok, code = character.AddMoney(source, 'BANK', 250, 'delivery')
if not ok then print(code) end

RemoveMoney

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 — default MONEY.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, audits money.addOffline and publishes opx:on:character:money with offline = true.
character.AddMoneyOffline(citizenId, moneyType, amount, reason)

Parameters

  • citizenId: string
  • moneyType: string
  • amount: number
  • reason?: string

Returns

  • Result — { balance, offline }; errors character.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); error character.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, or nil to 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 }; errors character.notFound, error.badRequest, error.unavailable, or check'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
Example
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 } } }; nil for 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 — the Player

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: table
    • mode: 'recent' (last played first) or 'search'
    • term?: string — 3–48 bytes, against name, account name, citizen id
    • seenAt?, cursor? — from the previous page

Returns

  • Result — { characters, mode, more, cursor, seenAt }
Example
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.

On this page