12. Database and framework
Config.DateFormat
Section titled “Config.DateFormat”Date format written to the character row: DD/MM/YYYY, MM/DD/YYYY or
YYYY/MM/DD. The UI always collects MM / DD / YYYY and converts before saving.
ESX only. On QBCore and Qbox the birthdate is always written as YYYY-MM-DD,
because that is what every other QB resource parses charinfo.birthdate as. This
setting still governs what this resource’s own screens display.
Config.ExtraDeleteTables
Section titled “Config.ExtraDeleteTables”Tables cleared when a character is deleted, as table -> owning column.
Nearly all of them are found at start without being told. Three things are read out of the database itself:
- Every table filing rows under a character-key column. The names are in
Config.DeleteSweep.columnsbelow —citizenid,identifier,owner,user_identifierand the rest — and a column has to hold text for the sweep to use it, because matching a string key against a numeric column would make MySQL read it as0and take somebody else’s rows. - Everything holding a foreign key on one of those, so a
RESTRICTconstraint cannot stop the delete. Anything the database already clears withON DELETE CASCADEorSET NULLis left to it. - The order all of that has to run in, worked out from the constraints.
Config.ExtraDeleteTables is for what is still missed — a table that names its
column something all of its own:
Config.ExtraDeleteTables = { ["my_table"] = "identifier",}Config.DeleteSweep
Section titled “Config.DeleteSweep”How hard the character delete looks for a character’s rows.
| Option | Notes |
|---|---|
enabled |
false goes back to sweeping only the framework’s own column — citizenid on QBCore and Qbox, identifier and owner on ESX. That is all any other character screen does, and it is what leaves npwd’s tables behind |
verify |
After a delete, check each swept table and name in the console any that still hold the key. Cheap, and the only way a table nobody thought of gets noticed |
columns |
The column names treated as holding a character key. Setting this replaces the built-in list rather than adding to it — config.lua.example ships the built-in list in full to copy from |
ignore |
Tables the sweep must never touch, on top of the built-in list: multicharacter_slots, multicharacter_entitlements, multicharacter_outfits and bans, all of which outlive a character |
Never put cid in columns. On QBCore and Qbox that column is the slot number.
Config.ExtraDeleteLinks
Section titled “Config.ExtraDeleteLinks”Parent–child relationships your database does not declare.
Some resources point one table at another’s row without a real FOREIGN KEY to
say so, and then neither the constraint walk nor the column sweep can find it —
the column is usually a plain integer id, so there is no character key in it to
match. Write the relationship down and it is treated exactly like a RESTRICT
key would be:
Config.ExtraDeleteLinks = { { table = "my_child", column = "parent_id", parent = "my_parent", parentColumn = "id" },}player_outfit_codes on Qbox is built in and does not need listing. A link
naming a table or column this database does not have is reported once and
ignored.
Config.PhoneDeleteTables
Section titled “Config.PhoneDeleteTables”Tables a phone resource keys on the character’s number rather than on the
character, as table -> column.
A text conversation is joined by phone number, so those rows hold nothing saying which character they belong to and no sweep can find them. The number is read off the character before the row holding it goes, and only ever the character’s own — a call log keyed on who was rung is somebody else’s row about this character and is not taken.
Config.PhoneDeleteTables = { ["my_phone_table"] = "phone_number",}npwd’s npwd_messages_participants is built in.
Config.PhoneProfiles
Section titled “Config.PhoneProfiles”The phone’s twitter handle, claimed as each character loads.
npwd builds a handle out of the character’s name the first time the app is
opened — Jessika_Brown — and npwd_twitter_profiles.profile_name is
UNIQUE. That INSERT is the only thing that ever creates a profile and there
is nothing behind it, so a name already on the index means no profile at all:
[NPWD] [twitter] [error]: Failed to get or create profile,Duplicate entry 'Jessika_Brown' for key 'profile_name_UNIQUE'Two different things put a name there and leave it. A character deleted by another multicharacter resource, which will not have cleared npwd’s tables, holds their handle forever. And two live characters really can both be called Jessika Brown, which no amount of cleaning up will ever fix.
So the handle is worked out and written here, before npwd goes looking for one.
getOrCreateProfile then finds a profile already filed under the character and
returns it, and the INSERT that used to fail is never reached:
| The name is | What happens |
|---|---|
| free | claimed exactly as npwd would have written it |
| held by a character who no longer exists | that character’s leftovers are cleared out first, then claimed |
| held by a character who still exists | numbered — Jessika_Brown2 — and the player can rename themselves in the app |
Config.PhoneProfiles = { enabled = true,
resource = "npwd", table = "npwd_twitter_profiles", keyColumn = "identifier", nameColumn = "profile_name",
maxSuffix = 25, reclaimOrphans = true,}| Option | Notes |
|---|---|
enabled |
false leaves npwd to build its own handles, as before |
resource |
folder the phone is installed under. Only used to read its config.json and to name it in mcdiag |
table, keyColumn, nameColumn |
for a fork that has moved them. The table is what decides whether any of this runs, so a fork under another folder name still works |
maxSuffix |
how far the numbering goes before the character is left without a handle and npwd is allowed to try its own way |
reclaimOrphans |
false leaves a deleted character’s rows alone and numbers the live character instead |
Nothing in npwd is edited and nothing in npwd has to be. On a server without npwd there is no table to write to and the whole thing switches itself off. A server already getting the error needs no clean-up: the handle is freed the next time the character who wants it logs in.
A server that has turned npwd’s own twitter.generateProfileNameFromUsers off is
left alone — that setting means players pick their own handle in the app, and the
duplicate npwd reports in that mode is it answering the UI properly rather than
the failure above. npwd’s config.json is read to find out; if it cannot be read,
npwd’s own default of on is assumed.
mcdiag prints a phone handles line saying which of those states this server
is in.
Rows a previous character screen left behind
Section titled “Rows a previous character screen left behind”Every other multicharacter resource deletes on the framework’s own column alone,
so a server that has been running one of them is carrying rows for characters
that no longer exist. They stay invisible until one lands on a UNIQUE index —
npwd’s twitter handles are how this is usually noticed, as a player whose phone
will not work and a Duplicate entry ... for key 'profile_name_UNIQUE' in the
console.
/mcorphans lists them. /mcorphans clean deletes them; back the database up
first.
A column is only ever cleaned when at least one key in it belongs to a character who does still exist. That is what tells a character-key column apart from an account-key column, every row of which would otherwise look like an orphan. A column that fails the test is reported and left alone.
Config.QB — QBCore and Qbox only
Section titled “Config.QB — QBCore and Qbox only”| Option | Notes |
|---|---|
nationality |
Written to charinfo.nationality. This creator does not ask for it, and plenty of MDT and ID card resources read it |
identifier |
Which identifier players.license is keyed on |
identifier
Section titled “identifier”| Value | Behaviour |
|---|---|
"auto" |
qb-core prefers license:, qbx_core prefers license2: — and whichever of a player’s identifiers already owns characters wins, so a server that has run both keeps everybody’s characters. What you want |
"license" |
Always the FiveM licence |
"license2" |
Always the Rockstar account licence, falling back to "license" |
