Skip to content

Inventory: config files ​

These files stay readable in every build (escrow too), so you can change them. Shown as shipped in version 1.0.3.

config/config.lua ​

lua
Config = {
    -- Player items are stored per framework (bridge/ picks the framework):
    --
    --   QBCore              `players`.`inventory`, the native column. No migration.
    --   Qbox/ESX/standalone `lumo_inventories`.`items`, created on first start.
    --
    -- On Qbox `players.inventory` already belongs to ox_inventory in a different
    -- format, so it is never written to.

    UseTarget = GetConvar('UseTarget', 'false') == 'true',

    MaxWeight = 120000, -- grams
    MaxSlots = 40,

    -- Who equips weapons, loads ammo and fits attachments.
    --   'auto'        qb-weapons when it runs (QBCore), otherwise the built-in
    --                 handler (Qbox, ESX, standalone). See Config.Weapons below.
    --   'qb-weapons'  always qb-weapons.
    --   'builtin'     always the built-in handler.
    --   any other     a resource with qb-weapons' (item, canShoot) contract;
    --                 set weaponEvent to its use event.
    ItemUse = {weaponResource = 'auto', weaponEvent = 'qb-weapons:client:UseWeapon'},

    -- Built-in weapon handler (used when qb-weapons is not running).
    Weapons = {
        -- Each ammo item loads this many rounds into the weapon in your hands,
        -- if the weapon takes that ammo type.
        Ammo = {
            pistol_ammo  = { type = 'AMMO_PISTOL', amount = 12 },
            smg_ammo     = { type = 'AMMO_SMG', amount = 20 },
            rifle_ammo   = { type = 'AMMO_RIFLE', amount = 30 },
            shotgun_ammo = { type = 'AMMO_SHOTGUN', amount = 10 },
            mg_ammo      = { type = 'AMMO_MG', amount = 30 },
            snp_ammo     = { type = 'AMMO_SNIPER', amount = 10 },
            emp_ammo     = { type = 'AMMO_EMPLAUNCHER', amount = 10 },
        },
        MaxAmmo = 250,          -- rounds one weapon can hold
        -- A weapon at 0 quality cannot be fired. Each shot takes this much
        -- quality off (0 = weapons never wear out).
        WearPerShot = 0.05,
        -- Block GTA's weapon wheel (TAB) and mouse-wheel weapon switching, which
        -- open over the inventory. 'auto': while the built-in handler is in use
        -- (ESX, Qbox, standalone); QBCore's qb-smallresources already does it.
        DisableWeaponWheel = 'auto',
    },

    -- ESX only.
    ESX = {
        -- Replace xPlayer.addInventoryItem / removeInventoryItem /
        -- getInventoryItem / canCarryItem / addWeapon / … so every ESX script
        -- gives and takes items from this inventory. Leave on.
        OverridePlayerFunctions = true,
        -- Weapons are inventory items. xPlayer.addWeapon adds a weapon item,
        -- xPlayer.removeWeapon removes it, getLoadout lists them.
        WeaponsAsItems = true,
        -- ESX's `items` table weighs in kilograms (bread = 1, 24 kg limit);
        -- this inventory weighs in grams. ESX weight x scale = grams.
        ItemWeightScale = 1000,
        -- false: ignore ESX weights and use the built-in catalogue's.
        UseESXWeights = true,
        -- Copy each character's existing ESX items and weapons in the first
        -- time they log in with lumo-inventory (once per character).
        ImportESXInventory = true,
        -- Items sold by these shop resources are read from their config: any
        -- the inventory does not know yet are added (shop label, ShopItemWeight
        -- grams), and they use the shop's own picture (nui://esx_shops/web/images/…).
        ShopResources = { 'esx_shops' },
        ShopItemWeight = 250,
        -- esx_weaponshop sees only the weapons on your ped. At its counters,
        -- put every weapon item on the ped (holstered) so it knows what you
        -- own, offers ammo and upgrades, and does not sell the same gun again.
        StageWeaponsAtShops = true,
    },

    -- Items to add on ESX and standalone without editing shared/items.lua.
    -- QBCore/Qbox: add items to your core's item list as usual.
    --   my_item = { label = 'My Item', weight = 100, image = 'my_item.png',
    --               unique = false, useable = true, description = '…' },
    ExtraItems = {},

    --- Food, drink and alcohol handlers.
    ---
    --- These exist because use-handlers are registered per item *name*, by
    --- whichever smallresources your framework ships. On QBCore that is
    --- qb-smallresources and it already claims these names; on Qbox it is
    --- qbx_smallresources, which registers ox names (`water`, `burger`) and so
    --- leaves the QBCore-named items with no handler at all -- using one did
    --- nothing, silently.
    ---
    --- auto: QBCore uses a live registered handler (qb-smallresources, medical,
    --- etc.), falling back to this inventory if none exists. Other frameworks
    --- use the built-in definitions below. Checked at use time, not startup.
    --- inventory/always: force built-in definitions; false: external handlers only.
    Consumables = {
        mode = 'auto',
        externalCooldown = 6500, -- guard repeated native consumable activation
        esxStatusMax = 1000000, -- match esx_status Config.StatusMax
        applyEffect = nil, -- optional custom (source, kind, percentAmount) hook

        --- Replenish amounts are ranges; a value is rolled per use.
        ---
        --- Only items present in your item catalogue can be used, so
        --- listing an item you do not have costs nothing.
        eat = {
            sandwich       = { 35, 54 },
            tosti          = { 40, 50 },
            twerks_candy   = { 35, 54 },
            snikkel_candy  = { 40, 50 },
            burger         = { 40, 55 },
            taco           = { 40, 55 },
            donut          = { 25, 40 },
            momo           = { 40, 55 },
            chocolate      = { 25, 40 },
            pizza_ham      = { 45, 60 },
            taco_chicken   = { 40, 55 },
        },

        drink = {
            water_bottle   = { 35, 54 },
            water          = { 35, 54 },
            kurkakola      = { 35, 54 },
            cola           = { 35, 54 },
            coffee         = { 40, 50 },
            sprunk         = { 35, 50 },
            energy_drink   = { 40, 55 },
            grapejuice     = { 35, 50 },
        },

        --- Alcohol replenishes thirst but also builds drunkenness.
        alcohol = {
            whiskey        = { 20, 30 },
            whiskey_bottle = { 20, 30 },
            luxury_whiskey = { 25, 35 },
            beer           = { 30, 40 },
            beer_bottle    = { 30, 40 },
            vodka          = { 20, 40 },
            vodka_bottle   = { 20, 40 },
            wine           = { 20, 35 },
            blackberry_ale = { 30, 40 },
        },

        --- Restores health rather than hunger or thirst.
        heal = {
            bandage        = { 20, 30 },
            firstaid       = { 45, 60 },
            painkiller     = { 15, 25 },
            painkillers    = { 15, 25 },
        },

        duration = { eat = 5000, drink = 4000, alcohol = 4000, heal = 4000 },
    },

    Money = {
        useCashAsItem = false, -- Keep in sync with qb-core: QBConfig.Money.UseCashAsItem
        cashItemName = 'cash', -- Keep in sync with qb-core: QBConfig.Money.CashItemName
        cashItemSlot = 6, -- Keep in sync with qb-core: QBConfig.Money.CashItemSlot
        -- Pin cash to cashItemSlot and forbid moving/splitting/dropping it.
        -- Off by default: cash is still a real item, it just behaves like every
        -- other item (drag anywhere, stack, split, drop, hand over).
        lockCashSlot = false,
    },

    PlayerInventoryOverride = {
        enabled = true,
        infiniteMaxWeight = 2147483647,
        infiniteSlots = 500,
        licenses = {
            -- ['license:xxxxxxxx'] = true,
        },
    },

    UISettings = {
        closeOnDoubleUse = true,
        tooltipDelayShow = 140,
        tooltipDelayHide = 40,
        compactMode = false,
        sharpMode = true,
        enableTypeGlow = true,
        enableRarityGlow = true,
        showTooltipHints = true,
        notificationDuration = 3000,
        notificationMaxStack = 3,
    },

    -- ═══════════════════════════════════════════════════════════════════
    --  Encumbrance
    --
    --  The pane header has always drawn a weight gauge that nothing consumed:
    --  119/120 kg moved exactly like carrying nothing at all. This makes the
    --  limit a trade-off instead of a number that only bites when a pickup is
    --  refused. Purely client-side handling — nothing is sent to the server.
    -- ═══════════════════════════════════════════════════════════════════
    Encumbrance = {
        enabled = true,
        threshold = 0.75,     -- fraction of max weight before any penalty starts
        minMoveRate = 0.88,   -- move rate at 100% capacity (1.0 = normal)
        staminaDrain = 6.0,   -- sprint stamina drained per tick while overloaded
        noSprintAt = 0.95,    -- above this fraction, sprinting is refused
        tickMs = 250,
        notify = true,        -- one notification when you cross the threshold
    },

    -- ═══════════════════════════════════════════════════════════════════
    --  Character panel: equipment (wear/remove clothing) + body damage
    -- ═══════════════════════════════════════════════════════════════════
    CharacterPanel = {
        enabled = true,        -- master switch for the whole left character panel
        showEquipment = true,  -- clickable clothing slots that toggle pieces on/off
        showBody = true,       -- body silhouette + HP / armor / bleed
        bodyResource = 'qb-ambulancejob', -- resource exporting GetBodyStatus
        refreshInterval = 800, -- ms between live body-status refreshes while open

        -- Each slot toggles a GTA clothing component or prop on the ped.
        --  kind     = 'component' (SetPedComponentVariation) or 'prop' (SetPedPropIndex)
        --  id       = the GTA component/prop index
        --  off      = drawable value used when the piece is "taken off"
        --             (props always use ClearPedProp, so 'off' is ignored for them)
        --  icon     = key matched to an inline SVG in the NUI
        -- Order here is the order shown in the panel.
        EquipmentSlots = {
            { key = 'hat',      label = 'Hat',     kind = 'prop',      id = 0,  icon = 'hat' },
            { key = 'glasses',  label = 'Glasses', kind = 'prop',      id = 1,  icon = 'glasses' },
            { key = 'ears',     label = 'Ears',    kind = 'prop',      id = 2,  icon = 'ears' },
            { key = 'mask',     label = 'Mask',    kind = 'component', id = 1,  off = 0,  icon = 'mask' },
            { key = 'jacket',   label = 'Jacket',  kind = 'component', id = 11, off = 15, icon = 'jacket' },
            { key = 'shirt',    label = 'Shirt',   kind = 'component', id = 8,  off = 15, icon = 'shirt' },
            { key = 'vest',     label = 'Vest',    kind = 'component', id = 9,  off = 0,  icon = 'vest' },
            { key = 'gloves',   label = 'Gloves',  kind = 'component', id = 3,  off = 15, icon = 'gloves' },
            { key = 'pants',    label = 'Pants',   kind = 'component', id = 4,  off = 14, icon = 'pants' },
            { key = 'shoes',    label = 'Shoes',   kind = 'component', id = 6,  off = 0,  icon = 'shoes' },
            { key = 'bag',      label = 'Bag',     kind = 'component', id = 5,  off = 0,  icon = 'bag' },
            { key = 'chain',    label = 'Chain',   kind = 'component', id = 7,  off = 0,  icon = 'chain' },
            { key = 'watch',    label = 'Watch',   kind = 'prop',      id = 6,  icon = 'watch' },
            { key = 'bracelet', label = 'Brace',   kind = 'prop',      id = 7,  icon = 'bracelet' },
        },

        -- Body regions on the silhouette, mapped to the ambulance BodyParts keys
        -- that feed each one. Highest severity among the listed parts wins.
        BodyRegions = {
            { key = 'head',     label = 'Head',      parts = { 'HEAD', 'NECK' } },
            { key = 'chest',    label = 'Chest',     parts = { 'SPINE', 'UPPER_BODY' } },
            { key = 'stomach',  label = 'Stomach',   parts = { 'LOWER_BODY' } },
            { key = 'leftArm',  label = 'Left Arm',  parts = { 'LARM', 'LHAND', 'LFINGER' } },
            { key = 'rightArm', label = 'Right Arm', parts = { 'RARM', 'RHAND', 'RFINGER' } },
            { key = 'leftLeg',  label = 'Left Leg',  parts = { 'LLEG', 'LFOOT' } },
            { key = 'rightLeg', label = 'Right Leg', parts = { 'RLEG', 'RFOOT' } },
        },
    },

    WeaponPreview = {
        Base = {
            distance = 2.7,
            height = 0.9,
            side = 0.0,
            fov = 47.0,
        },
        Groups = {
            pistol = { distance = 2.6, height = 0.88, fov = 45.0 },
            smg = { distance = 2.8, height = 0.95, fov = 48.0 },
            long = { distance = 3.1, height = 1.05, fov = 51.0 },
            heavy = { distance = 3.4, height = 1.1, fov = 54.0 },
        },
        Offsets = {
            distance = 0.0,
            height = 0.0,
            side = 0.0,
            fov = 0.0,
        },
        Smooth = {
            enter = 350,
            exit = 600,
        },
    },
    -- Once Human-style character stage: while the inventory is open, a scripted
    -- camera frames your ped front-and-centre between the two panels.
    -- IDLE ONLY: the camera activates only when standing still on foot — while
    -- running / driving / fighting the inventory opens with no camera change.
    InventoryCam = {
        -- Disabled: the inventory no longer reframes the ped on open.
        enabled = false,
        distance = 2.6,  -- metres in front of the ped
        height = 0.45,   -- camera height above the ped origin
        side = 0.0,      -- sideways offset (+right / -left)
        fov = 40.0,
        enter = 400,     -- ms blend in
        exit = 400,      -- ms blend out
    },
    -- Weapons slung on the player's back are drawn as real weapon objects, so
    -- whatever is fitted to the gun in the inventory -- scope, suppressor, grip,
    -- extended clip -- and whatever tint is applied to it show on the back too.
    -- Entities are local to each client and driven by a replicated statebag, so
    -- back items cost nothing on the networked entity budget. See
    -- client/backitems.lua for the full rationale.
    BackItems = {
        enabled = true,

        -- Performance knobs. Nothing here polls inventories: content updates
        -- arrive through events, and these only govern how often the renderer
        -- checks who is standing nearby.
        renderDistance = 30.0,      -- metres; a gun on a back is not readable past this
        maxRenderedPlayers = 12,    -- hard cap so a crowded street cannot balloon
        streamInterval = 1500,      -- ms between attach/detach sweeps
        publishInterval = 500,      -- ms between checks of our own weapon/vehicle state
        refreshDebounce = 250,      -- ms to coalesce a burst of inventory updates
        tuner = true,               -- enables /backitemtune (see below)

        hideInVehicle = true,

        -- Clean weapons-only back: only guns show, filling the two prime
        -- upper-back slots. Small utility items never clutter it. To allow
        -- bulky gear (armor/parachute) below the guns, set maxItems = 1.
        maxDisplayed = 2,
        maxWeapons = 2,
        maxItems = 0,
        weaponSlots = { 1, 2 },
        itemSlots = { 3, 4 },

        -- IMPORTANT -- how these offsets actually work.
        --
        -- A spine bone's local axes are NOT world axes. On SKEL_Spine1 (24816):
        --
        --     offset.x  = ALONG the spine   (+ up towards the head)
        --     offset.y  = DEPTH             (- out behind the back)
        --     offset.z  = ACROSS the back   (+/- to either side)
        --
        -- This is what the old values got wrong: they separated the two weapon
        -- slots with x = +0.19 / -0.19 expecting a side-by-side pair, and instead
        -- put one gun 38 cm higher up the spine than the other -- one literally
        -- stacked on top of the other, with the upper one clipping through the
        -- head. Two guns are separated on Z, not on X.
        --
        -- The values below follow the placement every established weapons-on-back
        -- script converges on: bone 24816 (SKEL_Spine1, low enough that upper-body
        -- animation does not swing the gun around), lifted ~0.22 up the spine,
        -- ~0.18 out behind the back, and rotated ~170 on Y so the weapon hangs
        -- muzzle-down along the back rather than standing upright.
        --
        --   TGIANN weapons-on-back : 24816, (0.285, -0.17, 0.13/0.0/-0.13), (0, 170, 0)
        --   qb-weaponsonback       : 24816, (0.2275, -0.16, 0.016),         (0, 0, 1)
        --   OneWithoutAName        : 24816, (0.15, -0.18, -0.02),           (0, 165, 0)
        --
        -- Fine-tune with /backitemtune <slot> in game; it prints a ready-to-paste
        -- line to the F8 console.
        Slots = {
            [1] = { bone = 24816, offset = vector3(0.22, -0.175, 0.105), rotation = vector3(0.0, 170.0, 0.0) },
            [2] = { bone = 24816, offset = vector3(0.22, -0.195, -0.105), rotation = vector3(0.0, 170.0, 0.0) },
            [3] = { bone = 24816, offset = vector3(0.16, -0.135, 0.0), rotation = vector3(0.0, 178.0, 0.0) },
            [4] = { bone = 24816, offset = vector3(0.02, -0.15, 0.155), rotation = vector3(0.0, 100.0, 0.0) },
        },
        MaleSlots = {
            [1] = { bone = 24816, offset = vector3(0.22, -0.175, 0.105), rotation = vector3(0.0, 170.0, 0.0) },
            [2] = { bone = 24816, offset = vector3(0.22, -0.195, -0.105), rotation = vector3(0.0, 170.0, 0.0) },
            [3] = { bone = 24816, offset = vector3(0.16, -0.135, 0.0), rotation = vector3(0.0, 178.0, 0.0) },
            [4] = { bone = 24816, offset = vector3(0.02, -0.15, 0.155), rotation = vector3(0.0, 100.0, 0.0) },
        },
        -- Female peds have a shorter, slimmer torso: the same placement sits a
        -- little lower on the spine and a little closer in on the depth axis.
        FemaleSlots = {
            [1] = { bone = 24816, offset = vector3(0.21, -0.155, 0.10), rotation = vector3(0.0, 170.0, 0.0) },
            [2] = { bone = 24816, offset = vector3(0.21, -0.175, -0.10), rotation = vector3(0.0, 170.0, 0.0) },
            [3] = { bone = 24816, offset = vector3(0.15, -0.12, 0.0), rotation = vector3(0.0, 178.0, 0.0) },
            [4] = { bone = 24816, offset = vector3(0.02, -0.135, 0.145), rotation = vector3(0.0, 100.0, 0.0) },
        },

        -- A micro SMG and a heavy sniper differ by half a metre in length but
        -- share one slot transform, so one rides up at the neck and the other
        -- drags its muzzle through the legs. These corrections are ADDED to the
        -- slot transform, per weapon size class, and are mostly on X -- the
        -- along-the-spine axis -- so a longer gun is carried a little higher.
        -- A per-weapon offset/rotation in the Weapons table below overrides the
        -- slot outright and skips the class entirely.
        --
        -- Melee is the exception: a bat or a machete is slung low and diagonally,
        -- not hung vertically like a rifle, so its correction is large. The
        -- posture is taken from OneWithoutAName's weapons-on-back, which special
        -- cases melee to (-0.40, -0.15, 0.0) / (-75, 185, 92) on the same bone.
        Classes = {
            rifle   = { offset = vector3(0.0, 0.0, 0.0),    rotation = vector3(0.0, 0.0, 0.0) },
            sniper  = { offset = vector3(0.03, 0.0, 0.0),   rotation = vector3(0.0, 0.0, 0.0) },
            shotgun = { offset = vector3(0.01, 0.0, 0.0),   rotation = vector3(0.0, 0.0, 0.0) },
            mg      = { offset = vector3(0.02, 0.0, 0.0),   rotation = vector3(0.0, 0.0, 0.0) },
            heavy   = { offset = vector3(0.04, -0.01, 0.0), rotation = vector3(0.0, 0.0, 0.0) },
            smg     = { offset = vector3(-0.03, 0.0, 0.0),  rotation = vector3(0.0, 0.0, 0.0) },
            pistol  = { offset = vector3(-0.06, 0.02, 0.0), rotation = vector3(0.0, 0.0, 0.0) },
            melee   = { offset = vector3(-0.55, 0.03, 0.0), rotation = vector3(-75.0, 20.0, 92.0) },
        },

        -- Weapons whose real size does not match their engine group. Anything
        -- not listed here is classified automatically from GetWeapontypeGroup.
        ClassOf = {
            weapon_compactrifle = 'smg',
            weapon_sawnoffshotgun = 'smg',
            weapon_dbshotgun = 'smg',
            weapon_machinepistol = 'pistol',
            weapon_minismg = 'pistol',
        },

        -- Higher number = shown first and given the prime (upper-back) slots.
        -- Tiers keep BIG guns on top, then bulky items, then small utility items.
        -- Anything not listed here falls back to a size-based default (big guns
        -- 800 / pistols 500 / melee 300 / bulky items 250 / small items 100).
        Priority = {
            -- Tier 1: big guns (always prioritized on the back)
            weapon_carbinerifle = 1000,
            weapon_assaultrifle = 990,
            weapon_specialcarbine = 985,
            weapon_bullpuprifle = 980,
            weapon_advancedrifle = 975,
            weapon_heavyrifle = 973,
            weapon_militaryrifle = 972,
            weapon_tacticalrifle = 971,
            weapon_heavysniper = 970,
            weapon_sniperrifle = 965,
            weapon_marksmanrifle = 960,
            weapon_precisionrifle = 958,
            weapon_pumpshotgun = 950,
            weapon_assaultshotgun = 945,
            weapon_combatshotgun = 943,
            weapon_heavyshotgun = 941,
            weapon_bullpupshotgun = 940,
            weapon_gusenberg = 935,
            weapon_combatmg = 932,
            weapon_mg = 930,
            weapon_assaultsmg = 925,
            weapon_combatpdw = 920,
            weapon_smg = 915,
            weapon_microsmg = 905,
            -- Tier 2: bulky items
            heavyarmor = 280,
            armor = 275,
            parachute = 270,
            drill = 265,
            police_stormram = 260,
            goldbar = 255,
            -- Tier 3: small utility items (always last on the back)
            thermite = 130,
            trojan_usb = 125,
            radio = 120,
            handcuffs = 110,
            lockpick = 100,
            advancedlockpick = 95,
        },

        blockedWeapons = {
            weapon_unarmed = true,
            weapon_petrolcan = true,
            weapon_fireextinguisher = true,
            weapon_stungun = true,
            weapon_snowball = true,
            weapon_ball = true,
            weapon_smokegrenade = true,
            weapon_flare = true,
            weapon_bzgas = true,
            weapon_molotov = true,
            weapon_stickybomb = true,
            weapon_pipebomb = true,
            weapon_grenade = true,
            weapon_proxmine = true,
        },

        -- Allow-list of weapons that may appear on the back. `model` is no
        -- longer used for weapons (the gun is built from the weapon hash so it
        -- keeps its components and tint) and is kept only as documentation.
        -- `slot` pins an entry to one back slot; `offset` / `rotation` override
        -- the slot transform outright for that one weapon.
        Weapons = {
            weapon_assaultrifle = { model = `w_ar_assaultrifle` },
            weapon_carbinerifle = { model = `w_ar_carbinerifle` },
            weapon_advancedrifle = { model = `w_ar_advancedrifle` },
            weapon_specialcarbine = { model = `w_ar_specialcarbine` },
            weapon_bullpuprifle = { model = `w_ar_bullpuprifle` },
            weapon_heavyrifle = { model = `w_ar_heavyrifle` },
            weapon_militaryrifle = { model = `w_ar_militaryrifle` },
            weapon_tacticalrifle = { model = `w_ar_tacticalrifle` },
            weapon_musket = { model = `w_ar_musket` },
            weapon_compactrifle = { model = `w_ar_assaultrifle_smg` },
            weapon_sniperrifle = { model = `w_sr_sniperrifle` },
            weapon_heavysniper = { model = `w_sr_heavysniper` },
            weapon_marksmanrifle = { model = `w_sr_marksmanrifle` },
            weapon_precisionrifle = { model = `w_sr_precisionrifle` },
            weapon_pumpshotgun = { model = `w_sg_pumpshotgun` },
            weapon_sawnoffshotgun = { model = `w_sg_sawnoff` },
            weapon_bullpupshotgun = { model = `w_sg_bullpupshotgun` },
            weapon_assaultshotgun = { model = `w_sg_assaultshotgun` },
            weapon_combatshotgun = { model = `w_sg_combatshotgun` },
            weapon_heavyshotgun = { model = `w_sg_heavyshotgun` },
            weapon_dbshotgun = { model = `w_sg_doublebarrel` },
            weapon_microsmg = { model = `w_sb_microsmg` },
            weapon_smg = { model = `w_sb_smg` },
            weapon_assaultsmg = { model = `w_sb_assaultsmg` },
            weapon_combatpdw = { model = `w_sb_pdw` },
            weapon_gusenberg = { model = `w_sb_gusenberg` },
            weapon_mg = { model = `w_mg_mg` },
            weapon_combatmg = { model = `w_mg_combatmg` },
            weapon_bat = { model = `w_me_bat`, slot = 2 },
            weapon_crowbar = { model = `w_me_crowbar`, slot = 2 },
            weapon_golfclub = { model = `w_me_gclub`, slot = 2 },
            weapon_machete = { model = `w_me_machette_lr`, slot = 2 },
        },

        -- Non-weapon gear. These stay ordinary props: they have no components
        -- and no tint palette, so there is nothing a weapon object would add.
        Items = {
            radio = { model = `prop_cs_hand_radio`, slot = 4 },
            armor = { model = `prop_armour_pickup`, slot = 3 },
            heavyarmor = { model = `prop_armour_pickup`, slot = 3 },
            handcuffs = { model = `prop_cs_cuffs_01`, slot = 4 },
            binoculars = { model = `prop_binoc_01`, slot = 4 },
            parachute = { model = `p_parachute1_mp_s`, slot = 3, rotation = vector3(0.0, 90.0, 90.0) },
            laptop = { model = `prop_laptop_lester2`, slot = 3 },
            lockpick = { model = `prop_tool_screwdvr03`, slot = 4 },
            advancedlockpick = { model = `prop_tool_screwdvr03`, slot = 4 },
            electronickit = { model = `prop_cs_tablet`, slot = 4 },
            thermite = { model = `hei_prop_heist_thermite`, slot = 4 },
            trojan_usb = { model = `prop_cs_usb_drive`, slot = 4 },
            drill = { model = `hei_prop_heist_drill`, slot = 3 },
            tunerlaptop = { model = `prop_laptop_lester2`, slot = 3 },
            radioscanner = { model = `prop_paper_bag_small`, slot = 4 },
            police_stormram = { model = `prop_batteringram`, slot = 3 },
            goldbar = { model = `hei_prop_heist_gold_bar`, slot = 3 },
            markedbills = { model = `prop_money_bag_01`, slot = 4 },
            cokebaggy = { model = `bkr_prop_meth_smallbag_01a`, slot = 4 },
            weed_brick = { model = `bkr_prop_weed_drying_01a`, slot = 3 },
        },
    },

    -- Equip slots (Gadget/Phone/Backpack/Armor/Radio) and backpack contents live
    -- in reserved slot-number ranges above Config.MaxSlots. They are ordinary
    -- player-inventory slots on the wire — reserving a numeric range is what
    -- makes them real and persistent with no schema change. AddItem() must not
    -- clamp them back into the visible grid, so keep these in sync with
    -- SPECIAL_SLOT_BASE / BACKPACK_CONTENTS_BASE in nui/src/types.ts.
    SpecialSlots = {
        base = 9000,
        count = 6,
        backpackBase = 9100,
        backpackMax = 60,
        -- SIM bays on the phone. A handset needs at least one SIM fitted
        -- before qb-phone will open, and a second bay allows a burner.
        simBase = 9200,
        simCount = 2,
        simItems = { 'sim_card' },
    },

    -- Plate carriers use five readable protection levels. A fresh carrier
    -- includes one plate; loose plates raise it one level at a time. Damage
    -- can partially wear the active plate and that condition persists on the
    -- unique carrier item when it is removed or stored.
    Armor = {
        sockets = 5,
        plateInstallTime = 3500,
        carrierEquipTime = 3000,
        carriers = {
            armor = { capacity = 75, startingDurability = 20 },
            heavyarmor = { capacity = 100, startingDurability = 20 },
        },
    },

    -- Hybrid crafting. Small field recipes remain available from ordinary
    -- inventory; advanced recipes require a validated physical workbench.
    -- Every recipe and access mode is enforced server-side.
    Crafting = {
        QueueSize = 3,
        -- Hard server cap. The UI sizes the batch field from this value, so
        -- three-digit quantities remain centered and readable.
        MaxBatch = 999,
        Personal = {
            key = 'personal',
            label = 'Field Crafting',
            description = 'Quick assembly of simple supplies that needs no installed workbench.',
        },
        -- Merge recipes from the currently active qb-crafting bench into this
        -- inventory's Crafting tab. XP requirements and rewards are retained.
        IntegrateQbCrafting = true,
        QbCraftingResource = 'qb-crafting',
        DifficultySeconds = { easy = 6, medium = 10, hard = 14, expert = 20 },
        Recipes = {
            -- Supplemental utility recipes. Core workbench products live in
            -- qb-crafting so they appear once with one authoritative cost.
            { key = 'cleaningkit', item = 'cleaningkit', amount = 1, category = 'Field Supplies', bench = 'personal', seconds = 6,
              ingredients = { { item = 'plastic', amount = 6 }, { item = 'rubber', amount = 4 } } },

            -- Bench tools
            { key = 'advancedlockpick', item = 'advancedlockpick', amount = 1, category = 'Tools', bench = 'item_bench', seconds = 12,
              ingredients = { { item = 'lockpick', amount = 2 }, { item = 'steel', amount = 5 }, { item = 'electronickit', amount = 1 } } },

            -- Simple medical assembly can be done in the field. Sterile and
            -- specialist equipment still requires the utility workbench.
            { key = 'bandage', item = 'bandage', amount = 2, category = 'Field Medical', bench = 'personal', seconds = 5,
              ingredients = { { item = 'plastic', amount = 2 }, { item = 'rubber', amount = 2 } } },
            { key = 'medkit', item = 'medkit', amount = 1, category = 'Field Medical', bench = 'personal', seconds = 10,
              ingredients = { { item = 'bandage', amount = 4 }, { item = 'plastic', amount = 4 } } },
            { key = 'suturekit', item = 'suturekit', amount = 1, category = 'Medical', bench = 'item_bench', seconds = 9,
              ingredients = { { item = 'bandage', amount = 2 }, { item = 'aluminum', amount = 4 } } },

            -- Constructed gear requires the utility workbench.
            { key = 'backpack_small', item = 'backpack_small', amount = 1, category = 'Gear', bench = 'item_bench', seconds = 12,
              ingredients = { { item = 'rubber', amount = 8 }, { item = 'plastic', amount = 6 }, { item = 'aluminum', amount = 4 } } },
            { key = 'backpack_medium', item = 'backpack_medium', amount = 1, category = 'Gear', bench = 'item_bench', seconds = 20,
              ingredients = { { item = 'backpack_small', amount = 1 }, { item = 'rubber', amount = 10 }, { item = 'steel', amount = 6 } } },
            { key = 'backpack_large', item = 'backpack_large', amount = 1, category = 'Gear', bench = 'item_bench', seconds = 30,
              ingredients = { { item = 'backpack_medium', amount = 1 }, { item = 'steel', amount = 12 }, { item = 'aluminum', amount = 10 } } },
        },
    },

    -- ── Experience / progression ────────────────────────────────────────
    -- Stored on the player's QBCore metadata (`metadata.experience`), so there
    -- is no schema change and it survives across sessions like any other
    -- character data. A category's level is derived from the sum of its
    -- tracks, so working any skill under it advances the parent too.
    Experience = {
        enabled = true,
        maxLevel = 20,
        -- XP to go from level n to n+1 is base * growth^(n-1).
        curve = { base = 120, growth = 1.24 },
        -- Per-track daily ceiling; 0 disables the cap.
        dailyCap = 2500,
        notifyLevelUp = true,

        -- Tracks whose XP and level ladder are owned by another resource. The
        -- inventory mirrors them for display only: it takes their levels from
        -- the owner's own ladder and never awards XP, applies the daily cap or
        -- pays milestone rewards on them. See server/experience.lua.
        externalTracks = {
            farming = { resource = 'lumo-farming', profile = 'GetFarmerProfile', ladder = 'GetExperienceLadder' },
        },

        -- These disciplines intentionally mirror resources enabled by this
        -- server.cfg. Disabled PRP fishing tracks are not advertised; Farming
        -- is lumo-farming's own progression, mirrored via externalTracks.
        categories = {
            { key = 'agriculture', label = 'Agriculture', icon = 'AG', tracks = {
                { key = 'farming', label = 'Farming', icon = 'FM' },
            } },
            { key = 'enterprise', label = 'Enterprise', icon = 'EN', tracks = {
                { key = 'vineyard', label = 'Vineyard', icon = 'VY' },
                { key = 'business', label = 'Business', icon = 'BZ' },
            } },
            { key = 'exploration', label = 'Exploration', icon = 'EX', tracks = {
                { key = 'diving', label = 'Deep Sea Diving', icon = 'DV' },
            } },
            { key = 'outlaw', label = 'Outlaw', icon = 'OL', tracks = {
                { key = 'narcotics', label = 'Narcotics', icon = 'NC' },
                { key = 'robbery', label = 'Street Robbery', icon = 'RB' },
                { key = 'house_robbery', label = 'House Robbery', icon = 'HR' },
                { key = 'heists', label = 'Major Heists', icon = 'HS' },
                { key = 'chopping', label = 'Vehicle Chopping', icon = 'CH' },
                { key = 'racing', label = 'Street Racing', icon = 'SR' },
            } },
            { key = 'trades', label = 'Skilled Trades', icon = 'TR', tracks = {
                { key = 'crafting', label = 'Fabrication', icon = 'CR' },
                { key = 'gunsmithing', label = 'Gunsmithing', icon = 'GS' },
                { key = 'mechanic', label = 'Mechanic', icon = 'MC' },
                { key = 'recycling', label = 'Recycling', icon = 'RC' },
            } },
            { key = 'services', label = 'Public Services', icon = 'SV', tracks = {
                { key = 'police', label = 'Police Service', icon = 'PD' },
                { key = 'ems', label = 'Emergency Medical', icon = 'EM' },
                { key = 'taxi', label = 'Taxi Driver', icon = 'TX' },
                { key = 'towing', label = 'Tow Operator', icon = 'TW' },
                { key = 'sanitation', label = 'Sanitation', icon = 'SN' },
            } },
        },

        -- One-time, per-character import from the retired standalone
        -- player_reputation table. Values are merged into these tracks without
        -- consuming the daily XP cap, then a metadata marker prevents repeats.
        legacyImport = {
            enabled = true,
            marker = 'playerReputationV1',
            tracks = {
                racing = 'racing',
                heists = 'heists',
                mechanic = 'mechanic',
                ems = 'ems',
                police = 'police',
            },
        },

        rankTitles = {
            'Rookie', 'Trainee', 'Runner', 'Operator', 'Journeyman',
            'Specialist', 'Adept', 'Veteran', 'Expert', 'Professional',
            'Master', 'Elite', 'Mythic', 'Legendary', 'Renowned',
            'Distinguished', 'Premier', 'Exalted', 'Icon', 'Pinnacle',
        },

        -- Perks unlocked by track level. Read by other resources through the
        -- GetExperienceLevel export; lumo-inventory itself uses `craftSpeed`.
        perks = {
            house_robbery = {
                { level = 3, label = 'Practiced Search: 10% faster', effect = 'searchSpeed', value = 0.10 },
                { level = 6, label = 'Eye for Electronics: +10% bulky finds', effect = 'bulkyChance', value = 10 },
                { level = 10, label = 'Expert Search: 25% faster', effect = 'searchSpeed', value = 0.25 },
            },
            crafting = {
                { level = 3, label = 'Steady Hands', effect = 'craftSpeed', value = 0.10 },
                { level = 6, label = 'Efficient Bench', effect = 'craftSpeed', value = 0.20 },
                { level = 10, label = 'Master Fabricator', effect = 'craftSpeed', value = 0.35 },
                { level = 15, label = 'Assembly Line', effect = 'craftSpeed', value = 0.50 },
            },
            gunsmithing = {
                { level = 4, label = 'Component Familiarity', effect = 'gunsmithAccess', value = 1 },
                { level = 8, label = 'Precision Machining', effect = 'gunsmithAccess', value = 2 },
                { level = 12, label = 'Advanced Optics', effect = 'gunsmithAccess', value = 3 },
                { level = 16, label = 'Master Armorer', effect = 'gunsmithAccess', value = 4 },
            },
            mechanic = {
                { level = 5, label = 'Efficient Diagnosis', effect = 'mechanicEfficiency', value = 0.05 },
                { level = 10, label = 'Senior Technician', effect = 'mechanicEfficiency', value = 0.10 },
                { level = 15, label = 'Master Technician', effect = 'mechanicEfficiency', value = 0.15 },
            },
            diving = {
                { level = 5, label = 'Experienced Diver', effect = 'divingYield', value = 0.05 },
                { level = 10, label = 'Salvage Expert', effect = 'divingYield', value = 0.10 },
                { level = 15, label = 'Deep Sea Veteran', effect = 'divingYield', value = 0.15 },
            },
        },

        -- Every track pays modest cash milestones; selected tracks substitute
        -- useful RP items at levels 6 and 15. All rewards are one-time claims,
        -- persisted in metadata and validated on the server.
        rewards = {
            defaults = {
                [3] = { type = 'cash', amount = 250, label = '$250 cash' },
                [10] = { type = 'bank', amount = 1000, label = '$1,000 bank deposit' },
                [20] = { type = 'cash', amount = 3500, label = '$3,500 mastery bonus' },
            },
            tracks = {
                vineyard = { [6] = { type = 'item', item = 'grapejuice', amount = 5 }, [15] = { type = 'item', item = 'wine', amount = 4 } },
                business = { [6] = { type = 'item', item = 'radio', amount = 1 }, [15] = { type = 'bank', amount = 1800 } },
                diving = { [6] = { type = 'item', item = 'diving_fill', amount = 2 }, [15] = { type = 'item', item = 'diving_gear', amount = 1 } },
                narcotics = { [6] = { type = 'item', item = 'rolling_paper', amount = 20 }, [15] = { type = 'item', item = 'empty_weed_bag', amount = 20 } },
                house_robbery = { [3] = { type = 'item', item = 'lockpick', amount = 2 }, [6] = { type = 'item', item = 'advancedlockpick', amount = 1 }, [10] = { type = 'bank', amount = 1500 }, [15] = { type = 'item', item = 'advancedlockpick', amount = 3 } },
                robbery = { [6] = { type = 'item', item = 'lockpick', amount = 2 }, [15] = { type = 'item', item = 'advancedlockpick', amount = 1 } },
                heists = { [6] = { type = 'item', item = 'electronickit', amount = 1 }, [15] = { type = 'item', item = 'thermite', amount = 1 } },
                chopping = { [6] = { type = 'item', item = 'repairkit', amount = 1 }, [15] = { type = 'item', item = 'advancedrepairkit', amount = 1 } },
                crafting = { [6] = { type = 'item', item = 'screwdriverset', amount = 1 }, [15] = { type = 'item', item = 'precision_toolkit', amount = 1 } },
                gunsmithing = { [6] = { type = 'item', item = 'flashlight_attachment', amount = 1 }, [15] = { type = 'item', item = 'barrel_attachment', amount = 1 } },
                mechanic = { [6] = { type = 'item', item = 'repairkit', amount = 2 }, [15] = { type = 'item', item = 'advancedrepairkit', amount = 2 } },
                recycling = { [6] = { type = 'item', item = 'metalscrap', amount = 40 }, [15] = { type = 'item', item = 'steel', amount = 30 } },
                taxi = { [6] = { type = 'cash', amount = 600 }, [15] = { type = 'bank', amount = 1800 } },
                towing = { [6] = { type = 'item', item = 'repairkit', amount = 1 }, [15] = { type = 'bank', amount = 1800 } },
                sanitation = { [6] = { type = 'cash', amount = 600 }, [15] = { type = 'item', item = 'cryptostick', amount = 1 } },
            },
        },

        -- Picking an item up is the signal. lumo-inventory sees every item that
        -- enters a player's inventory, so mapping item -> track awards XP for
        -- farming, fishing, mining and so on without touching those resources.
        itemXp = {
            -- Enterprise / exploration
            grape = { track = 'vineyard', xp = 3 },
            grapejuice = { track = 'vineyard', xp = 5 },
            wine = { track = 'vineyard', xp = 7 },
            coral = { track = 'diving', xp = 8 },
            coral_piece = { track = 'diving', xp = 8 },
            goldchain = { track = 'diving', xp = 10 },

            -- Outlaw
            coke = { track = 'narcotics', xp = 5 },
            cutted_coke = { track = 'narcotics', xp = 6 },
            coke_brick = { track = 'narcotics', xp = 25 },
            meth = { track = 'narcotics', xp = 5 },
            markedbills = { track = 'robbery', xp = 10 },
            goldbar = { track = 'heists', xp = 30 },
            diamond = { track = 'heists', xp = 25 },
            rolex = { track = 'robbery', xp = 8 },
            stolen_bag = { track = 'robbery', xp = 10 },

            -- Trades
            metalscrap = { track = 'recycling', xp = 1 },
            iron = { track = 'recycling', xp = 2 },
            copper = { track = 'recycling', xp = 2 },
            steel = { track = 'recycling', xp = 3 },
            aluminum = { track = 'recycling', xp = 2 },
            glass = { track = 'recycling', xp = 1 },
        },

        -- AddItem reasons let one item map differently depending on the actual
        -- resource that produced it (for example marked bills from a drug run
        -- versus a bank robbery). First matching prefix wins.
        reasonXp = {
            { prefix = 'qb-diving:', track = 'diving', xp = 8 },
            { prefix = 'qb-vineyard:', track = 'vineyard', xp = 5 },
            { prefix = 'qb-drugs:', track = 'narcotics', xp = 7 },
            { prefix = 'qb-storerobbery:', track = 'robbery', xp = 10 },
            { prefix = 'qb-houserobbery:', track = 'robbery', xp = 10 },
            { prefix = 'qb-jewellery:', track = 'heists', xp = 14 },
            { prefix = 'qb-bankrobbery:', track = 'heists', xp = 18 },
            { prefix = 'qb-truckrobbery:', track = 'heists', xp = 16 },
            { prefix = 'qb-heist-reward', track = 'heists', xp = 20 },
            -- Lumo OS boosting. Deliberately specific rather than a blanket
            -- 'lumo-boosting' prefix: the same resource also hands over bought
            -- tablets and trade-ins, and buying your own equipment is not a
            -- skill. Only loot pulled out of a contract vehicle counts.
            { prefix = 'lumo-boosting-glovebox', track = 'chopping', xp = 6 },
            { prefix = 'lumo-boosting-market', track = 'chopping', xp = 2 },
            { prefix = 'qb-scrapyard:server:IllegalChop', track = 'chopping', xp = 10 },
            { prefix = 'qb-scrapyard:', track = 'recycling', xp = 4 },
            { prefix = 'qb-recyclejob:', track = 'recycling', xp = 4 },
            { prefix = 'qb-taxi:', track = 'taxi', xp = 8 },
            { prefix = 'qb-tow:', track = 'towing', xp = 8 },
            { prefix = 'qb-garbagejob:', track = 'sanitation', xp = 8 },
        },

        -- XP for actions lumo-inventory performs itself.
        actionXp = {
            craft = { track = 'crafting', xp = 12 },
            gunsmith = { track = 'gunsmithing', xp = 16 },
        },
    },

    StashSize = {
        maxweight = 2000000,
        slots = 100
    },

    DropSize = {
        maxweight = 1000000,
        slots = 50
    },

    -- How close you must stand to a dropped bag for the inventory key to open
    -- it as the right-hand pane. Server-side openDrop still enforces 2.5.
    DropOpenDistance = 2.5,

    Keybinds = {
        Open = 'TAB',
        Hotbar = 'Z',
    },

    CleanupDropTime = 15,    -- in minutes
    CleanupDropInterval = 1, -- in minutes

    -- Kept for compatibility with resources that read the old setting. The
    -- drop system below now chooses a model per item and uses a small paper
    -- bag for everything without an explicit, reliable GTA V prop.
    ItemDropObject = `prop_paper_bag_small`,
    ItemDropObjectBone = 28422,
    ItemDropObjectOffset = {
        vector3(0.260000, 0.040000, 0.000000),
        vector3(90.000000, 0.000000, -78.989998),
    },

    -- New acquisitions may land on the ground; transfers still require room.
    Overflow = { enabled = true, maxDropsPerPlayer = 12 },
    GiveSelection = { distance = 3.0, timeout = 30000, refreshInterval = 250 },

    -- The give hand-off. Both players turn to face each other and the item's
    -- own prop (its DropSystem model) passes from the giver's right hand to
    -- the receiver's. Everyone within broadcastDistance sees it.
    GiveAnimation = {
        enabled = true,
        showProp = true,
        broadcastDistance = 40.0,
        turnTime = 350,   -- ms spent turning to face before the animations start
        giver = { dict = 'mp_common', anim = 'givetake1_a', duration = 2000, flags = 48 },
        receiver = { dict = 'mp_common', anim = 'givetake1_b', duration = 2000, flags = 48 },
        handoffAt = 950,  -- ms after the animations start
        pocketAt = 1750,  -- the receiver puts it away
    },

    DropSystem = {
        enabled = true,
        fallbackModel = 'prop_paper_bag_small',
        interactDistance = 2.5,
        createTimeout = 5000,

        -- One networked object is used for the ground item, carrying and the
        -- throw. There are no decorative duplicates or per-frame proximity
        -- scans; OneSync streaming and qb-target handle visibility/interact.
        throwing = {
            enabled = true,
            -- A hidden stock snowball weapon supplies GTA's native throwable
            -- aim camera, locomotion and release animation. The spawned dummy
            -- projectile is removed and the real item prop takes its place.
            nativeAimWeapon = 'WEAPON_SNOWBALL',
            speed = 13.0,
            upwardBoost = 2.1,
            vehicleVelocityScale = 0.75,
            vehicleCollisionGrace = 650,
            settleTimeout = 8000,
            settleSpeed = 0.22,
            maxServerDistance = 25.0,
            impactDamage = 5,
            impactRadius = 1.15,
            -- End-over-end tumble in rad/s, so a thrown bottle turns over in
            -- the air instead of gliding flat. Sizes and items can override it.
            spin = 6.0,
            -- A stack at least this heavy in total (grams) throws as `heavy`
            -- when its entry does not name a size.
            heavyWeight = 4000,
        },

        placement = {
            enabled = true,
            maxDistance = 8.0,
            alpha = 170,
            rotateStep = 15.0,
            fineRotateStep = 1.0,
            heightStep = 0.05,
            maxHeightAdjust = 2.0,
        },

        animations = {
            pickup = { dict = 'pickup_object', anim = 'pickup_low', duration = 850, releaseAt = 450, flags = 48 },
            place = { dict = 'pickup_object', anim = 'putdown_low', duration = 850, releaseAt = 430, flags = 48 },
            -- Drop from the inventory: the item rides the hand and is let go at releaseAt.
            drop = { dict = 'pickup_object', anim = 'putdown_low', duration = 850, releaseAt = 430, flags = 48 },
            -- Two-handed toss for `bulky` items, which have no aim camera.
            toss = { dict = 'anim@heists@narcotics@trash', anim = 'throw_b', duration = 1200, releaseAt = 650, flags = 48 },
        },

        -- Transform used while the object is in the right hand. Individual
        -- entries can override this when a model needs a different grip.
        hold = {
            bone = 57005, -- SKEL_R_Hand
            offset = vector3(0.12, 0.02, -0.02),
            rotation = vector3(-90.0, 0.0, 15.0),
        },

        -- How an item is carried and thrown. An entry picks one with `size`;
        -- anything unsized is `small`, or `heavy` past throwing.heavyWeight.
        --   small  one hand, native aim camera, full throw
        --   heavy  one hand, native aim camera, a shorter, flatter throw
        --   bulky  both arms (box carry), no sprint/jump, a short two-handed toss
        -- `hold` is the grip for the whole size; an item's own `hold` wins.
        Sizes = {
            small = {},
            heavy = { throw = { speed = 8.5, upwardBoost = 2.4, spin = 2.5 } },
            bulky = {
                carry = 'box',
                anim = { dict = 'anim@heists@box_carry@', anim = 'idle', flags = 49 },
                hold = { bone = 60309, offset = vector3(0.025, 0.08, 0.255), rotation = vector3(-145.0, 290.0, 0.0) },
                throw = { speed = 4.5, upwardBoost = 2.6, spin = 1.2 },
            },
        },

        -- Set true to get /dropgrip while holding an item: it re-attaches the
        -- prop with the offsets you type and prints a ready-made Items line.
        gripTuning = false,

        -- Exact item names win. Models are stock GTA V (server.cfg enforces game
        -- build 3751), except farm produce, which uses lumo-farming's stream.
        --   model    object used on the ground, in the hand and in a give
        --   size     'small' | 'heavy' | 'bulky' (see Sizes)
        --   hold     grip override { bone, offset, rotation }
        --   carry    bulky only: { bone, offset, rotation } while it is carried in
        --            the arms; unset = the two-hand box grip. Easier: carry it
        --            and use /carrygrip, which saves in game for everyone.
        --   throw    { speed, upwardBoost, spin } override
        --   amounts  { { min = n, model = '...' }, ... } a bigger stack, a bigger prop
        -- If this game build lacks a model, the first client to log in reports
        -- it, the server console lists it and that item uses fallbackModel.
        Items = {
            -- Drinks
            water_bottle = { model = 'prop_ld_flow_bottle' },
            water = { model = 'prop_ld_flow_bottle' },
            tap_water = { model = 'prop_ld_flow_bottle' },
            kurkakola = { model = 'prop_ecola_can' },
            cola = { model = 'prop_ecola_can' },
            soda_can = { model = 'prop_ld_can_01' },
            energy_drink = { model = 'prop_energy_drink' },
            coffee = { model = 'p_amb_coffeecup_01' },
            chiya = { model = 'p_ing_coffeecup_01' },
            lassi = { model = 'prop_plastic_cup_02' },
            milk = { model = 'prop_cs_milk_01' },
            lumo_farm_milk = { model = 'prop_cs_milk_01' },
            grapejuice = { model = 'prop_food_bs_juice01' },
            beer = { model = 'prop_amb_beer_bottle' },
            beer_bottle = { model = 'prop_cs_beer_bot_01' },
            wine = { model = 'prop_wine_bot_01' },
            whiskey = { model = 'prop_whiskey_bottle' },
            whiskey_bottle = { model = 'prop_whiskey_bottle' },
            luxury_whiskey = { model = 'p_whiskey_bottle_s' },
            vodka = { model = 'prop_vodka_bottle' },
            vodka_bottle = { model = 'prop_vodka_bottle' },
            moonshine = { model = 'prop_ceramic_jug_cork' },
            moonshine_pack = { model = 'sf_prop_sf_crate_jugs_01a', size = 'bulky' },
            moonshine_still = { model = 'prop_still', size = 'bulky' },

            -- Food
            sandwich = { model = 'prop_sandwich_01' },
            tosti = { model = 'prop_sandwich_01' },
            toast = { model = 'prop_sandwich_01' },
            burger = { model = 'prop_cs_burger_01' },
            hotdog = { model = 'prop_cs_hotdog_01' },
            sausage = { model = 'prop_cs_hotdog_02' },
            taco = { model = 'prop_taco_01' },
            donut = { model = 'prop_amb_donut' },
            fries = { model = 'prop_food_bs_chips' },
            chickenwrap = { model = 'prop_food_cb_bag_01' },
            samosa = { model = 'prop_food_cb_bag_01' },
            sel_roti = { model = 'prop_food_bs_bag_01' },
            sukuti = { model = 'prop_food_bs_bag_01' },
            fish_chips = { model = 'prop_food_bs_tray_02' },
            chicken_meal = { model = 'prop_food_cb_tray_02' },
            pizza = { model = 'prop_pizza_box_02' },
            pizza_slice = { model = 'prop_cs_plate_01' },
            steak = { model = 'prop_cs_steak' },
            meat = { model = 'prop_cs_steak' },
            chicken = { model = 'prop_cs_steak' },
            fish = { model = 'prop_fish_slice_01' },
            luxury_dinner = { model = 'prop_cs_plate_01' },
            omelette = { model = 'prop_cs_plate_01' },
            pasta_plate = { model = 'prop_cs_plate_01' },
            dal_bhat = { model = 'prop_cs_plate_01' },
            momo = { model = 'prop_cs_plate_01' },
            chatamari = { model = 'prop_cs_plate_01' },
            yomari = { model = 'prop_cs_plate_01' },
            tika = { model = 'prop_cs_plate_01' },
            salad = { model = 'prop_cs_bowl_01' },
            thukpa = { model = 'prop_cs_bowl_01' },
            jujudhau = { model = 'prop_cs_bowl_01b' },
            noodles = { model = 'prop_ff_noodle_01' },
            chowmein = { model = 'prop_ff_noodle_01' },
            twerks_candy = { model = 'prop_choc_meto' },
            snikkel_candy = { model = 'prop_choc_ego' },
            laddu = { model = 'prop_candy_pqs' },
            bread = { model = 'v_res_fa_bread01' },
            egg = { model = 'v_ret_247_eggs' },
            -- Farm produce uses lumo-farming's own streamed crop models: single
            -- fruit and veg (apple 9 cm, lettuce 19 cm, watermelon 25 cm), the
            -- same ones that grow on the plots. Wheat and soy only exist as
            -- plants, so they use the smallest stage (a wheat sheaf, a soy plant).
            -- Without lumo-farming running these report missing and use the bag.
            farm_apple = { model = 'prp_farming_apple' },
            farm_banana = { model = 'prp_farming_banana' },
            farm_cucumber = { model = 'prp_farming_cucumber' },
            farm_eggplant = { model = 'prp_farming_eggplant' },
            farm_grape = { model = 'prp_farming_grape' },
            farm_lettuce = { model = 'prp_farming_lettuce' },
            farm_onion = { model = 'prp_farming_onion' },
            farm_potato = { model = 'prp_farming_potato' },
            farm_strawberry = { model = 'prp_farming_strawberry' },
            farm_tomato = { model = 'prp_farming_tomato' },
            farm_watermelon = { model = 'prp_farming_watermelon', size = 'heavy' },
            farm_wheat = { model = 'prp_wheat_stage1' },
            farm_soy = { model = 'pr_soya_stage_1' },
            grape = { model = 'prp_farming_grape' },
            lettuce = { model = 'prp_farming_lettuce' },
            tomato = { model = 'prp_farming_tomato' },
            potato = { model = 'prp_farming_potato' },
            old_fruit = { model = 'prp_farming_apple' },
            -- Dry groceries in a small paper bag.
            flour = { model = 'prop_paper_bag_small' },
            rice = { model = 'prop_paper_bag_small' },
            pasta = { model = 'prop_paper_bag_small' },
            spices = { model = 'prop_paper_bag_small' },
            dough = { model = 'prop_paper_bag_small' },
            cheese = { model = 'prop_paper_bag_small' },
            tea_leaves = { model = 'prop_paper_bag_small' },
            old_corn = { model = 'prop_paper_bag_small' },

            -- Farming
            farm_water_can = { model = 'prop_wateringcan' },
            farm_fertilizer = { model = 'prop_cs_fertilizer' },
            fertilizer = { model = 'prop_cs_fertilizer' },
            weed_nutrition = { model = 'prop_cs_fertilizer' },
            dust = { model = 'prop_cs_fertilizer' },
            plant_spray = { model = 'bkr_prop_weed_spray_01a' },
            farm_soil_scanner = { model = 'w_am_digiscanner' },

            -- Drugs and chemicals
            joint = { model = 'p_cs_joint_01' },
            rolled_weed = { model = 'p_cs_joint_01' },
            rolling_paper = { model = 'ng_proc_cigpak01a' },
            weed_bag = { model = 'bkr_prop_weed_smallbag_01a' },
            empty_weed_bag = { model = 'bkr_prop_weed_smallbag_01a' },
            weed_brick = { model = 'prop_weed_block_01' },
            weed_package = { model = 'prop_weed_block_01' },
            packed_weed = { model = 'prop_weed_block_01' },
            unpacked_weed = { model = 'bkr_prop_weed_bigbag_open_01a' },
            cokebaggy = { model = 'bkr_prop_meth_smallbag_01a' },
            cocaine_bag = { model = 'bkr_prop_meth_smallbag_01a' },
            coke = { model = 'bkr_prop_meth_smallbag_01a' },
            crack_baggy = { model = 'bkr_prop_meth_smallbag_01a' },
            xtcbaggy = { model = 'bkr_prop_meth_smallbag_01a' },
            meth = { model = 'bkr_prop_meth_smallbag_01a' },
            meth_bag = { model = 'bkr_prop_meth_smallbag_01a' },
            cutted_coke = { model = 'bkr_prop_coke_powder_01' },
            unpacked_coke = { model = 'bkr_prop_coke_powder_01' },
            coke_brick = { model = 'bkr_prop_coke_block_01a' },
            coke_small_brick = { model = 'bkr_prop_coke_block_01a' },
            coke_package = { model = 'bkr_prop_coke_block_01a' },
            packed_coke = { model = 'bkr_prop_coke_block_01a' },
            coca_leaf = { model = 'bkr_prop_weed_leaf_01a' },
            coca_paste = { model = 'bkr_prop_coke_plasticbowl_01' },
            coca_blend = { model = 'bkr_prop_coke_plasticbowl_01' },
            crushed_ingredients = { model = 'bkr_prop_coke_metalbowl_02' },
            blended_ingredients = { model = 'bkr_prop_coke_metalbowl_02' },
            cooked_meth = { model = 'bkr_prop_meth_tray_01a' },
            breaked_meth = { model = 'bkr_prop_meth_smashedtray_01' },
            meth_pack = { model = 'bkr_prop_meth_bigbag_01a', size = 'heavy' },
            meth_package = { model = 'bkr_prop_meth_bigbag_01a', size = 'heavy' },
            packed_meth = { model = 'bkr_prop_meth_bigbag_01a', size = 'heavy' },
            powdered_milk = { model = 'bkr_prop_coke_powderedmilk' },
            oxy = { model = 'prop_cs_pills' },
            painkillers = { model = 'prop_cs_pills' },
            painkiller = { model = 'prop_cs_pills' },
            kq_acetone = { model = 'bkr_prop_meth_acetone' },
            kq_ammonia = { model = 'bkr_prop_meth_ammonia' },
            ammonia = { model = 'bkr_prop_meth_ammonia' },
            kq_lithium = { model = 'bkr_prop_meth_lithium' },
            kq_meth_pills = { model = 'bkr_prop_meth_pseudoephedrine' },
            kq_ethanol = { model = 'prop_drug_bottle' },
            hydrochloric_acid = { model = 'bkr_prop_meth_hcacid' },
            sacid = { model = 'bkr_prop_meth_sacid' },
            kq_meth_lab_kit = { model = 'hei_prop_hei_drug_case', size = 'heavy' },
            gasoline = { model = 'w_am_jerrycan', size = 'heavy' },

            -- Money and valuables
            cash = { model = 'prop_cash_pile_01', amounts = {
                { min = 1, model = 'prop_anim_cash_note' },
                { min = 100, model = 'prop_anim_cash_pile_01' },
                { min = 2500, model = 'bkr_prop_money_wrapped_01' },
                { min = 25000, model = 'prop_cash_case_02', size = 'heavy' },
            } },
            money = { model = 'bkr_prop_money_wrapped_01' },
            markedbills = { model = 'prop_money_bag_01', size = 'heavy' },
            moneybag = { model = 'prop_money_bag_01', size = 'heavy' },
            money_sheet = { model = 'bkr_prop_prtmachine_moneypage' },
            cutted_money = { model = 'bkr_prop_prtmachine_moneypage' },
            uncutted_money = { model = 'bkr_prop_prtmachine_moneyream' },
            casinochips = { model = 'vw_prop_chip_100dollar_st' },
            lumo_lifted_wallet = { model = 'prop_ld_wallet_01' },
            stolen_bag = { model = 'prop_ld_purse_01' },
            rolex = { model = 'p_watch_01' },
            gold_watch = { model = 'p_watch_01' },
            goldwatch = { model = 'p_watch_01' },
            luxury_watch = { model = 'p_watch_01' },
            fitbit = { model = 'p_watch_02' },
            diamond_ring = { model = 'v_res_jewelbox' },
            gold_ring = { model = 'v_res_jewelbox' },
            silver_ring = { model = 'v_res_jewelbox' },
            diamond = { model = 'v_res_jewelbox' },
            goldchain = { model = 'p_jewel_necklace01_s' },
            tenkgoldchain = { model = 'p_jewel_necklace01_s' },
            gold_chain = { model = 'p_jewel_necklace01_s' },
            gold_necklace = { model = 'p_jewel_necklace01_s' },
            gold_bracelet = { model = 'p_jewel_necklace02_s' },
            goldbar = { model = 'hei_prop_heist_gold_bar', size = 'heavy' },
            gold_bar = { model = 'hei_prop_heist_gold_bar', size = 'heavy' },
            gold = { model = 'hei_prop_heist_gold_bar', size = 'heavy' },

            -- Cards, keys and paperwork
            id_card = { model = 'p_ld_id_card_01' },
            lawyerpass = { model = 'p_ld_id_card_01' },
            weaponlicense = { model = 'p_ld_id_card_01' },
            mechanicworkcard = { model = 'p_ld_id_card_01' },
            driver_license = { model = 'bkr_prop_fakeid_singledriverl' },
            bank_card = { model = 'prop_cs_credit_card' },
            blank_card = { model = 'prop_cs_credit_card' },
            fake_credit_card = { model = 'prop_cs_credit_card' },
            sim_card = { model = 'prop_cs_credit_card' },
            security_card_01 = { model = 'prop_cs_swipe_card' },
            security_card_02 = { model = 'prop_cs_swipe_card' },
            stolen_tv = { model = 'prop_tv_flat_02b', size = 'bulky' },
            stolen_microwave = { model = 'prop_microwave_1', size = 'bulky' },
            stolen_printer = { model = 'prop_printer_01', size = 'bulky' },
            housekey = { model = 'p_car_keys_01' },
            labkey = { model = 'p_car_keys_01' },
            printerdocument = { model = 'prop_cs_documents_01' },
            certificate = { model = 'prop_cs_documents_01' },
            serviceinvoice = { model = 'prop_cs_documents_01' },
            order_slip = { model = 'prop_notepad_01' },
            stickynote = { model = 'xs_prop_arena_stickynote_01a' },
            lumo_stolen_mail = { model = 'h4_prop_h4_pile_letters_01a' },
            lumo_parcel = { model = 'prop_cs_package_01' },
            sagun = { model = 'prop_cs_package_01' },
            marigold = { model = 'prop_single_rose' },
            delivery_box = { model = 'hei_prop_heist_box', size = 'bulky' },
            lumo_hotgoods = { model = 'prop_cs_cardbox_01', size = 'bulky' },
            electronics = { model = 'prop_cs_cardbox_01', size = 'bulky' },

            -- Phones, radios and electronics
            phone = { model = 'prop_npc_phone_02', hold = { offset = vector3(0.10, 0.02, 0.0), rotation = vector3(-95.0, 0.0, 0.0) } },
            iphone = { model = 'prop_npc_phone_02', hold = { offset = vector3(0.10, 0.02, 0.0), rotation = vector3(-95.0, 0.0, 0.0) } },
            samsungphone = { model = 'prop_npc_phone_02', hold = { offset = vector3(0.10, 0.02, 0.0), rotation = vector3(-95.0, 0.0, 0.0) } },
            smartphone = { model = 'prop_npc_phone_02', hold = { offset = vector3(0.10, 0.02, 0.0), rotation = vector3(-95.0, 0.0, 0.0) } },
            radio = { model = 'prop_cs_hand_radio' },
            pinger = { model = 'prop_cs_hand_radio' },
            tablet = { model = 'prop_cs_tablet' },
            heistpack_tablet = { model = 'prop_cs_tablet' },
            illegal_tablet = { model = 'prop_cs_tablet' },
            electronickit = { model = 'prop_cs_tablet' },
            carplay = { model = 'prop_cs_tablet_02' },
            laptop = { model = 'prop_laptop_lester2' },
            tunerlaptop = { model = 'prop_laptop_lester2' },
            hacking_laptop = { model = 'hei_prop_hst_laptop' },
            radioscanner = { model = 'w_am_digiscanner' },
            signal_scanner = { model = 'w_am_digiscanner' },
            imsi_catcher = { model = 'w_am_digiscanner' },
            diagnostic_scanner = { model = 'w_am_digiscanner' },
            multimeter = { model = 'w_am_digiscanner' },
            trojan_usb = { model = 'hei_prop_hst_usb_drive' },
            cryptostick = { model = 'hei_prop_hst_usb_drive' },
            decryption_key = { model = 'hei_prop_hst_usb_drive' },
            card_reader = { model = 'hei_prop_heist_card_hack' },
            cctv_splicer = { model = 'hei_prop_heist_card_hack' },
            circuit_probe = { model = 'hei_prop_heist_card_hack' },
            weapon_hackingdevice = { model = 'hei_prop_heist_card_hack' },
            atm_skimmer = { model = 'hei_prop_heist_card_hack_02' },
            atm_hack_device = { model = 'hei_prop_heist_card_hack_02' },
            heistpack_drone = { model = 'ba_prop_battle_drone_quad' },
            newscam = { model = 'prop_v_cam_01', size = 'heavy' },
            newsmic = { model = 'p_ing_microphonel_01' },
            newsbmic = { model = 'prop_v_bmike_01', size = 'heavy' },
            binoculars = { model = 'prop_binoc_01' },

            -- Tools and heist gear
            lockpick = { model = 'prop_tool_screwdvr03' },
            advancedlockpick = { model = 'prop_tool_screwdvr03' },
            masterpick = { model = 'prop_tool_screwdvr01' },
            gatecrack = { model = 'prop_tool_screwdvr02' },
            screwdriverset = { model = 'prop_tool_box_04', size = 'heavy' },
            precision_toolkit = { model = 'prop_tool_box_04', size = 'heavy' },
            repairkit = { model = 'prop_tool_box_04', size = 'heavy' },
            advancedrepairkit = { model = 'prop_tool_box_04', size = 'heavy' },
            tirerepairkit = { model = 'prop_tool_box_04', size = 'heavy' },
            veh_toolbox = { model = 'prop_tool_box_04', size = 'heavy' },
            cleaningkit = { model = 'prop_sponge_01' },
            drill = { model = 'hei_prop_heist_drill', size = 'heavy' },
            heistpack_drill = { model = 'hei_prop_heist_drill', size = 'heavy' },
            heistpack_grinder = { model = 'imp_prop_grinder_01a', size = 'heavy' },
            thermite = { model = 'hei_prop_heist_thermite' },
            thermite_charge = { model = 'hei_prop_heist_thermite' },
            heistpack_anchor = { model = 'prop_rope_hook_01' },
            anchor = { model = 'prop_rope_hook_01' },
            heavy_rope = { model = 'prop_cs_heist_rope', size = 'heavy' },
            graffiti_can = { model = 'prop_cs_spray_can' },
            graffiti_remover = { model = 'ng_proc_spraycan01a' },
            lighter = { model = 'p_cs_lighter_01' },
            jerry_can = { model = 'w_am_jerrycan', size = 'heavy' },
            petrolcan = { model = 'w_am_jerrycan', size = 'heavy' },
            nitrous = { model = 'p_s_scuba_tank_s', size = 'heavy' },
            glass = { model = 'prop_cs_glass_scrap' },

            -- Vehicle parts. The big ones are carried in both arms.
            veh_engine = { model = 'prop_car_engine_01', size = 'bulky' },
            veh_transmission = { model = 'imp_prop_impexp_gearbox_01', size = 'bulky' },
            veh_turbo = { model = 'imp_prop_impexp_engine_part_01a', size = 'bulky' },
            veh_wheels = { model = 'prop_wheel_01', size = 'bulky' },
            veh_exterior = { model = 'imp_prop_impexp_car_panel_01a', size = 'bulky' },
            veh_armor = { model = 'imp_prop_impexp_car_door_01a', size = 'bulky' },
            veh_interior = { model = 'prop_car_seat', size = 'bulky' },
            veh_brakes = { model = 'prop_wheel_hub_01', size = 'heavy' },
            veh_suspension = { model = 'prop_wheel_hub_01', size = 'heavy' },
            veh_plates = { model = 'p_num_plate_01' },

            -- Medical
            bandage = { model = 'prop_ld_health_pack' },
            firstaid = { model = 'prop_ld_health_pack' },
            ifaks = { model = 'prop_ld_health_pack' },
            splint = { model = 'prop_ld_health_pack' },
            tourniquet = { model = 'prop_ld_health_pack' },
            suturekit = { model = 'prop_ld_health_pack' },
            medkit = { model = 'xm_prop_x17_bag_med_01a', size = 'heavy' },
            adrenaline = { model = 'prop_syringe_01' },
            walkstick = { model = 'prop_cs_walking_stick' },

            -- Gear and clothing
            armor = { model = 'prop_bodyarmour_03', size = 'heavy' },
            heavyarmor = { model = 'prop_bodyarmour_03', size = 'heavy' },
            armor_plate = { model = 'prop_armour_pickup' },
            backpack_small = { model = 'prop_michael_backpack' },
            backpack_medium = { model = 'p_michael_backpack_s' },
            backpack_large = { model = 'bkr_prop_duffel_bag_01a', size = 'heavy' },
            parachute = { model = 'p_parachute_s', size = 'heavy' },
            diving_gear = { model = 'p_s_scuba_tank_s', size = 'heavy' },
            diving_fill = { model = 'p_d_scuba_tank_s' },
            gasmask = { model = 'p_gasmask_s' },
            handcuffs = { model = 'prop_cs_cuffs_01' },
            clothing_mask = { model = 'prop_michael_balaclava' },
            clothing_jacket = { model = 'prop_ld_shirt_01' },
            clothing_shirt = { model = 'prop_ld_tshirt_01' },
            clothing_vest = { model = 'prop_ld_tshirt_02' },
            clothing_pants = { model = 'prop_ld_jeans_01' },
            clothing_shoes = { model = 'prop_ld_shoe_01' },
            clothing_hat = { model = 'prop_proxy_hat_01' },
            clothing_glasses = { model = 'prop_cs_sol_glasses' },
            clothing_necklace = { model = 'p_jewel_necklace01_s' },
            clothing_earrings = { model = 'p_tmom_earrings_s' },
            clothing_watch = { model = 'p_watch_01' },
            clothing_bracelet = { model = 'p_jewel_necklace02_s' },
            clothing_bag = { model = 'prop_michael_backpack' },

            -- Fireworks and diving finds
            firework1 = { model = 'ind_prop_firework_01' },
            firework2 = { model = 'ind_prop_firework_02' },
            firework3 = { model = 'ind_prop_firework_03' },
            firework4 = { model = 'ind_prop_firework_04' },
            dendrogyra_coral = { model = 'prop_coral_01' },
            antipatharia_coral = { model = 'prop_coral_02' },

            -- Ammunition and weapon parts
            pistol_ammo = { model = 'prop_ld_ammo_pack_01' },
            smg_ammo = { model = 'prop_ld_ammo_pack_01' },
            rifle_ammo = { model = 'prop_ld_ammo_pack_01' },
            shotgun_ammo = { model = 'prop_ld_ammo_pack_01' },
            weapon_ammo = { model = 'prop_box_ammo03a' },
            suppressor_attachment = { model = 'w_at_ar_supp' },
            flashlight_attachment = { model = 'w_at_ar_flsh' },
            grip_attachment = { model = 'w_at_ar_afgrip' },
            clip_attachment = { model = 'w_ar_assaultrifle_mag1' },
            drum_attachment = { model = 'w_ar_assaultrifle_boxmag' },
            barrel_attachment = { model = 'w_at_ar_barrel_1' },
            comp_attachment = { model = 'w_at_pi_comp_1' },
            smallscope_attachment = { model = 'w_at_scope_small' },
            medscope_attachment = { model = 'w_at_scope_medium' },
            largescope_attachment = { model = 'w_at_scope_large' },
            holoscope_attachment = { model = 'w_at_scope_macro' },
            advscope_attachment = { model = 'w_at_scope_max' },
            nvscope_attachment = { model = 'w_at_scope_nv' },
            thermalscope_attachment = { model = 'w_at_scope_max' },
        },

        -- Families of items, tried in order after exact names and weapons, so
        -- new items in a family get a sensible prop without an entry. `match`
        -- is a Lua pattern against the item name; the rest is an Items entry.
        Patterns = {
            { match = '^weapontint_', model = 'prop_paint_spray01a' },
            { match = 'camo_attachment$', model = 'prop_paint_spray01a' },
            { match = '^luxuryfinish_', model = 'prop_paint_spray01a' },
            { match = 'muzzle_brake$', model = 'w_at_muzzle_1' },
            { match = '^seeds_', model = 'prop_paper_bag_small' },
            { match = '_seed$', model = 'prop_paper_bag_small' },
            { match = '^weed_', model = 'bkr_prop_weed_smallbag_01a' },
            { match = '^kq_meth_', model = 'bkr_prop_meth_smallbag_01a' },
            { match = '^clothing_', model = 'prop_ld_tshirt_01' },
            { match = '^heist_paint_', model = 'prop_cs_rolled_paper' },
            { match = '^veh_', model = 'prop_cs_cardbox_01', size = 'bulky' },
            { match = '_ammo$', model = 'prop_ld_ammo_pack_01' },
        },

        -- Weapon inventory names do not equal their object model names. Known
        -- stock weapons use their real pickup/world model; unknown/add-on guns
        -- deliberately fall back to the paper bag until configured above.
        -- A value is a model name, or an Items-style entry to set a size.
        Weapons = {
            weapon_snspistol_mk2 = 'w_pi_sns_pistolmk2',
            weapon_revolver = 'w_pi_revolver',
            weapon_revolver_mk2 = 'w_pi_revolvermk2',
            weapon_doubleaction = 'w_pi_wep1_gun',
            weapon_navyrevolver = 'w_pi_wep2_gun',
            weapon_ceramicpistol = 'w_pi_ceramic_pistol',
            weapon_marksmanpistol = 'w_pi_singleshot',
            weapon_gadgetpistol = 'w_pi_singleshoth4',
            weapon_flaregun = 'w_pi_flaregun',
            weapon_raypistol = 'w_pi_raygun',
            weapon_tecpistol = 'w_pi_pistolsmg_m31',
            weapon_minismg = 'w_sb_minismg',
            weapon_smg_mk2 = 'w_sb_smgmk2',
            weapon_specialcarbine_mk2 = 'w_ar_specialcarbinemk2',
            weapon_bullpuprifle_mk2 = 'w_ar_bullpupriflemk2',
            weapon_militaryrifle = 'w_ar_bullpuprifleh4',
            weapon_heavyrifle = 'w_ar_heavyrifleh',
            weapon_tacticalrifle = 'w_ar_carbinerifle_reh',
            weapon_pumpshotgun_mk2 = 'w_sg_pumpshotgunmk2',
            weapon_autoshotgun = 'w_sg_sweeper',
            weapon_combatshotgun = 'w_sg_pumpshotgunh4',
            weapon_heavysniper_mk2 = { model = 'w_sr_heavysnipermk2', size = 'heavy' },
            weapon_marksmanrifle_mk2 = 'w_sr_marksmanriflemk2',
            weapon_precisionrifle = 'w_sr_precisionrifle_reh',
            weapon_combatmg_mk2 = { model = 'w_mg_combatmgmk2', size = 'heavy' },
            weapon_minigun = { model = 'w_mg_minigun', size = 'heavy' },
            weapon_rpg = { model = 'w_lr_rpg', size = 'heavy' },
            weapon_grenadelauncher = { model = 'w_lr_grenadelauncher', size = 'heavy' },
            weapon_compactlauncher = 'w_lr_compactgl',
            weapon_hominglauncher = { model = 'w_lr_homing', size = 'heavy' },
            weapon_firework = { model = 'w_lr_firework', size = 'heavy' },
            weapon_grenade = 'w_ex_grenadefrag',
            weapon_smokegrenade = 'w_ex_grenadesmoke',
            weapon_bzgas = 'w_ex_grenadesmoke',
            weapon_molotov = 'w_ex_molotov',
            weapon_stickybomb = 'w_ex_pe',
            weapon_pipebomb = 'w_ex_pipebomb',
            weapon_snowball = 'w_ex_snowball',
            weapon_ball = 'w_am_baseball',
            weapon_flare = 'w_am_flare',
            weapon_fireextinguisher = 'w_am_fire_exting',
            weapon_bottle = 'w_me_bottle',
            weapon_dagger = 'w_me_dagger',
            weapon_knuckle = 'w_me_knuckle',
            weapon_switchblade = 'w_me_switchblade',
            weapon_battleaxe = 'w_me_battleaxe',
            weapon_poolcue = 'w_me_poolcue',
            weapon_wrench = 'w_me_wrench',
            weapon_flashlight = 'w_me_flashlight',
            weapon_nightstick = 'w_me_nightstick',
            weapon_stone_hatchet = 'w_me_stonehatchet',
            weapon_candycane = 'w_me_candy_xm3',
            weapon_pistolxm3 = 'w_pi_pistol_xm3',
            weapon_raycarbine = 'w_ar_srifle',
            weapon_rayminigun = { model = 'w_mg_sminigun', size = 'heavy' },
            weapon_railgun = { model = 'w_ar_railgun', size = 'heavy' },
            weapon_railgunxm3 = { model = 'w_ar_railgun_xm3', size = 'heavy' },
            weapon_remotesniper = 'w_sr_sniperrifle',
            weapon_grenadelauncher_smoke = { model = 'w_lr_grenadelauncher', size = 'heavy' },
            weapon_proxmine = 'w_ex_apmine',
            weapon_hazardcan = { model = 'w_ch_jerrycan', size = 'heavy' },
            weapon_digiscanner = 'w_am_digiscanner',
            weapon_briefcase = { model = 'prop_ld_case_01', size = 'heavy' },
            weapon_briefcase_02 = { model = 'prop_security_case_01', size = 'heavy' },
            weapon_garbagebag = 'prop_cs_street_binbag_01',
            weapon_handcuffs = 'prop_cs_cuffs_01',
            weapon_bread = 'v_res_fa_bread01',
            weapon_pistol = 'w_pi_pistol',
            weapon_pistol_mk2 = 'w_pi_pistolmk2',
            weapon_combatpistol = 'w_pi_combatpistol',
            weapon_appistol = 'w_pi_appistol',
            weapon_pistol50 = 'w_pi_pistol50',
            weapon_heavypistol = 'w_pi_heavypistol',
            weapon_snspistol = 'w_pi_sns_pistol',
            weapon_vintagepistol = 'w_pi_vintage_pistol',
            weapon_stungun = 'w_pi_stungun',
            weapon_microsmg = 'w_sb_microsmg',
            weapon_smg = 'w_sb_smg',
            weapon_assaultsmg = 'w_sb_assaultsmg',
            weapon_combatpdw = 'w_sb_pdw',
            weapon_machinepistol = 'w_sb_compactsmg',
            weapon_assaultrifle = 'w_ar_assaultrifle',
            weapon_assaultrifle_mk2 = 'w_ar_assaultriflemk2',
            weapon_carbinerifle = 'w_ar_carbinerifle',
            weapon_carbinerifle_mk2 = 'w_ar_carbineriflemk2',
            weapon_advancedrifle = 'w_ar_advancedrifle',
            weapon_specialcarbine = 'w_ar_specialcarbine',
            weapon_bullpuprifle = 'w_ar_bullpuprifle',
            weapon_musket = 'w_ar_musket',
            weapon_compactrifle = 'w_ar_assaultrifle_smg',
            weapon_pumpshotgun = 'w_sg_pumpshotgun',
            weapon_sawnoffshotgun = 'w_sg_sawnoff',
            weapon_assaultshotgun = 'w_sg_assaultshotgun',
            weapon_bullpupshotgun = 'w_sg_bullpupshotgun',
            weapon_heavyshotgun = 'w_sg_heavyshotgun',
            weapon_dbshotgun = 'w_sg_doublebarrel',
            weapon_sniperrifle = 'w_sr_sniperrifle',
            weapon_heavysniper = 'w_sr_heavysniper',
            weapon_marksmanrifle = 'w_sr_marksmanrifle',
            weapon_mg = { model = 'w_mg_mg', size = 'heavy' },
            weapon_combatmg = { model = 'w_mg_combatmg', size = 'heavy' },
            weapon_gusenberg = 'w_sb_gusenberg',
            weapon_bat = 'w_me_bat',
            weapon_crowbar = 'w_me_crowbar',
            weapon_golfclub = 'w_me_gclub',
            weapon_hammer = 'w_me_hammer',
            weapon_hatchet = 'w_me_hatchet',
            weapon_knife = 'w_me_knife_01',
            weapon_machete = 'w_me_machette_lr',
            weapon_petrolcan = 'w_am_jerrycan',
        },
    },

    VendingObjects = {
        'prop_vend_soda_01',
        'prop_vend_soda_02',
        'prop_vend_water_01',
        'prop_vend_coffe_01',
    },

    VendingItems = {
        { name = 'kurkakola',    price = 4, amount = 50 },
        { name = 'water_bottle', price = 4, amount = 50 },
    },
}

