Skip to content

12. Database and framework

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.

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:

  1. Every table filing rows under a character-key column. The names are in Config.DeleteSweep.columns below — citizenid, identifier, owner, user_identifier and 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 as 0 and take somebody else’s rows.
  2. Everything holding a foreign key on one of those, so a RESTRICT constraint cannot stop the delete. Anything the database already clears with ON DELETE CASCADE or SET NULL is left to it.
  3. 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",
}

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 itconfig.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.

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.

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.

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.

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
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"