> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/mad-racing-system-racing-tablet/configuration.md).

# Configuration

## General Configuration

```lua
lib.locale('en')

cfg = {}

-- ============================================================================
--  rm_racing — server configuration
--
--  Sections (in order):
--    1.  Framework & dependencies          — qbox/qb/esx/standalone, ox_*, etc.
--    2.  Tablet & UI                       — /racing command, item gating, map tile
--    3.  Economy                           — money type used for prizes / fees
--    4.  User tiers & permissions          — racer/creator/master/god + flag map
--    5.  Player profile defaults           — pictures, picker palettes, name change cost
--    6.  ELO rating                        — Arpad K-factor + display tiers
--    7.  Race engine                       — countdown, grid layout, DNF timeout
--    8.  Race types                        — sprint/circuit/drift/etc.
--    9.  Drift scoring                     — points-per-second, combo, penalties
--    10. Tracks                            — creator rules + tags + placeable objects
--    11. Competitions                      — auto schedule, prize pool, vehicle classes
--    12. Pink slips (1v1 vehicle wager)    — allowed types, redeem locations
--    13. Bounties (time-trial chase)       — interval, prize, target formula
--    14. Item payouts                      — item rolls per finishing position
--    15. 3D GPS route renderer             — line/arrow style, lookahead, sizing
--    16. Discord webhooks                  — notifications channel + colors
--    17. Limits                            — page sizes / row caps
-- ============================================================================


-- ============================================================================
--  1.  FRAMEWORK & DEPENDENCIES
-- ============================================================================

---@type 'auto' | 'qbox' | 'qb' | 'esx' | 'standalone'
cfg.framework = 'auto'

-- Standalone fallback (no qbox/qb/esx). license identifiers become the
-- citizenid and money uses a virtual wallet in `rm_racing_wallet` instead
-- of a framework account.
cfg.standalone = {
    initialBalance = 10000,     ---@type number  -- credits new players start with
    currencyLabel = 'credits',  ---@type string  -- displayed in modals / tooltips (informational only)

    -- DEV ONLY: when true, the standalone bridge suffixes the citizenid with
    -- the player's server id so two FiveM clients on the same license get
    -- distinct profiles. Each session is treated as a separate player — new
    -- profile row, new wallet with initialBalance. TURN OFF in production
    -- or every reconnect orphans the previous profile.
    perSessionId = false,       ---@type boolean
}

---@type 'auto' | 'ox_target' | 'qb-target' | false
cfg.target       = 'auto'

---@type 'rm_minigames' | 'ox_lib' | 'qb-minigames' | 'ps-ui' | 'bl_ui' | false
cfg.minigame     = 'ox_lib'

---@type 'ox_lib' | 'qb' | 'esx' | 'okokNotify' | 'ps-ui'
cfg.notification = 'ox_lib'

---@type 'rm_racing' | 'ox_lib' | 'esx' | 'qb' | 'okokTextUI' | 'jg-textui'
-- 'rm_racing' = built-in NUI hint (RaceHud-styled, auto-chips for [KEY]).
-- Switch to a third-party system if you want it to match the rest of your
-- server's UI instead.
cfg.textUI       = 'rm_racing'

---@type 'ox_lib' | 'qb' | 'esx' | false
cfg.progressbar  = 'ox_lib'

---@type 'auto' | false | 'qb-vehiclekeys' | 'wasabi_carlock' | 'qs-vehiclekeys' | 'cd_garage' | 'Renewed-Vehiclekeys' | 'okokGarage' | 't1ger_keys' | 'MrNewbVehicleKeys'
cfg.vehiclelock  = 'auto'

---@type false | 'default_dispatch' | 'cd_dispatch' | 'qs-dispatch' | 'ps-dispatch' | 'rcore_dispatch' | 'sonoran_cad' | 'origen_police' | 'redutzu-mdt' | 'lb-tablet' | 'core_dispatch' | 'tk_dispatch' | 'l2s-dispatch'
cfg.dispatch     = false

-- Visual style for the in-world `[E] interact` prompt (used by some bridges).
cfg.interaction = {
    controlId = 38,
    text = 'E',
    colors = {
        background = { r = 241, g = 93,  b = 56,  a = 255 },
        text       = { r = 226, g = 232, b = 240, a = 255 },
    },
}

-- Police count gating (kept for parity with other resources; unused here unless > 0).
cfg.requiredPoliceCount         = 0
cfg.enableOldMethodForPoliceCount = false


-- ============================================================================
--  2.  TABLET & UI
-- ============================================================================

-- Chat command that opens the racing tablet. `/racing` by default.
cfg.command = 'racing'  ---@type string

-- Inventory item that opens the racing tablet when used. Set to `false` to
-- disable item-gating (the /racing command always works).
cfg.tabletItem = 'rm_racing_tablet'  ---@type string|false

-- NUI-specific UI tweaks. The TrackInfoPage's Leaflet preview renders a
-- GTA V tile background. Default points at the Rockstar Social Club tile
-- server (stable, dark styled). Set `tileUrl = false` for a blank
-- background that only shows the track polyline.
cfg.ui = {
    tileUrl = 'https://s.rsg.sc/sc/images/games/GTAV/map/game/{z}/{x}/{y}.jpg',  ---@type string|false
}


-- ============================================================================
--  3.  ECONOMY
-- ============================================================================

-- Money type used for entry fees and prizes.
cfg.moneyType = 'bank'  ---@type 'cash' | 'bank'


-- ============================================================================
--  4.  USER TIERS & PERMISSIONS
--
--  Tier hierarchy is by INDEX, NOT by ELO threshold. Index 1 = highest;
--  the last entry is the default for everyone without an override. ELO-based
--  auto-assignment uses `min` (highest tier whose min the player meets). A
--  tier override on profile.tier (set via /createracinguser) wins over ELO.
-- ============================================================================

---@type { name: string, min: number }[]
cfg.tiers = {
    { name = 'God',     min = 99999 },  -- never auto-assigned, must be granted
    { name = 'Master',  min = 1850  },
    { name = 'Creator', min = 1700  },
    { name = 'Racer',   min = 0     },
}

-- For each capability, the MINIMUM tier required. Anything at or above the
-- listed tier in cfg.tiers (lower index = higher tier) passes.
cfg.permissions = {
    join             = 'Racer',     ---@type string  -- join races / lobbies
    create           = 'Racer',     ---@type string  -- start a race (lobby or solo)
    createTrack      = 'Creator',   ---@type string  -- save a new track via the editor
    createCrew       = 'Racer',     ---@type string  -- found a crew
    control          = 'Master',    ---@type string  -- moderate own creations (delete tracks, etc.)
    controlAll       = 'God',       ---@type string  -- moderate everyone's creations
    handleBounties   = 'Master',    ---@type string  -- re-roll / cancel active bounties
    runAdminCommands = 'God',       ---@type string  -- /createracinguser, /removeallracetracks etc.
}


-- ============================================================================
--  5.  PLAYER PROFILE DEFAULTS
-- ============================================================================

cfg.profile = {
    pictureCount     = 8,  ---@type number  -- number of presets in /web/public/profiles/1..N.png
    checkpointColors = { 'orange', 'red', 'blue', 'navy', 'green', 'yellow', 'purple', 'white' },
    gpsColors        = { 'orange', 'red', 'blue', 'navy', 'green', 'yellow', 'purple', 'white' },
    -- 3D checkpoint visual styles. 'classic' = the original tall translucent
    -- pillar + sphere top. 'highway' = highway-style billboard sign on a
    -- vertical pole, distance label updating live. 'nav' = GPS-style nav
    -- panel (distance + direction arrow + street name from
    -- GetStreetNameAtCoord). Persisted on profile.checkpoint_style
    -- (default 'classic').
    checkpointStyles = { 'classic', 'highway', 'nav' },
    -- Default for the "show my 3D PlayerCard above my head to other
    -- players" privacy preference. Each player can flip it individually
    -- in their profile; new players inherit this default. Saved on
    -- profile.card_visible (1 = visible, 0 = hidden).
    cardVisibleByDefault = true,
    nameChangeCost   = 1000,
}

-- Crew emblem allow-list. Must mirror `web/src/constants/crewEmblems.ts`
-- exactly — server validates the picked emblem against this list before
-- inserting the crew, so a new icon there needs a new entry here too.
cfg.crew = {
    emblems = {
        'flag', 'trophy', 'crown', 'bolt', 'flame',
        'skull', 'shield', 'swords', 'target', 'anchor',
        'compass', 'gem', 'ghost', 'rocket', 'star',
        'wrench', 'cog', 'eye', 'heart', 'crosshair',
    },
}


-- ============================================================================
--  PlayerCard — in-world 3D nameplates rendered above racers / nearby players
--
--  Powered by rm_stream (DUI projected onto a billboard marker). The same
--  React PlayerCard component used in the tablet renders standalone via
--  `build/index.html?card=1&...`; one stream per visible player, attached
--  to their vehicle (preferred) or ped fallback. Distance-faded.
-- ============================================================================
cfg.playerCard = {
    enabled         = true,   ---@type boolean
    duringRace      = true,   ---@type boolean  -- show above each racer's vehicle in-race
    outOfRace       = true,   ---@type boolean  -- show above nearby players' peds in free-roam

    -- Render & culling. renderDistance is the same value rm_stream uses to
    -- skip drawing — keep it tight to limit DUI overdraw on busy servers.
    renderDistance  = 50.0,   ---@type number   -- metres
    maxConcurrent   = 10,     ---@type integer  -- hard cap; closest-N kept, rest hidden
    scanIntervalMs  = 1000,   ---@type integer  -- free-roam scan cadence

    -- Server-side profile cache TTL (seconds). A nearby-scan returns up to
    -- maxConcurrent player summaries; cache means repeated scans don't slam
    -- the DB if the same players hang around.
    profileCacheSec = 30,     ---@type number

    -- Vertical offset above the attach entity. Vehicle uses model height +
    -- this; ped uses ~head height + this. Tune per server vehicle pool.
    vehicleOffsetZ  = 0.8,    ---@type number
    pedOffsetZ      = 0.6,    ---@type number   -- on top of head bone (already ~head height)

    -- Card billboard sizing (passed to rm_stream). Width/height drive the
    -- DUI texture resolution; scale is the world-space size of the marker
    -- (1.0 ≈ 1 metre tall before distance falloff).
    width           = 600,    ---@type integer  -- DUI texture px
    height          = 200,    ---@type integer
    scale           = 0.8,    ---@type number   -- world units
}


-- ============================================================================
--  6.  ELO RATING
-- ============================================================================

cfg.elo = {
    initial         = 1500,  ---@type number  -- starting rating for new players
    kFactor         = 32,    ---@type number  -- rating volatility (chess uses 10-40)
    minParticipants = 1,     ---@type number  -- minimum racers required for ELO to apply

    -- Display tiers shown on profiles / leaderboards (separate from cfg.tiers
    -- which gates permissions). Purely cosmetic ranks based on ELO bands.
    tiers = {                ---@type { name: string, min: number }[]
        { name = 'Racer', min = 0    },
        { name = 'Host',  min = 1600 },
        { name = 'Alien', min = 1850 },
    },
}


-- ============================================================================
--  7.  RACE ENGINE
-- ============================================================================

cfg.race = {
    countdownSec             = 5,     ---@type number  -- 3-2-1-go countdown length
    dnfTimeoutSec            = 100,   ---@type number  -- racer kicked if still running this long after first finish
    checkpointArriveDistance = 3.5,   ---@type number  -- on-foot trigger distance; vehicles use cp.radius

    -- Players must be within this distance of cp1 when /start fires, otherwise
    -- the race rejects start with a "too far from grid" modal.
    maxDistanceFromGrid      = 20.0,

    -- Starting-grid layout. Racers are placed in a `columns` × ceil(N/columns)
    -- grid behind cp1, all facing toward cp2.
    grid = {
        columns       = 2,     ---@type number  -- racers per row
        columnSpacing = 4.0,   ---@type number  -- m between side-by-side slots
        rowSpacing    = 5.0,   ---@type number  -- m between rows
        backOffset    = 4.0,   ---@type number  -- m added to cp1.radius for the front row
    },
}


-- ============================================================================
--  8.  RACE TYPES
--
--  circuit = true   →  loops, supports laps
--  drift   = true   →  drift scoring is the win condition
--  timeTrial         →  solo runs only, no lobby
--  elimination       →  last-place dropped each lap
-- ============================================================================

---@type { id: string, label: string, circuit: boolean, drift: boolean, timeTrial?: boolean, elimination?: boolean }[]
cfg.raceTypes = {
    { id = 'sprint',          label = 'Sprint',          circuit = false, drift = false },
    { id = 'circuit',         label = 'Circuit',         circuit = true,  drift = false },
    { id = 'drift',           label = 'Drift',           circuit = false, drift = true  },
    { id = 'drift_challenge', label = 'Drift challenge', circuit = true,  drift = true  },
    { id = 'time_trial',      label = 'Time trial',      circuit = false, drift = false, timeTrial   = true },
    { id = 'elimination',     label = 'Elimination',     circuit = true,  drift = false, elimination = true },
}


-- ============================================================================
--  9.  DRIFT SCORING
--  Used by drift & drift_challenge race types.
-- ============================================================================

cfg.drift = {
    pointsPerSecond     = 8,      -- baseline points for sustained drift
    angleThreshold      = 12.0,   -- degrees; below this, no drift
    minSpeedMps         = 10.0,   -- metres/sec; below this, no drift
    wallHitPenalty      = 500,    -- points lost when hitting a wall/entity mid-drift
    comboMultiplierStep = 0.25,   -- +25% points per sustained second up to max
    comboMultiplierMax  = 3.0,
    tickMs              = 100,    -- scoring tick interval
}


-- ============================================================================
--  10. TRACKS
-- ============================================================================

-- Creator rules — bounds enforced server-side at saveTrack.
cfg.track = {
    minCheckpoints          = 2,
    maxCheckpoints          = 300,
    maxObjects              = 600,
    checkpointDefaultRadius = 15.0,
    checkpointMinRadius     = 10.0,
    checkpointMaxRadius     = 50.0,
    creatorMoneyCost        = 5000,
    nameMaxLen              = 40,
    descriptionMaxLen       = 140,
    objectPlacementDistance = 3.5,    ---@type number  -- m in front of player for object preview spawn (fallback when raycast misses)
    objectYawStep           = 15.0,
}

-- Track tags shown on track cards.
cfg.trackTags = {
    newTrackDays            = 7,    ---@type number  -- track <= N days old → 'New'
    popularMinTimesLastWeek = 25,   ---@type number  -- ≥ N races last week → 'Popular'
    legendaryMinTimesDriven = 200,  ---@type number  -- ≥ N total races → 'Legendary'
}

-- "Author" of seed-tracks.sql tracks (creator_cid = ''). Substituted
-- server-side in decorateTrack so the listing UI doesn't fall back to
-- "Unknown" + default avatar. picture accepts:
--   - a numeric preset id '1'..'8'         (web/public/profiles/N.png)
--   - a relative file ending in .png/.webp (web/public/<file>, e.g.
--     'eyescoloured.png' which ships alongside the NUI bundle)
--   - a full https:// URL                  (any image hostable to FiveM CEF)
cfg.seedTracks = {
    creatorName    = 'rm_racing',
    creatorPicture = 'eyescoloured.png',
}

cfg.trackObjects = {
    -- ============ Flags ============
    { id = 'flag_red',         label = 'Red flag',           model = 'prop_flagpole_2b'  },
    { id = 'flag_blue',        label = 'Blue flag',          model = 'prop_flagpole_2c'  },
    { id = 'flag_checkered',   label = 'Checkered flag',     model = 'prop_flagpole_2d'  },
    { id = 'flag_beach_red',   label = 'Beach flag (black)', model = 'prop_beachflag_01' },
    { id = 'flag_beach_blue',  label = 'Beach flag (green)', model = 'prop_beachflag_02' },

    -- ============ Tyres (single / pile) ============
    { id = 'tyre',                 label = 'Tyre',                  model = 'prop_wheel_tyre'         },
    { id = 'tyre_stack',           label = 'Tyre stack',            model = 'prop_offroad_tyres02'    },
    { id = 'tyre_offroad',         label = 'Offroad tyre',          model = 'prop_offroad_tyres01_tu' },
    { id = 'tyre_offroad_open',    label = 'Offroad tyres (open)',  model = 'prop_offroad_tyres01'    },
    { id = 'tyre_snow',            label = 'Snow tyre',             model = 'prop_snow_tyre_01'       },
    { id = 'tyre_industrial_1',    label = 'Industrial tyre #1',    model = 'v_ind_cm_tyre01'         },
    { id = 'tyre_industrial_3',    label = 'Industrial tyre #3',    model = 'v_ind_cm_tyre03'         },
    { id = 'tyre_industrial_6',    label = 'Industrial tyre #6',    model = 'v_ind_cm_tyre06'         },
    { id = 'tyre_industrial_7',    label = 'Industrial tyre #7',    model = 'v_ind_cm_tyre07'         },
    { id = 'tyre_procedural',      label = 'Procedural tyre',       model = 'ng_proc_tyre_01'         },
    { id = 'tyre_junk_pile',       label = 'Junk tyre cluster',     model = 'v_46_cm_tyres'           },
    { id = 'tyre_carware_pile',    label = 'Carware tyre rack',     model = 'imp_carwarecw_tyres'     },
    { id = 'tyre_sea_pile',        label = 'Sea tyre pile',         model = 'ch1_12_sea_tyrepileb'    },
    { id = 'tyre_shelves',         label = 'Tyre shelves',          model = 'v_71_tyreshelves'        },
    { id = 'tyre_hoop_atomic',     label = 'Atomic hoop tyre',      model = 'stt_prop_hoop_tyre_01a'  },
    { id = 'tyre_arrow',           label = 'Arrow tyre',            model = 'xs_prop_arrow_tyre_01a'  },
    { id = 'tyre_gate_arch',       label = 'Tyre gate arch',        model = 'xs_prop_gate_tyre_01a_wl'},

    -- ============ Tyre walls (basic) ============
    { id = 'tyre_wall',            label = 'Tyre wall',             model = 'prop_tyre_wall_03b'      },
    { id = 'tyre_wall_short',      label = 'Tyre wall (short)',     model = 'prop_tyre_wall_01'       },
    { id = 'tyre_wall_rolled',     label = 'Tyre wall (rolled)',    model = 'prop_tyre_wall_03'       },
    { id = 'tyre_wall_mid',        label = 'Tyre wall (mid)',       model = 'prop_tyre_wall_03c'      },
    { id = 'tyre_wall_yellow',     label = 'Tyre wall (yellow)',    model = 'prop_tyre_wall_04'       },
    { id = 'tyre_wall_heavy',      label = 'Tyre wall (heavy)',     model = 'prop_tyre_wall_05'       },
    { id = 'tyre_wall_curved',     label = 'Tyre wall (curved)',    model = 'xs_prop_wall_tyre_l_01a' },
    { id = 'tyre_wall_u_curve',    label = 'Tyre wall (U-curve)',   model = 'tr_prop_tr_tyre_wall_u_l'},

    -- ============ Stunt tyre walls — straight (sponsor liveries) ============
    { id = 'stt_wall_basic',       label = 'Stunt wall #1',         model = 'stt_prop_tyre_wall_01'   },
    { id = 'stt_wall_shitzu',      label = 'Shitzu wall',           model = 'stt_prop_tyre_wall_02'   },
    { id = 'stt_wall_sprunk',      label = 'Sprunk wall',           model = 'stt_prop_tyre_wall_03'   },
    { id = 'stt_wall_xero',        label = 'Xero wall',             model = 'stt_prop_tyre_wall_04'   },
    { id = 'stt_wall_pisswasser',  label = 'Pißwasser wall',        model = 'stt_prop_tyre_wall_05'   },
    { id = 'stt_wall_burgershot',  label = 'Burger Shot wall',      model = 'stt_prop_tyre_wall_06'   },
    { id = 'stt_wall_cheetah',     label = 'Cheetah wall',          model = 'stt_prop_tyre_wall_07'   },
    { id = 'stt_wall_globeoil',    label = 'Globe Oil wall',        model = 'stt_prop_tyre_wall_08'   },
    { id = 'stt_wall_vapid',       label = 'Vapid wall',            model = 'stt_prop_tyre_wall_09'   },
    { id = 'stt_wall_chevron',     label = 'Chevron wall (black)',  model = 'stt_prop_tyre_wall_010'  },
    { id = 'stt_wall_patriot',     label = 'Patriot wall',          model = 'stt_prop_tyre_wall_011'  },
    { id = 'stt_wall_pisswasser2', label = 'Pißwasser wall #2',     model = 'stt_prop_tyre_wall_012'  },
    { id = 'stt_wall_imponte_p',   label = 'Imponte wall (purple)', model = 'stt_prop_tyre_wall_013'  },
    { id = 'stt_wall_imponte_b',   label = 'Imponte wall (black)',  model = 'stt_prop_tyre_wall_014'  },
    { id = 'stt_wall_fukaru_f',    label = 'Fukaru F wall',         model = 'stt_prop_tyre_wall_015'  },
    { id = 'stt_wall_atomic_g',    label = 'Atomic wall (green)',   model = 'stt_prop_tyre_wall_016'  },
    { id = 'stt_wall_pisswasser3', label = 'Pißwasser wall #3',     model = 'stt_prop_tyre_wall_017'  },
    { id = 'stt_wall_imponte_w',   label = 'Imponte wall (white)',  model = 'stt_prop_tyre_wall_018'  },

    -- ============ Stunt tyre walls — curved (L = left bend, R = right bend) ============
    { id = 'stt_curve_l3',         label = 'Fukaru curve (L)',      model = 'stt_prop_tyre_wall_0l3'  },
    { id = 'stt_curve_r3',         label = 'Fukaru curve (R)',      model = 'stt_prop_tyre_wall_0r3'  },
    { id = 'stt_curve_r1',         label = 'Tyre wall curve (R)',   model = 'stt_prop_tyre_wall_0r1'  },
    { id = 'stt_curve_l2',         label = 'Sprunk curve (L)',      model = 'stt_prop_tyre_wall_0l2'  },
    { id = 'stt_curve_l04',        label = 'Xero curve (L)',        model = 'stt_prop_tyre_wall_0l04' },
    { id = 'stt_curve_r04',        label = 'Xero curve (R)',        model = 'stt_prop_tyre_wall_0r04' },
    { id = 'stt_curve_l05',        label = 'Sprunk curve (L)',      model = 'stt_prop_tyre_wall_0l05' },
    { id = 'stt_curve_r05',        label = 'Atomic curve (R)',      model = 'stt_prop_tyre_wall_0r05' },
    { id = 'stt_curve_l06',        label = 'Auto Exotic curve (L)', model = 'stt_prop_tyre_wall_0l06' },
    { id = 'stt_curve_r06',        label = 'Pißwasser curve (R)',   model = 'stt_prop_tyre_wall_0r06' },
    { id = 'stt_curve_l07',        label = 'Fukaru curve (L)',      model = 'stt_prop_tyre_wall_0l07' },
    { id = 'stt_curve_r07',        label = 'Vapid curve (R)',       model = 'stt_prop_tyre_wall_0r07' },
    { id = 'stt_curve_l08',        label = 'Cheetah curve (L)',     model = 'stt_prop_tyre_wall_0l08' },
    { id = 'stt_curve_r08',        label = 'Cheetah curve (R)',     model = 'stt_prop_tyre_wall_0r08' },
    { id = 'stt_curve_r09',        label = 'Globe Oil curve (R)',   model = 'stt_prop_tyre_wall_0r09' },
    { id = 'stt_curve_l010',       label = 'Globe Oil curve (L)',   model = 'stt_prop_tyre_wall_0l010'},
    { id = 'stt_curve_r010',       label = 'Vapid curve (R)',       model = 'stt_prop_tyre_wall_0r010'},
    { id = 'stt_curve_l012',       label = 'Vapid curve (L)',       model = 'stt_prop_tyre_wall_0l012'},
    { id = 'stt_curve_r012',       label = 'Xero curve (R)',        model = 'stt_prop_tyre_wall_0r012'},
    { id = 'stt_curve_l013',       label = 'Junk Energy curve (L)', model = 'stt_prop_tyre_wall_0l013'},
    { id = 'stt_curve_r013',       label = 'Nagasaki curve (R)',    model = 'stt_prop_tyre_wall_0r013'},
    { id = 'stt_curve_l014',       label = 'Cheetah curve (L)',     model = 'stt_prop_tyre_wall_0l014'},
    { id = 'stt_curve_r014',       label = 'Xero curve (R)',        model = 'stt_prop_tyre_wall_0r014'},
    { id = 'stt_curve_l015',       label = 'Nagasaki curve (L)',    model = 'stt_prop_tyre_wall_0l015'},
    { id = 'stt_curve_r015',       label = 'Burger Shot curve (R)', model = 'stt_prop_tyre_wall_0r015'},
    { id = 'stt_curve_l016',       label = 'Atomic curve (L)',      model = 'stt_prop_tyre_wall_0l016'},
    { id = 'stt_curve_l16',        label = 'Fukaru green curve',    model = 'stt_prop_tyre_wall_0l16' },
    { id = 'stt_curve_l017',       label = 'Pißwasser curve (L)',   model = 'stt_prop_tyre_wall_0l017'},
    { id = 'stt_curve_r017',       label = 'Fukaru curve (R)',      model = 'stt_prop_tyre_wall_0r017'},
    { id = 'stt_curve_l018',       label = 'Imponte curve (L)',     model = 'stt_prop_tyre_wall_0l018'},
    { id = 'stt_curve_r018',       label = 'Nagasaki curve (R)',    model = 'stt_prop_tyre_wall_0r018'},
    { id = 'stt_curve_l019',       label = 'Fukaru curve (L)',      model = 'stt_prop_tyre_wall_0l019'},
    { id = 'stt_curve_r019',       label = 'Fukaru curve (R)',      model = 'stt_prop_tyre_wall_0r019'},
    { id = 'stt_curve_l020',       label = 'Fukaru F curve (L)',    model = 'stt_prop_tyre_wall_0l020'},

    -- ============ Chevron / direction walls (Tuner DLC arrows + lit panels) ============
    { id = 'chevron_lit',          label = 'Chevron wall (lit)',    model = 'sum_prop_ac_tyre_wall_lit_01'  },
    { id = 'chevron_lit_l',        label = 'Chevron wall lit (L)',  model = 'sum_prop_ac_tyre_wall_lit_0l1' },
    { id = 'chevron_lit_r',        label = 'Chevron wall lit (R)',  model = 'sum_prop_ac_tyre_wall_lit_0r1' },
    { id = 'chevron_arrow_l',      label = 'Chevron arrow (L)',     model = 'sum_prop_ac_tyre_wall_u_l'     },
    { id = 'chevron_arrow_r',      label = 'Chevron arrow (R)',     model = 'sum_prop_ac_tyre_wall_u_r'     },
    { id = 'pit_arrow_l',          label = 'PITS arrow (L)',        model = 'sum_prop_ac_tyre_wall_pit_l'   },
    { id = 'pit_arrow_r',          label = 'PITS arrow (R)',        model = 'sum_prop_ac_tyre_wall_pit_r'   },

    -- ============ Special ============
    { id = 'wall_nitrous',         label = 'Nitrous boost wall',    model = 'm24_2_prop_m42_tyre_wall_nos' },

    -- ============ Cones / barriers / barrels / bales ============
    { id = 'cone',                 label = 'Traffic cone',          model = 'prop_mp_cone_01'    },
    { id = 'barrier',              label = 'Barrier',               model = 'prop_mp_barrier_02' },
    { id = 'barrel',               label = 'Barrel',                model = 'prop_barrel_02a'    },
    { id = 'hay_bale',             label = 'Hay bale',              model = 'prop_offroad_bale01'},
}


-- ============================================================================
--  11. COMPETITIONS
--  Auto-generated competition lobbies + manual competition rules.
-- ============================================================================

cfg.competition = {
    -- Local server time HH:MM. Set to {} to disable the auto-scheduler.
    autoSchedule = {
        '12:00',
        '16:00',
        '19:00',
        '22:00',
    },

    lobbyDurationSec   = 900,   ---@type number  -- 15 min lobby before scheduler auto-starts (or auto-cancels if empty)
    minRatingToHost    = 1700,  ---@type number  -- min ELO to start a manual competition
    prizePoolPerPlayer = 2500,  ---@type number  -- added to pool for each joined racer

    -- Percent per finishing position; must sum to 100.
    prizeDistribution = {
        50, 25, 15, 10,
    },

    -- Vehicle classes pickable in lobby setup. id = -1 means "any class".
    vehicleClasses = {                 ---@type { id: number, label: string }[]
        { id = -1, label = 'Any'             },
        { id =  0, label = 'Compacts'        },
        { id =  1, label = 'Sedans'          },
        { id =  2, label = 'SUVs'            },
        { id =  3, label = 'Coupes'          },
        { id =  4, label = 'Muscle'          },
        { id =  5, label = 'Sports Classics' },
        { id =  6, label = 'Sports'          },
        { id =  7, label = 'Super'           },
    },

    defaultMaxPlayers = 8,
    phasingDefault    = true,

    -- Slipstream (drafting) — flips the engine's built-in slipstream
    -- physics for the duration of the race via SetEnableVehicleSlipstreaming.
    -- Global toggle (not per-vehicle), called once on start and once on end.
    slipstreamDefault = false,

    -- Private routing bucket — when on, race start moves every participant
    -- (and their vehicle) into an isolated server bucket so non-racers in
    -- the public world don't collide / desync with the race. Race end
    -- restores everyone to bucket 0. Voice does NOT respect buckets —
    -- proximity voice still bleeds through.
    privateBucketDefault = false,
}


-- ============================================================================
--  12. PINK SLIPS — wager your vehicle ownership in a 1v1 race.
--
--  The losing driver's vehicle gets transferred to the winner via the
--  framework bridge (qb/qbx → player_vehicles, esx → owned_vehicles).
--  Standalone has no inventory of vehicles, so the toggle is hidden in
--  the lobby UI when running standalone.
--  Claims are pending until the winner visits a redeemLocations entry and
--  interacts there — physical delivery, not a silent DB swap.
-- ============================================================================

cfg.pinkSlips = {
    enabled      = true,

    -- Race types this flag CAN be applied to. Drift / elimination / TT are
    -- excluded — pink slips need a clean position-based winner.
    allowedTypes = { 'sprint', 'circuit' },  ---@type string[]

    -- Forfeit window: if either player DNFs, the race still resolves and
    -- the surviving racer claims. Set false to require both to finish.
    awardOnDnf   = true,

    -- Where the winner picks up the vehicle. Each entry produces a blip
    -- and a small interaction zone. Add as many as you like.
    redeemLocations = {
        { x = 100.0, y = -1080.0, z = 29.2, label = 'Pink Slip Garage (Mission Row)' },
    },

    redeemBlip = {
        sprite = 357,                ---@type number
        color  = 1,                  ---@type number  -- 1 = red
        scale  = 0.85,
        label  = 'Pink Slip Redeem',
    },
}


-- ============================================================================
--  13. BOUNTIES — time-trial chase prizes.
--
--  A scheduled job rolls one active bounty at a time. When a player runs
--  the bountied track in a Time Trial mode and finishes UNDER the
--  target_ms, they claim the prize. Bounties expire after `expireMin` if
--  unclaimed.
-- ============================================================================

cfg.bounties = {
    enabled            = true,
    intervalMin        = 30,                        ---@type number  -- new roll every N minutes (also re-rolls if previous claimed/expired)
    expireMin          = 240,                       ---@type number  -- auto-expire after N minutes (0 = never)
    prize              = 75000,                     ---@type number  -- credits paid to the claimer
    eligibleTrackTypes = { 'time_trial', 'sprint', 'circuit' },  ---@type string[]
    requireRank        = false,                     ---@type string|false  -- e.g. 'Master' to gate claims; false = open
    minResultsOnTrack  = 0,                         ---@type number  -- 0 = never block on race count (random track gets picked even on fresh DBs)

    -- Target time formula. Bounty is computed as `best_ms * targetMult + random(extraMs)`.
    -- 1.0 + small variance keeps it tight for top players; pad it (e.g. 1.10 →
    -- 10% slower than best) for casual servers.
    targetMult         = 1.05,
    extraMs            = { min = 0, max = 3000 },

    -- Fallback target for tracks with NO best time yet (fresh server / no
    -- results on the picked track). Target = checkpoints_count × this value.
    -- ~5 s per CP is a generous ballpark; tweak if your tracks are unusually
    -- short or long.
    fallbackMsPerCp    = 5000,
}


-- ============================================================================
--  14. ITEM PAYOUTS
--
--  Item rewards distributed alongside money prizes when a race finishes.
--  Each `lists.<name>` is a weighted pool: an entry's `weight` is the chance
--  relative to the sum of weights in its list; `count` is rolled uniformly
--  in [min, max]. payoutStyle picks WHO gets a roll:
--    'all'       → every finisher gets one roll
--    'topThree'  → positions 1..3 each roll
--    'onlyOne'   → only the winner rolls
--    'custom'    → uses `perPosition[pos]` to pick which list(s) to roll
-- ============================================================================

cfg.itemPayouts = {
    enabled          = true,         ---@type boolean  -- enable when item names below exist in your inventory
    defaultList      = 'standard',   ---@type string   -- list rolled for non-custom styles
    payoutStyle      = 'topThree',   ---@type 'all'|'topThree'|'onlyOne'|'custom'
    minRaceLengthSec = 10,           ---@type number   -- skip payouts for short fluke races
    onlyCompetitions = false,        ---@type boolean  -- if true, only ranked competitions drop items

    ---@type table<string, { item: string, weight: number, count: { min: number, max: number } }[]>
    lists = {
        standard = {
            { item = 'water',    weight = 8, count = { min = 1, max = 3 } },
            { item = 'sandwich', weight = 6, count = { min = 1, max = 2 } },
            { item = 'lockpick', weight = 2, count = { min = 1, max = 1 } },
        },
        rare = {
            { item = 'lockpick',         weight = 4, count = { min = 1, max = 2 } },
            { item = 'advancedlockpick', weight = 1, count = { min = 1, max = 1 } },
        },
    },

    -- Only used when payoutStyle = 'custom'.
    ---@type table<integer, string[]>
    perPosition = {
        [1] = { 'rare', 'standard' },
        [2] = { 'standard' },
        [3] = { 'standard' },
    },
}


-- ============================================================================
--  15. 3D GPS ROUTE RENDERER
--
--  During a race the upcoming checkpoint polyline is drawn in 3D world
--  space so the player can follow the route without taking their eyes off
--  the road to glance at the minimap. Color is per-player (profile.gps_color)
--  and style is per-player (profile.gps_route_type). Set `enabled = false`
--  to disable the entire renderer.
-- ============================================================================

cfg.gps3d = {
    enabled       = true,

    -- Number of upcoming checkpoints to render ahead of the player. 1 = next
    -- one only; higher = full path preview (more drawcalls per frame).
    lookahead     = 4,

    -- Default style; each player can override via profile.gps_route_type:
    --   'off'           → nothing drawn
    --   'line' / 'arrow'→ legacy DrawLine-based renderer (no YTD needed)
    --   any preset id   → textured ribbon using the asset in stream/ (chevrons.ytd)
    defaultStyle  = 'classic',

    -- Lift the line a hair above the road so it doesn't z-fight on inclines
    -- but stays close to the surface — the car effectively drives over it
    -- instead of under a floating ribbon.
    heightOffset  = 0.1,

    -- Chevron arrow spacing (m) and dimensions for the LEGACY DrawLine path.
    arrowSpacing  = 6.0,
    arrowLength   = 1.6,
    arrowWidth    = 0.9,

    -- Textured ribbon (preset) parameters. The YTD ships in stream/ — its
    -- internal texture-dictionary name is what RequestStreamedTextureDict
    -- expects (usually 'chevrons' even if the .ytd file was renamed).
    textureDict     = 'routes',
    ribbonWidth     = 1.35,   -- half-width of the ribbon (m), so total = 2.7m
    ribbonLift      = 0.05,   -- extra Z offset on top of heightOffset
    ribbonRepeatM   = 4.0,    -- texture repeats every N metres along the path

    -- Preset list. `id` is what gets stored in profile.gps_route_type;
    -- `texture` is the entry inside textureDict to sample. Order here drives
    -- the order shown in the profile picker.
    --
    -- Per-preset `repeatM` overrides the global ribbonRepeatM. Per-preset
    -- `tintable = false` skips the player's gps_color tint and draws the
    -- texture with white modulation, preserving the colors baked into the
    -- texture itself. Per-preset `alpha` overrides the default 230 — the
    -- chevron_line textures have an opaque white background in their alpha
    -- channel, so we drop alpha to fade the slab and keep the chevrons
    -- the dominant element.
    presets = {
        { id = 'classic',         label = 'Classic',        texture = 'chevrons',        repeatM = 4.0, tintable = true  },
        { id = 'chevron_line_06', label = 'Line 06',        texture = 'chevron_line_06', repeatM = 0.8, tintable = false, alpha = 130 },
        { id = 'chevron_line_07', label = 'Line 07',        texture = 'chevron_line_07', repeatM = 0.8, tintable = false, alpha = 130 },
        { id = 'chevron_line_08', label = 'Line 08',        texture = 'chevron_line_08', repeatM = 0.8, tintable = false, alpha = 130 },
        { id = 'chevron_fire_01', label = 'Fire',           texture = 'chevron_fire_01', repeatM = 4.0, tintable = true  },
        { id = 'chevron_ice_01',  label = 'Ice',            texture = 'chevron_ice_01',  repeatM = 4.0, tintable = true  },
        { id = 'chevron_neon_01', label = 'Neon',           texture = 'chevron_neon_01', repeatM = 4.0, tintable = true  },
    },
}


-- ============================================================================
--  16. DISCORD WEBHOOKS — config moved to server/webhook.lua
--
--  The webhook URL is sensitive credential data and must NOT live in cfg.lua,
--  which is shipped/escrowed as part of the resource. server/webhook.lua is
--  listed in escrow_ignore so customers can edit the URL after install
--  without seeing the rest of the source. Set `WEBHOOK_URL` at the top of
--  that file.
-- ============================================================================


-- ============================================================================
--  17. LIMITS — page sizes / row caps
-- ============================================================================

cfg.limits = {
    tracksPerRequest  = 500,
    leaderboardSize   = 100,
    recentEventsCount = 10,
    recentRacesCount  = 100,
    trackBestResults  = 10,
}

```