config/shops.lua ​

lua
--- World shops: 24/7, liquor stores, LTD and Ammu-Nation, opened in this
--- inventory's own shop screen (buy by dragging into your pockets).
---
--- On QBCore, qb-shops places shops and opens them here already, so this is
--- for ESX and standalone servers. On ESX it can stand in for esx_shops and
--- esx_weaponshop, or leave them to run as they are.

Config.Shops = {
    --   'auto'  ESX / standalone: our shops wherever the resource a group
    --           replaces (esx_shops, esx_weaponshop) is not running. Stop
    --           that resource (remove its `ensure`) and ours take over; start
    --           it again and they step aside. QBCore / Qbox: off.
    --   'lumo'  always ours. Also stop esx_shops / esx_weaponshop, or both
    --           open at the same counter.
    --   'esx'   never ours: esx_shops / esx_weaponshop do the selling.
    Mode = 'auto',

    -- When the resource a group replaces is installed (even stopped), take
    -- its items, prices and positions from its own config files, so you keep
    -- one shop list. false: always use the Groups below.
    UseESXConfig = true,

    -- Shops whose esx_weaponshop zone is `Legal = true` need this licence
    -- (esx_license). nil: no licence check. ESX's own default is off.
    WeaponLicence = nil,

    -- Sold at every weapon shop on top of its weapons: ammo, the attachment
    -- parts the Attachments screen fits (ESX component names: one `suppressor`
    -- fits every weapon that takes one) and tint kits.
    WeaponExtras = {
        { name = 'pistol_ammo', price = 50 },
        { name = 'smg_ammo', price = 75 },
        { name = 'shotgun_ammo', price = 75 },
        { name = 'rifle_ammo', price = 100 },
        { name = 'snp_ammo', price = 150 },
        { name = 'clip_extended', price = 650 },
        { name = 'flashlight', price = 250 },
        { name = 'suppressor', price = 1250 },
        { name = 'grip', price = 900 },
        { name = 'scope', price = 1500 },
        { name = 'scope_holo', price = 1250 },
        { name = 'scope_small', price = 1450 },
        { name = 'scope_medium', price = 1800 },
        { name = 'weapontint_1', price = 500 },
        { name = 'weapontint_2', price = 2500 },
        { name = 'weapontint_3', price = 500 },
        { name = 'weapontint_4', price = 500 },
        { name = 'weapontint_5', price = 750 },
        { name = 'weapontint_6', price = 500 },
        { name = 'weapontint_7', price = 1500 },
    },

    -- Stock per item in each shop (refills on restart).
    Stock = 100,

    Marker = { type = 2, size = 0.35, color = { 163, 230, 53, 160 }, drawDistance = 12.0, interactDistance = 1.6 },

    -- Used when UseESXConfig finds nothing. `replaces`: the ESX resource this
    -- group stands in for. `weapons`: also sells WeaponExtras.
    Groups = {
        TwentyFourSeven = {
            label = '24/7', replaces = 'esx_shops',
            blip = { sprite = 59, color = 25, scale = 0.7 },
            items = {
                { name = 'burger', price = 15 }, { name = 'water', price = 10 }, { name = 'pizza_ham', price = 25 },
                { name = 'sprunk', price = 12 }, { name = 'phone', price = 250 }, { name = 'lockpick', price = 150 },
            },
            locations = {
                vector3(373.8, 325.8, 103.5), vector3(2557.4, 382.2, 108.6), vector3(-3038.9, 585.9, 7.9),
                vector3(-3241.9, 1001.4, 12.8), vector3(547.4, 2671.7, 42.1), vector3(1961.4, 3740.6, 32.3),
                vector3(2678.9, 3280.6, 55.2), vector3(1729.2, 6414.1, 35.0),
            },
        },
        RobsLiquor = {
            label = "Rob's Liquor", replaces = 'esx_shops',
            blip = { sprite = 93, color = 25, scale = 0.7 },
            items = {
                { name = 'burger', price = 15 }, { name = 'water', price = 10 }, { name = 'wine', price = 35 },
                { name = 'beer', price = 50 }, { name = 'blackberry_ale', price = 65 },
            },
            locations = {
                vector3(1135.8, -982.2, 46.4), vector3(-1222.9, -906.9, 12.3), vector3(-1487.5, -379.1, 40.1),
                vector3(-2968.2, 390.9, 15.0), vector3(1166.0, 2708.9, 38.1), vector3(1392.5, 3604.6, 34.9),
                vector3(127.8, -1284.7, 29.2), vector3(-1393.4, -606.6, 30.3), vector3(-559.9, 287.0, 82.1),
            },
        },
        LTDgasoline = {
            label = 'LTD Gasoline', replaces = 'esx_shops',
            blip = { sprite = 59, color = 25, scale = 0.7 },
            items = {
                { name = 'taco_chicken', price = 15 }, { name = 'water', price = 10 }, { name = 'sandwich', price = 20 },
                { name = 'coffee', price = 8 }, { name = 'repairkit', price = 350 }, { name = 'bandage', price = 45 },
            },
            locations = {
                vector3(-48.5, -1757.5, 29.4), vector3(1163.3, -323.8, 69.2), vector3(-707.5, -914.2, 19.2),
                vector3(-1820.5, 792.5, 138.1), vector3(1698.3, 4924.4, 42.0),
            },
        },
        GunShop = {
            label = 'Ammu-Nation', replaces = 'esx_weaponshop', weapons = true, legal = true,
            blip = { sprite = 110, color = 81, scale = 0.7 },
            items = {
                { name = 'weapon_pistol', price = 500 }, { name = 'weapon_flashlight', price = 70 },
                { name = 'weapon_machete', price = 110 }, { name = 'weapon_nightstick', price = 150 },
                { name = 'weapon_bat', price = 50 }, { name = 'weapon_stungun', price = 60 },
                { name = 'weapon_microsmg', price = 1700 }, { name = 'weapon_assaultrifle', price = 11000 },
                { name = 'weapon_specialcarbine', price = 15000 }, { name = 'weapon_sniperrifle', price = 24000 },
                { name = 'weapon_grenade', price = 700 }, { name = 'weapon_bzgas', price = 200 },
                { name = 'weapon_fireextinguisher', price = 100 }, { name = 'weapon_ball', price = 30 },
                { name = 'weapon_smokegrenade', price = 100 },
            },
            locations = {
                vector3(-662.1, -935.3, 20.8), vector3(810.2, -2157.3, 28.6), vector3(1693.4, 3759.5, 33.7),
                vector3(-330.2, 6083.8, 30.4), vector3(252.3, -50.0, 68.9), vector3(22.0, -1107.2, 28.8),
                vector3(2567.6, 294.3, 107.7), vector3(-1117.5, 2698.6, 17.5), vector3(842.4, -1033.4, 27.1),
            },
        },
    },
}

