fw_characters¶
Server-only API for one character row. Version 1.0.0. Registers with fw_core API 1.
Source: resources/[framework]/fw_characters.
This resource owns fw_characters. It does not open a menu, spawn a ped, or move the player — fw_creator does that and calls these exports. Any later resource that needs the active character should call these exports too, not read the table directly.
Startup¶
IsReady() is false until config loads, fw_core is ready, sql/characters.sql has been applied, and RegisterResource(1) succeeds. The wait for core is 120 seconds; if it expires, the log is fw_core is not ready and the character table is not created.
On success:
[fw_characters][info] ready version=1.0.0
Debug lines need:
set fw_characters_debug true
Creation, deletion, startup, and errors print regardless of the convar.
Exports¶
| Export | Summary |
|---|---|
IsReady() |
Whether the resource is ready to serve calls. |
GetCharacters(source) |
The lightweight list of an account's characters. |
GetCharacter(source, characterId) |
A full character record, by id. |
GetActiveCharacter(source) |
The full record of the currently selected character. |
CreateCharacter(source, data) |
Inserts a new character row. |
UpdateCharacter(source, characterId, data) |
Changes identity fields on an existing row. |
SelectCharacter(source, characterId) |
Makes a character the active one for a source. |
ClearCharacter(source) |
Deselects the active character for a source. |
SetAppearance(source, characterId, value) |
Replaces the appearance document. |
SetClothing(source, characterId, value) |
Replaces the clothing document. |
SetBarber(source, characterId, value) |
Replaces the barber document. |
SaveState(source, state) |
Writes position and health for the active character. |
DeleteCharacter(source, characterId) |
Deletes an owned row. |
Every export that takes a characterId checks that source is an attached fw_core player and that the row's player_id is that account. A missing row and someone else's row both return 'character not found'. When the resource is not ready, every export returns nil, 'fw_characters is not ready'.
IsReady()¶
Signature
---@return boolean
exports.fw_characters:IsReady()
Parameters — none.
Returns
| Type | Description |
|---|---|
boolean |
true once config, fw_core, the schema, and registration all succeeded. false otherwise, including during the startup wait. |
GetCharacters(source)¶
Signature
---@param source integer
---@return table[]|nil characters
---@return string|nil err
exports.fw_characters:GetCharacters(source)
Every character on that account, ordered by slot. Documents (appearance, clothing, barber) are omitted — see Character record for the has* flags that say whether each exists.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source |
integer |
yes | Server id of the connected player. |
Returns
| Type | Description |
|---|---|
table[] |
List of character records (no documents), ordered by slot. |
nil, 'player is not loaded' |
source is not an attached player. |
Example
local list, err = exports.fw_characters:GetCharacters(source)
if not list then
print(err)
return
end
for index = 1, #list do
local character = list[index]
print(character.id, character.slot, character.firstName, character.hasAppearance)
end
GetCharacter(source, characterId)¶
Signature
---@param source integer
---@param characterId integer
---@return table|nil character
---@return string|nil err
exports.fw_characters:GetCharacter(source, characterId)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source |
integer |
yes | Server id of the connected player. |
characterId |
integer |
yes | fw_characters.id to read. Must be owned by source's account. |
Returns
| Type | Description |
|---|---|
table |
Full copy of the record, including appearance, clothing, and barber. |
nil |
See Errors. |
Errors
err value |
Condition |
|---|---|
'player is not loaded' |
source is not an attached player. |
'character id must be a positive integer' |
characterId is missing, fractional, or below 1. |
'character not found' |
No row for that id on that account. |
GetActiveCharacter(source)¶
Signature
---@param source integer
---@return table|nil character
---@return string|nil err
exports.fw_characters:GetActiveCharacter(source)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source |
integer |
yes | Server id of the connected player. |
Returns
| Type | Description |
|---|---|
table |
Full copy of the character currently selected for source. |
nil, nil |
The resource is ready but nobody is selected. |
nil, err |
The resource is not ready. |
The active record lives in memory. SaveState and the document setters update it in place; identity updates update it only when the active id is the one being edited.
CreateCharacter(source, data)¶
Signature
---@param source integer
---@param data table
---@return integer|nil characterId
---@return string|nil err
exports.fw_characters:CreateCharacter(source, data)
Inserts a row and returns the new id. It does not select the character — call SelectCharacter afterward if it should become active. Position and health on the new row come from config (FwCharactersConfig.defaultSpawn), not from data.
Parameters
source: integer — server id of the connected player.
data: table —
| Field | Required | Rule |
|---|---|---|
firstName |
yes | String, 1–50 UTF-8 characters. Trimmed. No NUL or control characters. |
lastName |
yes | Same as firstName. |
gender |
yes | String, trimmed and lowercased. male or female; Male is accepted. |
birthDate |
no | YYYY-MM-DD inside the config year range, or omit. Must be a real calendar date, leap days included. Empty string stores NULL. |
nickname |
no | 1–50 characters, or '' to store NULL. |
description |
no | Up to 2000 characters. Newlines allowed. Omitted becomes ''. |
slot |
no | Integer from 1 through that account's character_slots. Omitted uses the lowest free slot. |
appearance |
no | Table, encoded JSON ≤ 262144 bytes. |
clothing |
no | Same limit. |
barber |
no | Same limit. |
The slot limit is read from fw_players.character_slots at create time (falling back to the connected player's cached characterSlots if that read fails).
Returns
| Type | Description |
|---|---|
integer |
The new fw_characters.id. |
nil, err |
See Errors. |
Errors
err value |
Condition |
|---|---|
'player is not loaded' |
source is not attached. |
'character data must be a table' |
data is not a table. |
'first name must be text' |
firstName is not a string. Same shape for last name, nickname, description. |
'first name contains unsupported characters' |
NUL, broken UTF-8, or a control character. |
'first name must be 1 to 50 characters' |
Empty or too long (description uses 2000). |
'gender must be male or female' |
Missing or any other value. |
'birth date must be YYYY-MM-DD' |
Present and not that shape. |
'birth date must be a real date from 1800 to 1899' |
Out of the config's year range, or not a real day. |
'appearance must be a table' |
The field is present and not a table (same for clothing, barber). nil means "do not set." |
'could not encode appearance' |
json.encode failed. |
'appearance is too large' |
Encoded size over 262144 bytes. |
'character slot limit reached' |
The account already has character_slots rows, or the limit is below 1. |
'character slot is not available' |
The requested slot is out of range, taken, or hit the unique key. |
'could not create the character' |
The insert failed for another reason, or the new id could not be read. |
Example
local characterId, err = exports.fw_characters:CreateCharacter(source, {
firstName = 'Jane',
lastName = 'Doe',
gender = 'female',
birthDate = '1870-04-12',
nickname = 'JD',
description = 'A line of lore.',
})
if not characterId then
print(err)
return
end
local selected, selectErr = exports.fw_characters:SelectCharacter(source, characterId)
if not selected then
print(selectErr)
end
Fires fw_characters:created with (source, character) after the insert — the character is a full copy and is not yet active.
UpdateCharacter(source, characterId, data)¶
Signature
---@param source integer
---@param characterId integer
---@param data table
---@return boolean|nil success
---@return string|nil err
exports.fw_characters:UpdateCharacter(source, characterId, data)
Updates only the identity fields present in data; omitted fields are left unchanged. Never touches documents, position, or health.
Parameters
source: integer, characterId: integer — must be owned by source's account.
data: table —
| Field | Effect when present |
|---|---|
firstName, lastName, gender |
Replaced. Same validation as CreateCharacter. |
nickname |
Replaced. '' sets NULL. |
birthDate |
Replaced. '' sets NULL. |
description |
Replaced. '' stores an empty string. |
Returns
| Type | Description |
|---|---|
true |
Row updated (or already matched the given values — changedRows == 0 still counts as success). |
nil, err |
See Errors. |
Errors
err value |
Condition |
|---|---|
'nothing to update' |
data contains none of the updatable fields. |
'character not found' |
The id is not owned by that account. |
Fires fw_characters:updated with (source, characterId) — the event does not carry the new values; call GetCharacter for those. If characterId is the active character, the in-memory copy is updated too.
SelectCharacter(source, characterId)¶
Signature
---@param source integer
---@param characterId integer
---@return boolean|nil success
---@return string|nil err
exports.fw_characters:SelectCharacter(source, characterId)
Loads an owned character into the active slot, sets state bag fwCharacterId, and touches last_played. Does not move the ped or change routing buckets — the caller teleports.
Parameters
source: integer, characterId: integer — must be owned by source's account.
Returns
boolean|nil success, string|nil err — 'character not found' when not owned; otherwise the same errors as other id-taking exports.
Selecting the id that's already active returns true without firing anything. Selecting a different id first clears the previous one (fw_characters:cleared with the old id), then fires fw_characters:selected with (source, character) — a full copy.
ClearCharacter(source)¶
Signature
---@param source integer
---@return boolean|nil success
---@return string|nil err
exports.fw_characters:ClearCharacter(source)
Clears the active character for source.
Parameters
source: integer.
Returns
| Type | Description |
|---|---|
true |
Cleared, or there was nothing to clear. |
nil, 'player is not loaded' |
source is not a positive integer. |
Fires fw_characters:cleared with (source, characterId) only when a character was actually active. Sets fwCharacterId to nil and touches last_played. This does not require the account to still be in the fw_core cache — it's also the path used while a player is disconnecting.
SetAppearance, SetClothing, SetBarber¶
Signature
---@param source integer
---@param characterId integer
---@param value table|nil
---@return boolean|nil success
---@return string|nil err
exports.fw_characters:SetAppearance(source, characterId, value)
SetClothing and SetBarber take identical arguments — the third argument is the document for that column.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source |
integer |
yes | Server id of the connected player. |
characterId |
integer |
yes | Must be owned by source's account. |
value |
table \| nil |
yes | A table is encoded and stored (≤ 262144 bytes). nil sets the column to NULL. No keys are required inside the table — fw_creator defines the keys it writes; see fw_creator. |
Returns
boolean|nil success, string|nil err — same error set as CreateCharacter's document fields: not a table, encode failure, too large, character not found, player not loaded, bad id.
Fires fw_characters:updated with (source, characterId). If this character is active, the in-memory copy of that document is replaced. (SaveState is the only write that does not fire updated.)
Example
local ok, err = exports.fw_characters:SetAppearance(source, characterId, {
gender = 'Female',
skintone = 1,
})
if not ok then
print(err)
end
-- Clear the clothing document.
exports.fw_characters:SetClothing(source, characterId, nil)
SaveState(source, state)¶
Signature
---@param source integer
---@param state table
---@return boolean|nil success
---@return string|nil err
exports.fw_characters:SaveState(source, state)
Writes position and health for the active character only. Never touches the JSON columns, and never fires fw_characters:updated.
Parameters
source: integer.
state: table — all fields optional, but position is all-or-nothing:
| Field | Rule |
|---|---|
x, y, z, heading |
If any one is present, all four are required and must be finite numbers. |
health |
Integer 0–65535. |
healthCore |
Integer 0–100. |
staminaCore |
Integer 0–100. |
isDead |
Boolean. |
There is no stamina-outer column — only health, healthCore, and staminaCore are stored.
Returns
boolean|nil success, string|nil err.
Errors
err value |
Condition |
|---|---|
'state must be a table' |
|
'player is not loaded' |
|
'no active character' |
SelectCharacter has not been called for this source. |
'position requires x, y, z, and heading' |
A partial position. |
'health must be an integer from 0 to 65535' |
|
'healthCore must be an integer from 0 to 100' |
|
'staminaCore must be an integer from 0 to 100' |
|
'isDead must be true or false' |
|
'nothing to save' |
state had none of these fields. |
'character not found' |
The active row disappeared. |
Example
local ok, err = exports.fw_characters:SaveState(source, {
x = -174.39,
y = 633.78,
z = 114.09,
heading = 90.0,
health = 540,
healthCore = 80,
staminaCore = 100,
isDead = false,
})
DeleteCharacter(source, characterId)¶
Signature
---@param source integer
---@param characterId integer
---@return boolean|nil success
---@return string|nil err
exports.fw_characters:DeleteCharacter(source, characterId)
Deletes an owned row. appearance, clothing, and barber go with it. If the row was active, it's cleared first (fw_characters:cleared fires), then fw_characters:deleted fires with (source, characterId). The freed slot can be used by the next CreateCharacter.
Parameters
source: integer, characterId: integer — must be owned by source's account.
Returns
boolean|nil success, string|nil err — same ownership/validation errors as the other id-taking exports.
Character record¶
GetCharacter and GetActiveCharacter return a copy of this record. GetCharacters returns the same fields minus appearance, clothing, and barber (replaced by the has* flags).
| Field | Type | Meaning |
|---|---|---|
id |
integer |
fw_characters.id. The id other resources should store. |
source |
integer |
Server id the row was loaded for. |
playerId |
integer |
fw_players.id of the owner. |
slot |
integer |
Slot on that account, 1 through character_slots. |
firstName |
string |
1–50 characters. |
lastName |
string |
1–50 characters. |
nickname |
string \| nil |
Optional, 1–50 characters. nil when the column is NULL. |
gender |
string |
male or female. |
birthDate |
string \| nil |
YYYY-MM-DD, or nil. |
description |
string |
Lore. Empty string when left blank. Max 2000 characters. |
x, y, z |
number |
Last saved position. |
heading |
number |
Last saved facing, in degrees. |
health |
integer |
Entity health, 0–65535. New characters use defaultHealth (600). |
healthCore |
integer |
0–100. Default 100. |
staminaCore |
integer |
0–100. Default 100. |
isDead |
boolean |
|
createdAt |
string |
Database timestamp. |
lastPlayed |
string \| nil |
Set on select, clear, or drop. nil until the first selection. |
hasAppearance |
boolean |
The appearance column is non-NULL. |
hasClothing |
boolean |
The clothing column is non-NULL. |
hasBarber |
boolean |
The barber column is non-NULL. |
appearance |
table \| nil |
Present on the full record only. |
clothing |
table \| nil |
Present on the full record only. |
barber |
table \| nil |
Present on the full record only. |
A document that is stored but cannot be decoded becomes nil and an error is logged — the rest of the row is still returned. The record is always a copy: changing it does not change the database; call UpdateCharacter, SetAppearance, SetClothing, SetBarber, or SaveState.
Config¶
server/config.lua, table FwCharactersConfig. Read once at startup — a bad value aborts startup and the resource stays not ready.
| Field | Current value | Rule |
|---|---|---|
defaultSpawn.x |
-174.39 |
Finite number. Used only when CreateCharacter inserts a row. |
defaultSpawn.y |
633.78 |
Finite number. |
defaultSpawn.z |
114.09 |
Finite number. |
defaultSpawn.heading |
0.0 |
Finite number. |
defaultHealth |
600 |
Integer 0–65535. |
defaultHealthCore |
100 |
Integer 0–100. |
defaultStaminaCore |
100 |
Integer 0–100. |
minBirthYear |
1800 |
Integer 1–9999, not above maxBirthYear. |
maxBirthYear |
1899 |
Integer 1–9999. |
Confirm the spawn coordinates in game. fw_creator overwrites them with the player's chosen spawn immediately after selecting the new character.
Local events¶
Server events. Listeners run before the export returns.
AddEventHandler('fw_characters:ready', function(version)
-- version: manifest string, e.g. '1.0.0'
end)
AddEventHandler('fw_characters:created', function(source, character)
-- character: full copy, not selected yet
end)
AddEventHandler('fw_characters:selected', function(source, character)
-- character: full copy of the newly active character
end)
AddEventHandler('fw_characters:cleared', function(source, characterId)
end)
AddEventHandler('fw_characters:updated', function(source, characterId)
-- identity or document save, not SaveState. The JSON is not in the event.
end)
AddEventHandler('fw_characters:deleted', function(source, characterId)
end)
A resource started after ready has already fired should call IsReady directly.
On playerDropped: an active character is cleared, last_played is updated, and fw_characters:cleared fires. fw_core:playerDropped also fires — the order between the two handlers follows resource start order. On resource stop: active rows get last_played updated, but cleared is not fired for that path.
State bag¶
Player(source).state.fwCharacterId is the selected character id, or nil when none is selected. It replicates. The account id remains fwPlayerId.
AddStateBagChangeHandler('fwCharacterId', nil, function(bagName, _, value)
local player = GetPlayerFromStateBagName(bagName)
if player == 0 then
return
end
-- value is the character id, or nil
end)
After a resource restart, a still-connected player is restored from this state bag. A restore that fails clears the bag.
Console commands¶
Restricted to the server console — a player gets no output.
fw_characters
fw_char_list <serverId>
fw_char_create <serverId> <firstName> <lastName> <male|female> [YYYY-MM-DD]
fw_char_select <serverId> <characterId>
fw_char_delete <serverId> <characterId>
| Command | Output |
|---|---|
fw_characters |
Prints ready and how many sources have an active character. |
fw_char_list |
Prints id, slot, name, and gender for that server id. |
fw_char_create |
Single-word names only (the console splits on spaces); the export accepts spaces inside firstName. Does not select the new character. |
serverId is the live server id from fw_health, after playerJoining. A character can be created only for a connected player.
Files¶
| File | Purpose |
|---|---|
fxmanifest.lua |
Version 1.0.0. Dependencies oxmysql and fw_core. |
server/config.lua |
Spawn, health defaults, birth years. |
sql/characters.sql |
Table definition. |
server/main.lua |
Schema, exports, events, console commands. |