### Heist Configuration

```
Config.heist = {
    --------------------------
    ----- QUIET APPROACH -----
    --------------------------
    terminalHacking = {
        coords = vector3(-623.15, -216.22, 53.54),
        requiredItem = "hacker_phone",
        removeItemOnUse = false,

        -- minigame options
        minigame = "none",
        duration = 60,      -- duration of hack minigame in seconds
        solutionLength = 5, -- length of the solution string
        policeAlertDuration = 30, -- seconds (after the hacking incident should the police be alerted)
    },
    plantingGasBomb = {
        ventModel = "prop_aircon_m_04", -- model of the vent where the bomb should be planted
        coords = vector3(-631.13, -228.23, 55.65),
        offset = vector3(-0.83, -0.5, 0.8), -- offset from the coords where the bomb should be planted (relative to the vent)
        requiredItem = "gas_bomb",
        removeItemOnUse = true,

        mask = {
            enable = true,
            requiredItem = "gas_mask",
            removeItemOnUse = true,

            clotheId = 175,
            duration = 300, -- duration of the gas mask effect in seconds
            damage = 5, -- damage dealt to the player every interval while the gas mask is active
            damageInterval = 5, -- interval in seconds at which the damage is applied to the player while the gas mask is active
            zone = vector4(-622.364, -231.020, 38.047, 11.0)
        },

        -- minigame options
        minigame = "none",
    },
    --------------------------
    ----- LOUD APPROACH ------
    --------------------------
    vangelicoEntrance = {
        coords = vector3(-631.760, -237.737, 38.073),
        lockpick = {
            requiredItem = "lockpick",
            removeItemOnUse = true,

            strength = 0.1, -- from 0.1 (easy) to 7 (hard)
            difficulty = 0.1, -- from 0.1 (easy) to 7 (hard)
            pins = 2, -- number of pins (max 9)

            -- minigame options
            minigame = "none",
        },
        thermite = {
            offset = vector3(1.3, -0.03, 0.0),
            requiredItem = "thermite_bomb",
            removeItemOnUse = true,

            -- minigame options
            minigame = "default",
        },
    },
    -------------------------
    ---- GENERAL OPTIONS ----
    -------------------------
    keypad = {
        coords = vector3(-629.135, -230.949, 38.057),
        searchCoords = {
            vector3(-625.925, -226.127, 38.057),
            vector3(-623.040, -235.807, 38.057),
            vector3(-618.774, -236.339, 38.057),
            vector3(-617.183, -231.740, 38.057),
            vector3(-621.453, -225.867, 38.057),
            vector3(-622.500, -229.772, 38.057),
            vector3(-630.369, -232.243, 38.057)
        },
    },
    doors = {
        { -- front doors
            door_1_model = 1425919976,
            door_2_model = 9467943,
            door_1_coords = vector4(-631.95538330078, -236.33326721191, 38.206531524658, 306.60577392578),
            door_2_coords = vector4(-630.42651367188, -238.43754577637, 38.206531524658, 305.39260864258),
            status = "locked", -- "locked", "unlocked"
        },
        { -- security room door
            door_1_model = 1335309163,
            door_1_coords = vector4(-629.13385009766, -230.15170288086, 38.20658493042, 36.000022888184),
            status = "locked", -- "locked", "unlocked"
        }
    },
    ---------------------------
    ----- LOOTABLE OPTIONS ----
    ---------------------------
    containers = {
        --  Weapons allowed to smash jewelry cabinets (whitelisted weapons)
        smashWeapons = {
            'WEAPON_ASSAULTRIFLE',
            'WEAPON_CARBINERIFLE',
            'WEAPON_ADVANCEDRIFLE',
            'WEAPON_BULLPUPRIFLE',
        },

        -- required items for certain loot actions:
        requiredItems = {
            paintings = {
                requiredItem = "painting_knife",
                removeItemOnUse = false,
            },
            displayCases = {
                requiredItem = "glass_cutter",
                removeItemOnUse = false,
            },
            statues = {
                requiredItem = "bag",
                removeItemOnUse = false,
            }
        },

        -- Cabinets:
        cabinets = {
            {
                coords = vector4(-626.83, -235.35, 38.05, 36.17),
                rayFire = 'DES_Jewel_Cab3',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-625.81, -234.7, 38.05, 36.17),
                rayFire = 'DES_Jewel_Cab4',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-626.95, -233.14, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-628.0, -233.86, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-625.7, -237.8, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab3',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-626.7, -238.58, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab2',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-624.55, -231.06, 38.05, 305.0),
                rayFire = 'DES_Jewel_Cab4',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-623.13, -232.94, 38.05, 305.0),
                rayFire = 'DES_Jewel_Cab',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-620.29, -234.44, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-619.15, -233.66, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab3',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-620.19, -233.44, 38.05, 36.17),
                rayFire = 'DES_Jewel_Cab4',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-617.63, -230.58, 38.05, 305.0),
                rayFire = 'DES_Jewel_Cab2',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-618.33, -229.55, 38.05, 305.0),
                rayFire = 'DES_Jewel_Cab3',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-619.7, -230.33, 38.05, 125.0),
                rayFire = 'DES_Jewel_Cab',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-620.95, -228.6, 38.05, 125.0),
                rayFire = 'DES_Jewel_Cab3',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-619.79, -227.6, 38.05, 305.0),
                rayFire = 'DES_Jewel_Cab2',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-620.42, -226.6, 38.05, 305.0),
                rayFire = 'DES_Jewel_Cab',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-623.94, -227.18, 38.05, 36.17),
                rayFire = 'DES_Jewel_Cab4',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-624.91, -227.87, 38.05, 36.17),
                rayFire = 'DES_Jewel_Cab3',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            },
            {
                coords = vector4(-623.94, -228.05, 38.05, 216.17),
                rayFire = 'DES_Jewel_Cab2',
                rewards = {
                    { itemName = "ring", amount = { min = 1, max = 2 } },
                    { itemName = "necklace", amount = { min = 1, max = 3 } },
                }
            }
        },

        -- Paintings:
        paintings = {
            {
                model = 'h4_prop_h4_painting_01h',
                coords = vector4(-627.21325683594, -228.30999755859, 38.106048583984, 92.391586303711),
                rewards = {
                    { itemName = "painting_01h", amount = { min = 1, max = 1 } },
                }
            },
            {
                model = 'h4_prop_h4_painting_01d',
                coords = vector4(-622.79998779297, -225.13999938965, 38.106048583984, 339.9309387207),
                rewards = {
                    { itemName = "painting_01d", amount = { min = 1, max = 1 } },
                }
            },
            {
                model = 'h4_prop_h4_painting_01e',
                coords = vector4(-617.0, -233.2200012207, 38.106048583984, 271.89691162109),
                rewards = {
                    { itemName = "painting_01e", amount = { min = 1, max = 1 } },
                }
            },
            {
                model = 'h4_prop_h4_painting_01b',
                coords = vector4(-621.36102294922, -236.33099365234, 38.106048583984, 161.18635559082),
                rewards = {
                    { itemName = "painting_01b", amount = { min = 1, max = 1 } },
                }
            }
        },

        -- Display Cases:
        displayCases = {
            {
                baseModel = "h4_prop_h4_glass_disp_01a",
                displayModel = "h4_prop_h4_diamond_disp_01a",
                rewardModel = "h4_prop_h4_diamond_01a",
                coords = vector4(-617.4622, -227.4347, 37.057, 127.06),
                rewardOffset = vector3(0.0, 0.0, 1.225),
                rewards = {
                    { itemName = "big_diamond", amount = 1 },
                }
            },
            {
                baseModel = "h4_prop_h4_glass_disp_01a",
                displayModel = "h4_prop_h4_diamond_disp_01a",
                rewardModel = "h4_prop_h4_art_pant_01a",
                coords = vector4(-631.7185, -234.6784, 36.9402, -53.705),
                rewardOffset = vector3(0.0, 0.0, 1.25),
                rewards = {
                    { itemName = "panther", amount = 1 },
                }
            },
            {
                baseModel = "h4_prop_h4_glass_disp_01a",
                displayModel = "h4_prop_h4_neck_disp_01a",
                rewardModel = "h4_prop_h4_necklace_01a",
                coords = vector4(-628.8231, -238.7317, 36.8647, -53.705),
                rewardOffset = vector3(0.0, 0.0, 1.2),
                rewards = {
                    { itemName = "diamond_necklace", amount = 1 },
                }  
            }
        },

        -- Statues:
        statues = {
            {
                tableModel = "v_ret_tablesml",
                statueModel = "vw_prop_casino_art_panther_01b",
                coords = vector4(-622.04541015625, -230.70222473145, 37.058856964111, 303.0),
                rewards = {
                    { itemName = "panther_statue", amount = { min = 1, max = 1 } },
                }
            }
        },

        -- Safes:
        safes = {
            {
                model = "h4_prop_h4_safe_01a",
                coords = vector4(-630.88995361328, -228.27618408203, 36.959362030029, 35.38557434082),
                difficulty = 1, -- 1-4 (1 being the easiest, 4 being the hardest)
                rewards = {
                    { itemName = "money", amount = { min = 500, max = 1500 } },
                }
            }
        }
    },
}


Config.missionData = {
    takePhoto = {
        vector3(-632.86, -238.62, 38.07),
        vector3(-623.01, -216.17, 53.54),
        vector3(-622.48, -233.67, 59.16),
        vector3(-623.24, -231.54, 38.06),
        vector3(-626.04, -238.05, 38.06),
        vector3(-629.15, -230.69, 38.06)
    },
    takeEquipment = {
        pedModel = "g_m_y_mexgang_01",
        coords = {
            vector4(591.996, 2782.770, 42.481, 8.644)
        },
        items = {
            {
                name = "hacker_phone",
                amount = 1
            },
            {
                name = "gas_bomb",
                amount = 1
            },
            {
                name = "gas_mask",
                amount = 4
            },
            {
                name = "glass_cutter",
                amount = 1
            },
            {
                name = "painting_knife",
                amount = 1
            },
            {
                name = "bag",
                amount = 1
            },
        }
    },
    takeClothes = {
        pedModel = "s_m_m_autoshop_02",
        coords = {
            vector4(632.676, -3015.434, 6.336, 359.265)
        },
        items = {
            {
                name = "outfit_bag",
                amount = 1
            }
        }
    },
}
```