config/vehicles.lua ​

lua
VehicleStorage = {
    default = {
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 35,
        trunkWeight = 60000
    },
    [0] = { -- Compacts
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 30,
        trunkWeight = 38000
    },
    [1] = { -- Sedans
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 40,
        trunkWeight = 50000
    },
    [2] = { -- SUVs
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 50,
        trunkWeight = 75000
    },
    [3] = { -- Coupes
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 35,
        trunkWeight = 42000
    },
    [4] = { -- Muscle
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 30,
        trunkWeight = 38000
    },
    [5] = { -- Sports Classics
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 25,
        trunkWeight = 30000
    },
    [6] = { -- Sports
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 25,
        trunkWeight = 30000
    },
    [7] = { -- Super
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 25,
        trunkWeight = 30000
    },
    [8] = { -- Motorcycles
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 15,
        trunkWeight = 15000
    },
    [9] = { -- Off-road
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 35,
        trunkWeight = 60000
    },
    [12] = { -- Vans
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 35,
        trunkWeight = 120000
    },
    [13] = { -- Cycles
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 0,
        trunkWeight = 0
    },
    [14] = { -- Boats
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 50,
        trunkWeight = 120000
    },
    [15] = { -- Helicopters
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 50,
        trunkWeight = 120000
    },
    [16] = { -- Planes
        gloveboxSlots = 5,
        gloveboxWeight = 10000,
        trunkSlots = 50,
        trunkWeight = 120000
    },
    [17] = { -- service
        gloveboxSlots = 0,
        gloveboxWeight = 0,
        trunkSlots = 0,
        trunkWeight = 0
    },
    [18] = { -- Emergency
        gloveboxSlots = 4,
        gloveboxWeight = 10000,
        trunkSlots = 12,
        trunkWeight = 150000
    },
    [19] = { -- Military
        gloveboxSlots = 0,
        gloveboxWeight = 0,
        trunkSlots = 0,
        trunkWeight = 0
    },
    [20] = { -- Commercial
        gloveboxSlots = 0,
        gloveboxWeight = 0,
        trunkSlots = 0,
        trunkWeight = 0
    },
    [21] = { -- trains
        gloveboxSlots = 0,
        gloveboxWeight = 0,
        trunkSlots = 0,
        trunkWeight = 0
    },
    [22] = { -- Commercial
        gloveboxSlots = 0,
        gloveboxWeight = 0,
        trunkSlots = 0,
        trunkWeight = 0
    },
}

