> For the complete documentation index, see [llms.txt](https://feron-scripts.gitbook.io/welcome/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://feron-scripts.gitbook.io/welcome/scripts/feron-multicharacter/config/appearance-misc.md).

# Appearance, Validation & Hooks

Appearance script integration, idle animation pool, creation-form validation, country / build / blood lists, locale, lifecycle hooks, and the delete cascade.

{% tabs %}
{% tab title="QBCore" %}

```lua

-- ── APPEARANCE ──────────────────────────────────────────────
-- How the saved character look is loaded onto the preview ped.
-- Supports the major QBCore / ESX appearance scripts. To add a
-- new one, drop an entry into Config.AppearanceLoaders below
-- (preview) and Config.AppearanceEditors (first-character flow).
Config.AppearanceScript = 'qb-clothing'

-- DB table + column to read saved skins from. Override if your
-- fork uses a different schema (e.g. illenium uses player_skins).
Config.AppearanceTable       = 'playerskins'
Config.AppearanceColumn      = 'skin'
Config.AppearanceModelColumn = 'model'

-- Some clothing systems (older qb-clothing forks, custom outfit
-- variation setups) store MULTIPLE rows per citizenid in the
-- skins table — one per outfit variant — and mark the currently
-- worn one with `active = 1`. Modern qb-clothing / illenium /
-- fivem-appearance keep a single row per citizenid and don't
-- need this filter.
--
-- Set to true ONLY if your skins table has an `active` column.
Config.AppearanceActiveFilter = false

-- Full override for the skin fetch. Use this when your schema is
-- too different for the Config.Appearance{Table,Column,...} options
-- to handle — e.g. JOINs across multiple tables, a non-citizenid
-- join key, or columns split across rows.
--
-- Receives (src, citizenid). Must return a table shaped:
--     { skin = <skin string|table>, model = <model string|number> }
-- or nil to fall back to the built-in query.
--
-- Define it in server_config.lua if you'd rather keep custom queries
-- out of the cosmetic config.
Config.OnGetAppearance = nil
-- Example:
-- Config.OnGetAppearance = function(src, citizenid)
--     local row = exports.oxmysql:singleSync([[
--         SELECT o.outfit_data AS skin, o.ped_hash AS model
--         FROM my_outfits o
--         INNER JOIN characters c ON c.id = o.char_id
--         WHERE c.citizenid = ? AND o.is_active = 1
--     ]], { citizenid })
--     return row
-- end

-- ── AppearanceLoaders ───────────────────────────────────────
-- Registry: how to APPLY a saved skin to the preview ped.
-- Function receives (ped, skinTable). pcall-wrapped at call site
-- so a missing dependency just no-ops. Add more entries to
-- support new clothing scripts — no code change needed.
Config.AppearanceLoaders = {
    ['qb-clothing']        = function(ped, skin)
        TriggerEvent('qb-clothing:client:loadPlayerClothing', skin, ped)
    end,
    ['illenium-appearance'] = function(ped, skin)
        exports['illenium-appearance']:setPedAppearance(ped, skin)
    end,
    ['fivem-appearance']    = function(ped, skin)
        exports['fivem-appearance']:setPedAppearance(ped, skin)
    end,
    -- ['bl_appearance'] = function(ped, skin)
    --     exports['bl_appearance']:setPedAppearance(ped, skin)
    -- end,
}

-- ── AppearanceEditors ───────────────────────────────────────
-- Registry: how to OPEN the first-character editor (face / body
-- / clothing customization) right after a brand-new character
-- spawns. Value can be:
--   * string → fired via TriggerEvent(value)
--   * function → invoked with (src) — for multi-step setups
-- Leave the entry nil to skip the editor for that script.
Config.AppearanceEditors = {
    ['qb-clothing']        = 'qb-clothing:client:CreateFirstCharacter',
    ['illenium-appearance']= 'illenium-appearance:client:openCreator',
    ['fivem-appearance']   = 'fivem-appearance:customizePed',
    -- ['custom'] = function(src)
    --     TriggerEvent('my-creator:open')
    -- end,
}


-- ════════════════════════════════════════════════════════════════
--  IDLE ANIMATIONS
--  Played in a loop on the preview ped so the character isn't a
--  static mannequin. A random entry from the gendered pool is
--  picked each time the preview switches.
-- ════════════════════════════════════════════════════════════════
Config.IdleAnimations = {
    enabled = true,

    -- ── CYCLING ─────────────────────────────────────────────────
    -- The idle animation rotates on a TIMER, not on character
    -- switch. Switching characters preserves the current idle
    -- so transitions stay smooth — every interval the ped blends
    -- into a different random pose with a slow crossfade.
    cycleInterval = 15000,   -- ms between animation switches
    cycleJitter   = 0.25,    -- 0..1 randomness on top of interval (0.25 = ±25%)
    blendIn       = 2.5,     -- TaskPlayAnim blendIn — LOWER = smoother (default 8.0)
    blendOut      = -2.5,    -- TaskPlayAnim blendOut — keep symmetric to blendIn
    avoidRepeats  = true,    -- never roll the same anim twice in a row

    male = {
        { dict = 'anim@heists@heist_corona@team_idles@male_a',           anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@male_b',           anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@male_c',           anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@male_d',           anim = 'idle' },
        { dict = 'amb@world_human_hang_out_street@male_b@idle_a',        anim = 'idle_a' },
        { dict = 'amb@world_human_drug_dealer_hard@male@idle_a',         anim = 'idle_a' },
    },
    female = {
        { dict = 'anim@heists@heist_corona@team_idles@female_a',         anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@female_b',         anim = 'idle' },
        { dict = 'amb@world_human_hang_out_street@female_arms_crossed@idle_a', anim = 'idle_a' },
        { dict = 'amb@world_human_drug_dealer_hard@female@idle_a',       anim = 'idle_a' },
    },
}


-- ════════════════════════════════════════════════════════════════
--  STARTER LOADOUT
-- ════════════════════════════════════════════════════════════════
-- Items granted when a brand new character is created.
Config.StarterItems = {
    { item = 'id_card',        amount = 1 },
    { item = 'driver_license', amount = 1 },
    -- { item = 'phone',          amount = 1 },
    -- { item = 'water',          amount = 2 },
    -- { item = 'sandwich',       amount = 2 },
}

-- ════════════════════════════════════════════════════════════════
--  VALIDATION RULES
-- ════════════════════════════════════════════════════════════════
Config.Validation = {
    ForenameMinLen = 2,
    ForenameMaxLen = 16,
    SurnameMinLen  = 2,
    SurnameMaxLen  = 16,
    YearMin        = 1900,
    YearMax        = 2010,
    HeightMin      = 140,
    HeightMax      = 220,
    BannedNames    = { 'admin', 'staff', 'mod', 'owner', 'gm' },
}


-- ════════════════════════════════════════════════════════════════
--  UI OPTIONS — populated into the creation form
-- ════════════════════════════════════════════════════════════════
-- ISO 3166-1 alpha-2 codes, alphabetical by label. Trim or
-- reorder as you like — most servers keep the full list because
-- the UI's nationality select is searchable. Add custom labels
-- (e.g. roleplay-only countries) by appending entries here.
Config.Nationalities = {
    { label = 'AFGHANISTAN',           code = 'AF' },
    { label = 'ALBANIA',               code = 'AL' },
    { label = 'ALGERIA',               code = 'DZ' },
    { label = 'ANDORRA',               code = 'AD' },
    { label = 'ANGOLA',                code = 'AO' },
    { label = 'ARGENTINA',             code = 'AR' },
    { label = 'ARMENIA',               code = 'AM' },
    { label = 'AUSTRALIA',             code = 'AU' },
    { label = 'AUSTRIA',               code = 'AT' },
    { label = 'AZERBAIJAN',            code = 'AZ' },
    { label = 'BAHAMAS',               code = 'BS' },
    { label = 'BAHRAIN',               code = 'BH' },
    { label = 'BANGLADESH',            code = 'BD' },
    { label = 'BARBADOS',              code = 'BB' },
    { label = 'BELARUS',               code = 'BY' },
    { label = 'BELGIUM',               code = 'BE' },
    { label = 'BELIZE',                code = 'BZ' },
    { label = 'BENIN',                 code = 'BJ' },
    { label = 'BHUTAN',                code = 'BT' },
    { label = 'BOLIVIA',               code = 'BO' },
    { label = 'BOSNIA & HERZEGOVINA',  code = 'BA' },
    { label = 'BOTSWANA',              code = 'BW' },
    { label = 'BRAZIL',                code = 'BR' },
    { label = 'BRUNEI',                code = 'BN' },
    { label = 'BULGARIA',              code = 'BG' },
    { label = 'BURKINA FASO',          code = 'BF' },
    { label = 'BURUNDI',               code = 'BI' },
    { label = 'CAMBODIA',              code = 'KH' },
    { label = 'CAMEROON',              code = 'CM' },
    { label = 'CANADA',                code = 'CA' },
    { label = 'CAPE VERDE',            code = 'CV' },
    { label = 'CENTRAL AFRICAN REP.',  code = 'CF' },
    { label = 'CHAD',                  code = 'TD' },
    { label = 'CHILE',                 code = 'CL' },
    { label = 'CHINA',                 code = 'CN' },
    { label = 'COLOMBIA',              code = 'CO' },
    { label = 'COMOROS',               code = 'KM' },
    { label = 'CONGO',                 code = 'CG' },
    { label = 'COSTA RICA',            code = 'CR' },
    { label = 'CROATIA',               code = 'HR' },
    { label = 'CUBA',                  code = 'CU' },
    { label = 'CYPRUS',                code = 'CY' },
    { label = 'CZECH REPUBLIC',        code = 'CZ' },
    { label = 'DENMARK',               code = 'DK' },
    { label = 'DJIBOUTI',              code = 'DJ' },
    { label = 'DOMINICA',              code = 'DM' },
    { label = 'DOMINICAN REPUBLIC',    code = 'DO' },
    { label = 'ECUADOR',               code = 'EC' },
    { label = 'EGYPT',                 code = 'EG' },
    { label = 'EL SALVADOR',           code = 'SV' },
    { label = 'EQUATORIAL GUINEA',     code = 'GQ' },
    { label = 'ERITREA',               code = 'ER' },
    { label = 'ESTONIA',               code = 'EE' },
    { label = 'ESWATINI',              code = 'SZ' },
    { label = 'ETHIOPIA',              code = 'ET' },
    { label = 'FIJI',                  code = 'FJ' },
    { label = 'FINLAND',               code = 'FI' },
    { label = 'FRANCE',                code = 'FR' },
    { label = 'GABON',                 code = 'GA' },
    { label = 'GAMBIA',                code = 'GM' },
    { label = 'GEORGIA',               code = 'GE' },
    { label = 'GERMANY',               code = 'DE' },
    { label = 'GHANA',                 code = 'GH' },
    { label = 'GREECE',                code = 'GR' },
    { label = 'GRENADA',               code = 'GD' },
    { label = 'GUATEMALA',             code = 'GT' },
    { label = 'GUINEA',                code = 'GN' },
    { label = 'GUINEA-BISSAU',         code = 'GW' },
    { label = 'GUYANA',                code = 'GY' },
    { label = 'HAITI',                 code = 'HT' },
    { label = 'HONDURAS',              code = 'HN' },
    { label = 'HUNGARY',               code = 'HU' },
    { label = 'ICELAND',               code = 'IS' },
    { label = 'INDIA',                 code = 'IN' },
    { label = 'INDONESIA',             code = 'ID' },
    { label = 'IRAN',                  code = 'IR' },
    { label = 'IRAQ',                  code = 'IQ' },
    { label = 'IRELAND',               code = 'IE' },
    { label = 'ISRAEL',                code = 'IL' },
    { label = 'ITALY',                 code = 'IT' },
    { label = 'IVORY COAST',           code = 'CI' },
    { label = 'JAMAICA',               code = 'JM' },
    { label = 'JAPAN',                 code = 'JP' },
    { label = 'JORDAN',                code = 'JO' },
    { label = 'KAZAKHSTAN',            code = 'KZ' },
    { label = 'KENYA',                 code = 'KE' },
    { label = 'KIRIBATI',              code = 'KI' },
    { label = 'KOSOVO',                code = 'XK' },
    { label = 'KUWAIT',                code = 'KW' },
    { label = 'KYRGYZSTAN',            code = 'KG' },
    { label = 'LAOS',                  code = 'LA' },
    { label = 'LATVIA',                code = 'LV' },
    { label = 'LEBANON',               code = 'LB' },
    { label = 'LESOTHO',               code = 'LS' },
    { label = 'LIBERIA',               code = 'LR' },
    { label = 'LIBYA',                 code = 'LY' },
    { label = 'LIECHTENSTEIN',         code = 'LI' },
    { label = 'LITHUANIA',             code = 'LT' },
    { label = 'LUXEMBOURG',            code = 'LU' },
    { label = 'MADAGASCAR',            code = 'MG' },
    { label = 'MALAWI',                code = 'MW' },
    { label = 'MALAYSIA',              code = 'MY' },
    { label = 'MALDIVES',              code = 'MV' },
    { label = 'MALI',                  code = 'ML' },
    { label = 'MALTA',                 code = 'MT' },
    { label = 'MARSHALL ISLANDS',      code = 'MH' },
    { label = 'MAURITANIA',            code = 'MR' },
    { label = 'MAURITIUS',             code = 'MU' },
    { label = 'MEXICO',                code = 'MX' },
    { label = 'MICRONESIA',            code = 'FM' },
    { label = 'MOLDOVA',               code = 'MD' },
    { label = 'MONACO',                code = 'MC' },
    { label = 'MONGOLIA',              code = 'MN' },
    { label = 'MONTENEGRO',            code = 'ME' },
    { label = 'MOROCCO',               code = 'MA' },
    { label = 'MOZAMBIQUE',            code = 'MZ' },
    { label = 'MYANMAR',               code = 'MM' },
    { label = 'NAMIBIA',               code = 'NA' },
    { label = 'NAURU',                 code = 'NR' },
    { label = 'NEPAL',                 code = 'NP' },
    { label = 'NETHERLANDS',           code = 'NL' },
    { label = 'NEW ZEALAND',           code = 'NZ' },
    { label = 'NICARAGUA',             code = 'NI' },
    { label = 'NIGER',                 code = 'NE' },
    { label = 'NIGERIA',               code = 'NG' },
    { label = 'NORTH KOREA',           code = 'KP' },
    { label = 'NORTH MACEDONIA',       code = 'MK' },
    { label = 'NORWAY',                code = 'NO' },
    { label = 'OMAN',                  code = 'OM' },
    { label = 'PAKISTAN',              code = 'PK' },
    { label = 'PALAU',                 code = 'PW' },
    { label = 'PALESTINE',             code = 'PS' },
    { label = 'PANAMA',                code = 'PA' },
    { label = 'PAPUA NEW GUINEA',      code = 'PG' },
    { label = 'PARAGUAY',              code = 'PY' },
    { label = 'PERU',                  code = 'PE' },
    { label = 'PHILIPPINES',           code = 'PH' },
    { label = 'POLAND',                code = 'PL' },
    { label = 'PORTUGAL',              code = 'PT' },
    { label = 'QATAR',                 code = 'QA' },
    { label = 'ROMANIA',               code = 'RO' },
    { label = 'RUSSIA',                code = 'RU' },
    { label = 'RWANDA',                code = 'RW' },
    { label = 'SAINT KITTS & NEVIS',   code = 'KN' },
    { label = 'SAINT LUCIA',           code = 'LC' },
    { label = 'SAMOA',                 code = 'WS' },
    { label = 'SAN MARINO',            code = 'SM' },
    { label = 'SAO TOME & PRINCIPE',   code = 'ST' },
    { label = 'SAUDI ARABIA',          code = 'SA' },
    { label = 'SENEGAL',               code = 'SN' },
    { label = 'SERBIA',                code = 'RS' },
    { label = 'SEYCHELLES',            code = 'SC' },
    { label = 'SIERRA LEONE',          code = 'SL' },
    { label = 'SINGAPORE',             code = 'SG' },
    { label = 'SLOVAKIA',              code = 'SK' },
    { label = 'SLOVENIA',              code = 'SI' },
    { label = 'SOLOMON ISLANDS',       code = 'SB' },
    { label = 'SOMALIA',               code = 'SO' },
    { label = 'SOUTH AFRICA',          code = 'ZA' },
    { label = 'SOUTH KOREA',           code = 'KR' },
    { label = 'SOUTH SUDAN',           code = 'SS' },
    { label = 'SPAIN',                 code = 'ES' },
    { label = 'SRI LANKA',             code = 'LK' },
    { label = 'SUDAN',                 code = 'SD' },
    { label = 'SURINAME',              code = 'SR' },
    { label = 'SWEDEN',                code = 'SE' },
    { label = 'SWITZERLAND',           code = 'CH' },
    { label = 'SYRIA',                 code = 'SY' },
    { label = 'TAIWAN',                code = 'TW' },
    { label = 'TAJIKISTAN',            code = 'TJ' },
    { label = 'TANZANIA',              code = 'TZ' },
    { label = 'THAILAND',              code = 'TH' },
    { label = 'TIMOR-LESTE',           code = 'TL' },
    { label = 'TOGO',                  code = 'TG' },
    { label = 'TONGA',                 code = 'TO' },
    { label = 'TRINIDAD & TOBAGO',     code = 'TT' },
    { label = 'TUNISIA',               code = 'TN' },
    { label = 'TURKEY',                code = 'TR' },
    { label = 'TURKMENISTAN',          code = 'TM' },
    { label = 'TUVALU',                code = 'TV' },
    { label = 'UGANDA',                code = 'UG' },
    { label = 'UKRAINE',               code = 'UA' },
    { label = 'UNITED ARAB EMIRATES',  code = 'AE' },
    { label = 'UNITED KINGDOM',        code = 'GB' },
    { label = 'UNITED STATES',         code = 'US' },
    { label = 'URUGUAY',               code = 'UY' },
    { label = 'UZBEKISTAN',            code = 'UZ' },
    { label = 'VANUATU',               code = 'VU' },
    { label = 'VATICAN CITY',          code = 'VA' },
    { label = 'VENEZUELA',             code = 'VE' },
    { label = 'VIETNAM',               code = 'VN' },
    { label = 'YEMEN',                 code = 'YE' },
    { label = 'ZAMBIA',                code = 'ZM' },
    { label = 'ZIMBABWE',              code = 'ZW' },
}

Config.Builds     = { 'SLIM', 'ATHLETIC', 'HEAVY' }
Config.BloodTypes = { 'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-' }


-- ════════════════════════════════════════════════════════════════
--  LOCALE
-- ════════════════════════════════════════════════════════════════
-- Loads locales/<Locale>.lua at runtime. Add new files for more languages.
Config.Locale = 'en'   -- 'en' | 'tr'


-- ════════════════════════════════════════════════════════════════
--  LIFECYCLE HOOKS
--  Optional callbacks. Leave as empty functions if unused.
--
--  Note: webhook URLs and other secrets live in server_config.lua
--  (loaded after this file). The hooks here are non-sensitive.
-- ════════════════════════════════════════════════════════════════
---@diagnostic disable: unused-local
Config.OnPlayerLoaded = function(source, citizenid, isNew)
    -- Called after a character is selected and spawned.
    -- isNew = true means the character was just created.
end

Config.OnCharacterCreated = function(source, citizenid, charinfo)
    -- Called right after DB insert. Useful for bonuses, faction registration.
end

Config.OnCharacterDeleted = function(source, citizenid)
    -- Called right after DB delete + cascade cleanup.
    -- Useful for clearing custom tables (gangs, factions, properties).
end

Config.OnPremiumSlotUnlocked = function(source, identifier, key)
    -- Called when a Tebex key is successfully redeemed.
end
---@diagnostic enable: unused-local


-- ════════════════════════════════════════════════════════════════
--  ADVANCED — DB CLEANUP CASCADE
--  Tables to wipe rows from when a character is deleted.
--  Each entry: { table = 'tbl_name', column = 'citizenid_column' }
-- ════════════════════════════════════════════════════════════════
Config.DeleteCascade = {
    -- QBCore defaults
    { table = 'player_vehicles',  column = 'citizenid' },
    { table = 'player_houses',    column = 'citizenid' },
    { table = 'bank_accounts',    column = 'citizenid' },
    { table = 'phone_phones',     column = 'citizenid' },
    { table = 'phone_messages',   column = 'citizenid' },
    -- Add custom tables here
    -- { table = 'crypto',           column = 'citizenid' },
}
```

{% endtab %}

{% tab title="ESX" %}

```lua

-- ── APPEARANCE ──────────────────────────────────────────────
-- How the saved character look is loaded onto the preview ped.
-- Supports the major ESX appearance scripts. To add a new one,
-- drop an entry into Config.AppearanceLoaders below (preview)
-- and Config.AppearanceEditors (first-character flow).
Config.AppearanceScript = 'skinchanger'   -- 'skinchanger' | 'illenium-appearance' | 'fivem-appearance'

-- DB table + columns to read saved skins from. ESX Legacy stores
-- the skin JSON in `users.skin`, keyed by the character identifier.
-- Override if your fork moved the skin to a separate table.
Config.AppearanceTable       = 'users'
Config.AppearanceColumn      = 'skin'
Config.AppearanceModelColumn = 'skin'     -- ESX skin JSON contains model info; we re-select it as a no-op
Config.AppearanceKeyColumn   = 'identifier'   -- ESX rows are keyed by identifier, not citizenid

-- Some clothing systems (illenium with outfit variants, custom
-- multi-row schemas) store MULTIPLE rows per identifier — one per
-- outfit — and mark the currently worn one with `active = 1`.
-- Set to true ONLY if your skins table has an `active` column.
Config.AppearanceActiveFilter = false

-- Full override for the skin fetch. Use this when your schema is
-- too different for the Config.Appearance{Table,Column,...} options
-- to handle — e.g. JOINs across multiple tables, columns split
-- across rows, or a separate skins table keyed differently.
--
-- Receives (src, charKey) where charKey is the character's identifier
-- like 'char1:license:abc...'. Must return a table shaped:
--     { skin = <skin string|table>, model = <model string|number> }
-- or nil to fall back to the built-in query.
--
-- Define it in server_config.lua if you'd rather keep custom queries
-- out of the cosmetic config.
Config.OnGetAppearance = nil
-- Example (illenium-appearance schema):
-- Config.OnGetAppearance = function(src, charKey)
--     return exports.oxmysql:singleSync([[
--         SELECT skin, model FROM player_skins WHERE identifier = ? LIMIT 1
--     ]], { charKey })
-- end

-- ── AppearanceLoaders ───────────────────────────────────────
-- Registry: how to APPLY a saved skin to the PREVIEW ped on the
-- character roster (not the player ped). Function receives
-- (ped, skinTable). pcall-wrapped at the call site so a missing
-- dependency just no-ops.
Config.AppearanceLoaders = {
    ['skinchanger']        = function(ped, skin)
        -- `skinchanger:loadSkin` only ever targets the player ped —
        -- it can't push a skin onto our custom preview ped. So we
        -- apply the skin table directly via natives. EsxSkin.Apply
        -- is declared in client/esx_skin.lua.
        if ped == PlayerPedId() then
            -- Player ped: let skinchanger handle it (it also writes
            -- the skinchanger-internal state so esx_skin can re-read).
            TriggerEvent('skinchanger:loadSkin', skin)
        else
            -- Preview / custom ped: raw natives.
            EsxSkin.Apply(ped, skin)
        end
    end,
    ['illenium-appearance'] = function(ped, skin)
        exports['illenium-appearance']:setPedAppearance(ped, skin)
    end,
    ['fivem-appearance']    = function(ped, skin)
        exports['fivem-appearance']:setPedAppearance(ped, skin)
    end,
    ['rcore_clothing']      = function(ped, skin)
        -- rcore_clothing applies to the player ped. Preview is best-effort.
        pcall(function() exports.rcore_clothing:setPedAppearance(ped, skin) end)
    end,
    ['crm-appearance']      = function(ped, skin)
        exports['crm-appearance']:setPedAppearance(ped, skin)
    end,
    ['tgiann-clothing']     = function(ped, skin)
        pcall(function() exports['tgiann-clothing']:LoadSkin(ped, skin) end)
    end,
}

-- ── AppearanceEditors ───────────────────────────────────────
-- Registry: how to OPEN the first-character editor (face / body
-- / clothing customization) right after a brand-new character is
-- created. Each entry MUST accept (sex, onDone) and call onDone()
-- when the player saves the appearance — that's how feron_multichar
-- chains the spawnselector after customization.
--
--   sex    'm' | 'f'  — passed through from server (users.sex column)
--   onDone function   — invoke when the user saves / cancels.
--                       Triggers the spawnselector next.
--
-- Some clothing scripts don't expose a save callback. For those,
-- listen for their save event inside the function and call onDone
-- there, OR call onDone after a heuristic delay. As a last resort
-- a manual trigger (`/finishappearance` command, etc.) can fire
-- the spawnselector.
Config.AppearanceEditors = {
    ['skinchanger']        = function(_sex, onDone)
        -- esx_skin's saveable menu takes (saveCb, cancelCb) — both
        -- close the menu, so we wire onDone to either path.
        TriggerEvent('esx_skin:openSaveableMenu', onDone, onDone)
    end,

    ['illenium-appearance'] = function(_sex, onDone)
        exports['illenium-appearance']:startPlayerCustomization(function(appearance)
            if appearance then
                exports['illenium-appearance']:setPlayerAppearance(appearance)
            end
            if onDone then onDone() end
        end)
    end,

    ['fivem-appearance']   = function(_sex, onDone)
        exports['fivem-appearance']:startPlayerCustomization(function(appearance)
            if appearance then
                exports['fivem-appearance']:setPlayerAppearance(appearance)
            end
            if onDone then onDone() end
        end)
    end,

    ['rcore_clothing']     = function(_sex, onDone)
        -- rcore_clothing's first-character flow is typically opened
        -- via the StartingClothing export. API varies by version —
        -- verify on your rcore_clothing install.
        local ok = pcall(function()
            exports.rcore_clothing:OpenStartingClothing(function()
                if onDone then onDone() end
            end)
        end)
        if not ok and onDone then onDone() end
    end,

    ['crm-appearance']     = function(_sex, onDone)
        exports['crm-appearance']:startPlayerCustomization(function(appearance)
            if appearance then
                exports['crm-appearance']:setPlayerAppearance(appearance)
            end
            if onDone then onDone() end
        end)
    end,

    ['tgiann-clothing']    = function(_sex, onDone)
        -- tgiann-clothing exports OpenFirstSpawnMenu in v2; older
        -- versions used an event. Try export first, fall back to
        -- event with a heuristic onDone.
        local ok = pcall(function()
            exports['tgiann-clothing']:OpenFirstSpawnMenu(function()
                if onDone then onDone() end
            end)
        end)
        if not ok then
            TriggerEvent('tgiann-clothing:client:openFirstSpawn')
            if onDone then
                CreateThread(function() Wait(60000); onDone() end)   -- fallback timeout
            end
        end
    end,

    -- ['custom'] = function(sex, onDone)
    --     TriggerEvent('my-creator:open', function() onDone() end)
    -- end,
}


-- ════════════════════════════════════════════════════════════════
--  IDLE ANIMATIONS
--  Played in a loop on the preview ped so the character isn't a
--  static mannequin. A random entry from the gendered pool is
--  picked each time the preview switches.
-- ════════════════════════════════════════════════════════════════
Config.IdleAnimations = {
    enabled = true,

    -- ── CYCLING ─────────────────────────────────────────────────
    -- The idle animation rotates on a TIMER, not on character
    -- switch. Switching characters preserves the current idle
    -- so transitions stay smooth — every interval the ped blends
    -- into a different random pose with a slow crossfade.
    cycleInterval = 15000,   -- ms between animation switches
    cycleJitter   = 0.25,    -- 0..1 randomness on top of interval (0.25 = ±25%)
    blendIn       = 2.5,     -- TaskPlayAnim blendIn — LOWER = smoother (default 8.0)
    blendOut      = -2.5,    -- TaskPlayAnim blendOut — keep symmetric to blendIn
    avoidRepeats  = true,    -- never roll the same anim twice in a row

    male = {
        { dict = 'anim@heists@heist_corona@team_idles@male_a',           anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@male_b',           anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@male_c',           anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@male_d',           anim = 'idle' },
        { dict = 'amb@world_human_hang_out_street@male_b@idle_a',        anim = 'idle_a' },
        { dict = 'amb@world_human_drug_dealer_hard@male@idle_a',         anim = 'idle_a' },
    },
    female = {
        { dict = 'anim@heists@heist_corona@team_idles@female_a',         anim = 'idle' },
        { dict = 'anim@heists@heist_corona@team_idles@female_b',         anim = 'idle' },
        { dict = 'amb@world_human_hang_out_street@female_arms_crossed@idle_a', anim = 'idle_a' },
        { dict = 'amb@world_human_drug_dealer_hard@female@idle_a',       anim = 'idle_a' },
    },
}


-- ════════════════════════════════════════════════════════════════
--  STARTER LOADOUT
-- ════════════════════════════════════════════════════════════════
-- Items granted when a brand new character is created.
Config.StarterItems = {
    { item = 'id_card',        amount = 1 },
    { item = 'driver_license', amount = 1 },
    -- { item = 'phone',          amount = 1 },
    -- { item = 'water',          amount = 2 },
    -- { item = 'sandwich',       amount = 2 },
}

-- ════════════════════════════════════════════════════════════════
--  VALIDATION RULES
-- ════════════════════════════════════════════════════════════════
Config.Validation = {
    ForenameMinLen = 2,
    ForenameMaxLen = 16,
    SurnameMinLen  = 2,
    SurnameMaxLen  = 16,
    YearMin        = 1900,
    YearMax        = 2010,
    HeightMin      = 140,
    HeightMax      = 220,
    BannedNames    = { 'admin', 'staff', 'mod', 'owner', 'gm' },
}


-- ════════════════════════════════════════════════════════════════
--  UI OPTIONS — populated into the creation form
-- ════════════════════════════════════════════════════════════════
-- ISO 3166-1 alpha-2 codes, alphabetical by label. Trim or
-- reorder as you like — most servers keep the full list because
-- the UI's nationality select is searchable. Add custom labels
-- (e.g. roleplay-only countries) by appending entries here.
Config.Nationalities = {
    { label = 'AFGHANISTAN',           code = 'AF' },
    { label = 'ALBANIA',               code = 'AL' },
    { label = 'ALGERIA',               code = 'DZ' },
    { label = 'ANDORRA',               code = 'AD' },
    { label = 'ANGOLA',                code = 'AO' },
    { label = 'ARGENTINA',             code = 'AR' },
    { label = 'ARMENIA',               code = 'AM' },
    { label = 'AUSTRALIA',             code = 'AU' },
    { label = 'AUSTRIA',               code = 'AT' },
    { label = 'AZERBAIJAN',            code = 'AZ' },
    { label = 'BAHAMAS',               code = 'BS' },
    { label = 'BAHRAIN',               code = 'BH' },
    { label = 'BANGLADESH',            code = 'BD' },
    { label = 'BARBADOS',              code = 'BB' },
    { label = 'BELARUS',               code = 'BY' },
    { label = 'BELGIUM',               code = 'BE' },
    { label = 'BELIZE',                code = 'BZ' },
    { label = 'BENIN',                 code = 'BJ' },
    { label = 'BHUTAN',                code = 'BT' },
    { label = 'BOLIVIA',               code = 'BO' },
    { label = 'BOSNIA & HERZEGOVINA',  code = 'BA' },
    { label = 'BOTSWANA',              code = 'BW' },
    { label = 'BRAZIL',                code = 'BR' },
    { label = 'BRUNEI',                code = 'BN' },
    { label = 'BULGARIA',              code = 'BG' },
    { label = 'BURKINA FASO',          code = 'BF' },
    { label = 'BURUNDI',               code = 'BI' },
    { label = 'CAMBODIA',              code = 'KH' },
    { label = 'CAMEROON',              code = 'CM' },
    { label = 'CANADA',                code = 'CA' },
    { label = 'CAPE VERDE',            code = 'CV' },
    { label = 'CENTRAL AFRICAN REP.',  code = 'CF' },
    { label = 'CHAD',                  code = 'TD' },
    { label = 'CHILE',                 code = 'CL' },
    { label = 'CHINA',                 code = 'CN' },
    { label = 'COLOMBIA',              code = 'CO' },
    { label = 'COMOROS',               code = 'KM' },
    { label = 'CONGO',                 code = 'CG' },
    { label = 'COSTA RICA',            code = 'CR' },
    { label = 'CROATIA',               code = 'HR' },
    { label = 'CUBA',                  code = 'CU' },
    { label = 'CYPRUS',                code = 'CY' },
    { label = 'CZECH REPUBLIC',        code = 'CZ' },
    { label = 'DENMARK',               code = 'DK' },
    { label = 'DJIBOUTI',              code = 'DJ' },
    { label = 'DOMINICA',              code = 'DM' },
    { label = 'DOMINICAN REPUBLIC',    code = 'DO' },
    { label = 'ECUADOR',               code = 'EC' },
    { label = 'EGYPT',                 code = 'EG' },
    { label = 'EL SALVADOR',           code = 'SV' },
    { label = 'EQUATORIAL GUINEA',     code = 'GQ' },
    { label = 'ERITREA',               code = 'ER' },
    { label = 'ESTONIA',               code = 'EE' },
    { label = 'ESWATINI',              code = 'SZ' },
    { label = 'ETHIOPIA',              code = 'ET' },
    { label = 'FIJI',                  code = 'FJ' },
    { label = 'FINLAND',               code = 'FI' },
    { label = 'FRANCE',                code = 'FR' },
    { label = 'GABON',                 code = 'GA' },
    { label = 'GAMBIA',                code = 'GM' },
    { label = 'GEORGIA',               code = 'GE' },
    { label = 'GERMANY',               code = 'DE' },
    { label = 'GHANA',                 code = 'GH' },
    { label = 'GREECE',                code = 'GR' },
    { label = 'GRENADA',               code = 'GD' },
    { label = 'GUATEMALA',             code = 'GT' },
    { label = 'GUINEA',                code = 'GN' },
    { label = 'GUINEA-BISSAU',         code = 'GW' },
    { label = 'GUYANA',                code = 'GY' },
    { label = 'HAITI',                 code = 'HT' },
    { label = 'HONDURAS',              code = 'HN' },
    { label = 'HUNGARY',               code = 'HU' },
    { label = 'ICELAND',               code = 'IS' },
    { label = 'INDIA',                 code = 'IN' },
    { label = 'INDONESIA',             code = 'ID' },
    { label = 'IRAN',                  code = 'IR' },
    { label = 'IRAQ',                  code = 'IQ' },
    { label = 'IRELAND',               code = 'IE' },
    { label = 'ISRAEL',                code = 'IL' },
    { label = 'ITALY',                 code = 'IT' },
    { label = 'IVORY COAST',           code = 'CI' },
    { label = 'JAMAICA',               code = 'JM' },
    { label = 'JAPAN',                 code = 'JP' },
    { label = 'JORDAN',                code = 'JO' },
    { label = 'KAZAKHSTAN',            code = 'KZ' },
    { label = 'KENYA',                 code = 'KE' },
    { label = 'KIRIBATI',              code = 'KI' },
    { label = 'KOSOVO',                code = 'XK' },
    { label = 'KUWAIT',                code = 'KW' },
    { label = 'KYRGYZSTAN',            code = 'KG' },
    { label = 'LAOS',                  code = 'LA' },
    { label = 'LATVIA',                code = 'LV' },
    { label = 'LEBANON',               code = 'LB' },
    { label = 'LESOTHO',               code = 'LS' },
    { label = 'LIBERIA',               code = 'LR' },
    { label = 'LIBYA',                 code = 'LY' },
    { label = 'LIECHTENSTEIN',         code = 'LI' },
    { label = 'LITHUANIA',             code = 'LT' },
    { label = 'LUXEMBOURG',            code = 'LU' },
    { label = 'MADAGASCAR',            code = 'MG' },
    { label = 'MALAWI',                code = 'MW' },
    { label = 'MALAYSIA',              code = 'MY' },
    { label = 'MALDIVES',              code = 'MV' },
    { label = 'MALI',                  code = 'ML' },
    { label = 'MALTA',                 code = 'MT' },
    { label = 'MARSHALL ISLANDS',      code = 'MH' },
    { label = 'MAURITANIA',            code = 'MR' },
    { label = 'MAURITIUS',             code = 'MU' },
    { label = 'MEXICO',                code = 'MX' },
    { label = 'MICRONESIA',            code = 'FM' },
    { label = 'MOLDOVA',               code = 'MD' },
    { label = 'MONACO',                code = 'MC' },
    { label = 'MONGOLIA',              code = 'MN' },
    { label = 'MONTENEGRO',            code = 'ME' },
    { label = 'MOROCCO',               code = 'MA' },
    { label = 'MOZAMBIQUE',            code = 'MZ' },
    { label = 'MYANMAR',               code = 'MM' },
    { label = 'NAMIBIA',               code = 'NA' },
    { label = 'NAURU',                 code = 'NR' },
    { label = 'NEPAL',                 code = 'NP' },
    { label = 'NETHERLANDS',           code = 'NL' },
    { label = 'NEW ZEALAND',           code = 'NZ' },
    { label = 'NICARAGUA',             code = 'NI' },
    { label = 'NIGER',                 code = 'NE' },
    { label = 'NIGERIA',               code = 'NG' },
    { label = 'NORTH KOREA',           code = 'KP' },
    { label = 'NORTH MACEDONIA',       code = 'MK' },
    { label = 'NORWAY',                code = 'NO' },
    { label = 'OMAN',                  code = 'OM' },
    { label = 'PAKISTAN',              code = 'PK' },
    { label = 'PALAU',                 code = 'PW' },
    { label = 'PALESTINE',             code = 'PS' },
    { label = 'PANAMA',                code = 'PA' },
    { label = 'PAPUA NEW GUINEA',      code = 'PG' },
    { label = 'PARAGUAY',              code = 'PY' },
    { label = 'PERU',                  code = 'PE' },
    { label = 'PHILIPPINES',           code = 'PH' },
    { label = 'POLAND',                code = 'PL' },
    { label = 'PORTUGAL',              code = 'PT' },
    { label = 'QATAR',                 code = 'QA' },
    { label = 'ROMANIA',               code = 'RO' },
    { label = 'RUSSIA',                code = 'RU' },
    { label = 'RWANDA',                code = 'RW' },
    { label = 'SAINT KITTS & NEVIS',   code = 'KN' },
    { label = 'SAINT LUCIA',           code = 'LC' },
    { label = 'SAMOA',                 code = 'WS' },
    { label = 'SAN MARINO',            code = 'SM' },
    { label = 'SAO TOME & PRINCIPE',   code = 'ST' },
    { label = 'SAUDI ARABIA',          code = 'SA' },
    { label = 'SENEGAL',               code = 'SN' },
    { label = 'SERBIA',                code = 'RS' },
    { label = 'SEYCHELLES',            code = 'SC' },
    { label = 'SIERRA LEONE',          code = 'SL' },
    { label = 'SINGAPORE',             code = 'SG' },
    { label = 'SLOVAKIA',              code = 'SK' },
    { label = 'SLOVENIA',              code = 'SI' },
    { label = 'SOLOMON ISLANDS',       code = 'SB' },
    { label = 'SOMALIA',               code = 'SO' },
    { label = 'SOUTH AFRICA',          code = 'ZA' },
    { label = 'SOUTH KOREA',           code = 'KR' },
    { label = 'SOUTH SUDAN',           code = 'SS' },
    { label = 'SPAIN',                 code = 'ES' },
    { label = 'SRI LANKA',             code = 'LK' },
    { label = 'SUDAN',                 code = 'SD' },
    { label = 'SURINAME',              code = 'SR' },
    { label = 'SWEDEN',                code = 'SE' },
    { label = 'SWITZERLAND',           code = 'CH' },
    { label = 'SYRIA',                 code = 'SY' },
    { label = 'TAIWAN',                code = 'TW' },
    { label = 'TAJIKISTAN',            code = 'TJ' },
    { label = 'TANZANIA',              code = 'TZ' },
    { label = 'THAILAND',              code = 'TH' },
    { label = 'TIMOR-LESTE',           code = 'TL' },
    { label = 'TOGO',                  code = 'TG' },
    { label = 'TONGA',                 code = 'TO' },
    { label = 'TRINIDAD & TOBAGO',     code = 'TT' },
    { label = 'TUNISIA',               code = 'TN' },
    { label = 'TURKEY',                code = 'TR' },
    { label = 'TURKMENISTAN',          code = 'TM' },
    { label = 'TUVALU',                code = 'TV' },
    { label = 'UGANDA',                code = 'UG' },
    { label = 'UKRAINE',               code = 'UA' },
    { label = 'UNITED ARAB EMIRATES',  code = 'AE' },
    { label = 'UNITED KINGDOM',        code = 'GB' },
    { label = 'UNITED STATES',         code = 'US' },
    { label = 'URUGUAY',               code = 'UY' },
    { label = 'UZBEKISTAN',            code = 'UZ' },
    { label = 'VANUATU',               code = 'VU' },
    { label = 'VATICAN CITY',          code = 'VA' },
    { label = 'VENEZUELA',             code = 'VE' },
    { label = 'VIETNAM',               code = 'VN' },
    { label = 'YEMEN',                 code = 'YE' },
    { label = 'ZAMBIA',                code = 'ZM' },
    { label = 'ZIMBABWE',              code = 'ZW' },
}

Config.Builds     = { 'SLIM', 'ATHLETIC', 'HEAVY' }
Config.BloodTypes = { 'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-' }


-- ════════════════════════════════════════════════════════════════
--  LOCALE
-- ════════════════════════════════════════════════════════════════
-- Loads locales/<Locale>.lua at runtime. Add new files for more languages.
Config.Locale = 'en'   -- 'en' | 'tr'


-- ════════════════════════════════════════════════════════════════
--  DEFAULT APPEARANCE  —  Legacy skinchanger / esx_skin flow.
--
--  Applied to a freshly-created character right before the
--  first-spawn appearance editor opens (esx_skin:openSaveableMenu
--  by default). Gives the player a presentable starting look so
--  they're not staring at a default freemode peasant.
--
--  Modern setups (illenium-appearance, fivem-appearance) handle
--  defaults themselves and ignore this block.
--
--  Sex keys match what ESX writes to `users.sex`:
--    'm' → mp_m_freemode_01
--    'f' → mp_f_freemode_01
-- ════════════════════════════════════════════════════════════════
Config.DefaultAppearance = {
    ['m'] = {
        mom = 43, dad = 29,
        face_md_weight = 61, skin_md_weight = 27,
        nose_1 = -5, nose_2 = 6, nose_3 = 5, nose_4 = 8, nose_5 = 10, nose_6 = 0,
        cheeks_1 = 2, cheeks_2 = -10, cheeks_3 = 6,
        lip_thickness = -2,
        jaw_1 = 0, jaw_2 = 0,
        chin_1 = 0, chin_2 = 0, chin_13 = 0, chin_4 = 0,
        neck_thickness = 0,
        hair_1 = 76, hair_2 = 0,
        hair_color_1 = 61, hair_color_2 = 29,
        tshirt_1 = 4, tshirt_2 = 2,
        torso_1 = 23, torso_2 = 2,
        decals_1 = 0, decals_2 = 0,
        arms = 1, arms_2 = 0,
        pants_1 = 28, pants_2 = 3,
        shoes_1 = 70, shoes_2 = 2,
        mask_1 = 0, mask_2 = 0,
        bproof_1 = 0, bproof_2 = 0,
        chain_1 = 22, chain_2 = 2,
        helmet_1 = -1, helmet_2 = 0,
        glasses_1 = 0, glasses_2 = 0,
        watches_1 = -1, watches_2 = 0,
        bracelets_1 = -1, bracelets_2 = 0,
        bags_1 = 0, bags_2 = 0,
        eye_color = 0, eye_squint = 0,
        eyebrows_1 = 0, eyebrows_2 = 0, eyebrows_3 = 0,
        eyebrows_4 = 0, eyebrows_5 = 0, eyebrows_6 = 0,
        makeup_1 = 0, makeup_2 = 0, makeup_3 = 0, makeup_4 = 0,
        lipstick_1 = 0, lipstick_2 = 0, lipstick_3 = 0, lipstick_4 = 0,
        ears_1 = -1, ears_2 = 0,
        chest_1 = 0, chest_2 = 0, chest_3 = 0,
        bodyb_1 = -1, bodyb_2 = 0, bodyb_3 = -1, bodyb_4 = 0,
        age_1 = 0, age_2 = 0,
        blemishes_1 = 0, blemishes_2 = 0,
        blush_1 = 0, blush_2 = 0, blush_3 = 0,
        complexion_1 = 0, complexion_2 = 0,
        sun_1 = 0, sun_2 = 0,
        moles_1 = 0, moles_2 = 0,
        beard_1 = 11, beard_2 = 10, beard_3 = 0, beard_4 = 0,
    },
    ['f'] = {
        mom = 28, dad = 6,
        face_md_weight = 63, skin_md_weight = 60,
        nose_1 = -10, nose_2 = 4, nose_3 = 5, nose_4 = 0, nose_5 = 0, nose_6 = 0,
        cheeks_1 = 0, cheeks_2 = 0, cheeks_3 = 0,
        lip_thickness = 0,
        jaw_1 = 0, jaw_2 = 0,
        chin_1 = -10, chin_2 = 10, chin_13 = -10, chin_4 = 0,
        neck_thickness = -5,
        hair_1 = 43, hair_2 = 0,
        hair_color_1 = 29, hair_color_2 = 35,
        tshirt_1 = 111, tshirt_2 = 5,
        torso_1 = 25, torso_2 = 2,
        decals_1 = 0, decals_2 = 0,
        arms = 3, arms_2 = 0,
        pants_1 = 12, pants_2 = 2,
        shoes_1 = 20, shoes_2 = 10,
        mask_1 = 0, mask_2 = 0,
        bproof_1 = 0, bproof_2 = 0,
        chain_1 = 85, chain_2 = 0,
        helmet_1 = -1, helmet_2 = 0,
        glasses_1 = 33, glasses_2 = 12,
        watches_1 = -1, watches_2 = 0,
        bracelets_1 = -1, bracelets_2 = 0,
        bags_1 = 0, bags_2 = 0,
        eye_color = 8, eye_squint = -6,
        eyebrows_1 = 32, eyebrows_2 = 7, eyebrows_3 = 52,
        eyebrows_4 = 9, eyebrows_5 = -5, eyebrows_6 = -8,
        makeup_1 = 0, makeup_2 = 0, makeup_3 = 0, makeup_4 = 0,
        lipstick_1 = 0, lipstick_2 = 0, lipstick_3 = 0, lipstick_4 = 0,
        ears_1 = -1, ears_2 = 0,
        chest_1 = 0, chest_2 = 0, chest_3 = 0,
        bodyb_1 = -1, bodyb_2 = 0, bodyb_3 = -1, bodyb_4 = 0,
        age_1 = 0, age_2 = 0,
        blemishes_1 = 0, blemishes_2 = 0,
        blush_1 = 0, blush_2 = 0, blush_3 = 0,
        complexion_1 = 0, complexion_2 = 0,
        sun_1 = 0, sun_2 = 0,
        moles_1 = 12, moles_2 = 8,
        beard_1 = 0, beard_2 = 0, beard_3 = 0, beard_4 = 0,
    },
}


-- ════════════════════════════════════════════════════════════════
--  LIFECYCLE HOOKS
--  Optional callbacks. Leave as empty functions if unused.
--
--  Note: webhook URLs and other secrets live in server_config.lua
--  (loaded after this file). The hooks here are non-sensitive.
-- ════════════════════════════════════════════════════════════════
---@diagnostic disable: unused-local
Config.OnPlayerLoaded = function(source, citizenid, isNew)
    -- Called after a character is selected and spawned.
    -- isNew = true means the character was just created.
end

Config.OnCharacterCreated = function(source, citizenid, charinfo)
    -- Called right after DB insert. Useful for bonuses, faction registration.
end

Config.OnCharacterDeleted = function(source, citizenid)
    -- Called right after DB delete + cascade cleanup.
    -- Useful for clearing custom tables (gangs, factions, properties).
end

Config.OnPremiumSlotUnlocked = function(source, identifier, key)
    -- Called when a Tebex key is successfully redeemed.
end
---@diagnostic enable: unused-local


-- ════════════════════════════════════════════════════════════════
--  ADVANCED — DB CLEANUP CASCADE
--  Tables to wipe rows from when a character is deleted.
--  Each entry: { table = 'tbl_name', column = 'identifier_column' }
--  The column is matched against the FULL character identifier
--  (e.g. 'char1:license:abc...') — most ESX tables key off the
--  same column as `users.identifier`.
-- ════════════════════════════════════════════════════════════════
Config.DeleteCascade = {
    -- ESX defaults — uncomment / add as needed for your server
    -- { table = 'owned_vehicles',      column = 'owner' },
    -- { table = 'user_licenses',       column = 'owner' },
    -- { table = 'addon_inventory_items', column = 'owner' },
    -- { table = 'datastore_data',      column = 'owner' },
    -- { table = 'phone_phones',        column = 'identifier' },
    -- { table = 'phone_messages',      column = 'identifier' },
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://feron-scripts.gitbook.io/welcome/scripts/feron-multicharacter/config/appearance-misc.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