BackEngineVehicles = {
    [`ninef`] = true,
    [`adder`] = true,
    [`vagner`] = true,
    [`t20`] = true,
    [`infernus`] = true,
    [`zentorno`] = true,
    [`reaper`] = true,
    [`comet2`] = true,
    [`comet3`] = true,
    [`jester`] = true,
    [`jester2`] = true,
    [`cheetah`] = true,
    [`cheetah2`] = true,
    [`prototipo`] = true,
    [`turismor`] = true,
    [`pfister811`] = true,
    [`ardent`] = true,
    [`nero`] = true,
    [`nero2`] = true,
    [`tempesta`] = true,
    [`vacca`] = true,
    [`bullet`] = true,
    [`osiris`] = true,
    [`entityxf`] = true,
    [`turismo2`] = true,
    [`fmj`] = true,
    [`re7b`] = true,
    [`tyrus`] = true,
    [`italigtb`] = true,
    [`italirsx`] = true,
    [`penetrator`] = true,
    [`monroe`] = true,
    [`ninef2`] = true,
    [`stingergt`] = true,
    [`surfer`] = true,
    [`surfer2`] = true,
    [`gp1`] = true,
    [`autarch`] = true,
    [`tyrant`] = true
}

bridge/config.lua ​

lua
--- lumo-inventory — framework settings
---
--- QBCore, Qbox and ESX are detected automatically; with none of them running
--- the inventory runs standalone. Most servers never touch this file. The
--- inventory's own settings live in config/config.lua.
---
--- This folder (bridge/) is the script's built-in framework layer. You do NOT
--- need the separate lumo-bridge resource.

LumoBridgeConfig = {
    --- 'auto' | 'qb' | 'qbx' | 'esx' | 'standalone' | 'custom'
    ---   auto       — Qbox (qbx_core) > QBCore (qb-core) > ESX (es_extended) > Fallback
    Framework = 'auto',

    --- Used by 'auto' when no core is running. 'standalone' = no framework.
    --- Set 'custom' only if you wire your own core in Custom below.
    Fallback = 'standalone',

    --- Print the detected framework and every unresolved call to the console.
    Debug = false,

    --- Resource names, in case yours are renamed.
    Resources = {
        qb  = 'qb-core',
        qbx = 'qbx_core',
        esx = 'es_extended',
        oxlib = 'ox_lib',
    },

    --- How long a server callback waits for its answer before giving up (ms).
    CallbackTimeout = 15000,

    --- Leave 'auto': this resource is the inventory.
    Inventory = 'auto',

    --- Targeting for item drops and vending machines. 'auto' probes: ox_target,
    --- qb-target, qtarget. Without one, drops are picked up with an [E] prompt.
    Target = 'auto',

    ESX = {
        --- How ESX multichar identifiers look. %s = slot, %l = licence hex.
        IdentifierFormat = 'char%s:%l',
        --- true only for an ESX build without per-slot identifiers.
        SingleCharacter = false,
        --- This script is not a character selector; leave false.
        VerifyMulticharAtBoot = false,
    },

    --- Standalone (no framework). A character script that owns characters
    --- (lumo-multicharacter) is used when it runs; without one every player is
    --- one character keyed by their Rockstar licence.
    Standalone = {
        OwnsCharacters = false,             -- keep false in this script
        CharacterResource = 'lumo-multicharacter',
        RememberPosition = false,           -- positions are a spawn script's job
        SetPedModel = false,
        --- With neither a character script nor lumo-spawn, load the inventory
        --- when FiveM's spawnmanager spawns the player.
        AnnounceOnSpawn = true,

        --- There is no money in standalone. With FreeShopping on, inventory
        --- shops and vending machines cost nothing. To charge, plug your
        --- economy in:
        ---   GetMoney    = function(source, account) return number end,
        ---   RemoveMoney = function(source, account, amount, reason) return true/false end,
        ---   AddMoney    = function(source, account, amount, reason) return true/false end,
        --- account is 'cash' or 'bank'. Any hook set here wins over FreeShopping.
        FreeShopping = true,
        Money = {},

        --- Job stashes and job shops need a job. Return
        --- { name = 'police', grade = { level = 2 }, onduty = true } from here
        --- if your server tracks jobs, or leave nil (everyone is a civilian).
        GetJob = nil,
    },

    --- Only for Framework = 'custom'. See docs/CUSTOM-FRAMEWORKS.md.
    Custom = {
        client = {},
        server = {},
        inventory = {},
    },
}