Skip to content

Battle Royale: config files ​

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

config.lua ​

lua
-- ============================================================================
--  Lumo Battle Royale — main config
--  Maps live in config/maps.lua, loot in config/loot.lua.
-- ============================================================================
Config = {}

Config.Debug = false            -- testing: /brdebug (coords + state to F8). Set false for a live server.
Config.Framework = 'auto'       -- 'auto' | 'qbx' | 'qb' | 'esx' | 'standalone'
Config.Target = 'auto'          -- 'auto' | 'qb' | 'ox' | 'none' (none = walk up and press E)

Config.Admin = {
    permissions = { 'admin', 'god' },   -- framework groups allowed to use the admin panel / admin commands
    ace = 'lumo.battleroyale.admin',    -- or grant this ACE
}

-- ── Lobby ───────────────────────────────────────────────────────────────────
Config.Lobby = {
    command = false,                -- players use the hub terminal; set e.g. 'br' to also allow a command
    leaveCommand = 'brleave',       -- leave the queue or the match (the results screen has a button too)
    adminCommand = 'bradmin',       -- admins: open the admin panel anywhere, also inside a match
    minPlayers = 1,                 -- humans needed to start the countdown (bots fill the rest)
    maxPlayers = 32,                -- humans per match
    countdown = 45,                 -- seconds from minPlayers to launch
    fullCountdown = 10,             -- countdown drops to this once maxPlayers are queued
    matchSize = 100,                -- humans + bots per match (bots fill up to this)
    allowConcurrentMatches = true,  -- a new queue can launch while another match is running
}

-- ── Deployment hub (walk-up lobby in the city) ──────────────────────────────
-- A military staging camp: crates, sandbags, floodlights, an instructor, a
-- video wall of real TV props showing the live status board, and a row of
-- desktop computers (deployment terminals) you interact with.
-- Admins can move it: admin panel › "Move hub here" (saved to data/hub.json).
-- Ground Z is found at runtime, so Z can be rough.
--
-- Screens are drawn into the props' own render targets (the same way GTA's TVs
-- work), so the picture sits exactly on the glass. GTA props face their -Y
-- side, so h = 0 turns a screen towards the people standing in the camp.
Config.Hub = {
    coords = vec4(-2126.79, 3268.84, 32.81, 146.16),   -- Fort Zancudo
    blip = {
        sprite = 377, colour = 2, scale = 0.95, label = 'Lumo Royale',   -- 377 = radar_parachute
        shortRange = true,           -- true: minimap shows it only nearby (the pause map always does)
        launchColour = 46,           -- gold + flashing while a queue is counting down
    },
    ped = 's_m_y_marine_01',
    drawDistance = 70.0,             -- props/screens exist only while a player is this close
    -- Offsets are relative to the hub: x = right, y = forward, z = up; h = heading offset.
    props = {
        { model = 'prop_gazebo_02',            x = 0.0,  y = 2.3,  z = 0.0, h = 0 },
        { model = 'prop_mil_crate_01',         x = -3.9, y = 6.6,  z = 0.0, h = 0 },
        { model = 'prop_mil_crate_02',         x = 3.9,  y = 6.6,  z = 0.0, h = 0 },
        { model = 'prop_box_ammo03a',          x = -3.9, y = 6.6,  z = 0.95, h = 15 },
        { model = 'gr_prop_gr_crates_weapon_mix_01a', x = 4.8, y = 3.0, z = 0.0, h = 80 },
        { model = 'prop_drop_armscrate_01',    x = -4.8, y = 3.2,  z = 0.0, h = -70 },
        { model = 'prop_mb_sandblock_01',      x = -4.4, y = -1.0, z = 0.0, h = 90 },
        { model = 'prop_mb_sandblock_01',      x = 4.4,  y = -1.0, z = 0.0, h = -90 },
        { model = 'prop_worklight_03b',        x = -3.6, y = 4.6,  z = 0.0, h = 20 },    -- lamps face the camp, toed in
        { model = 'prop_worklight_03b',        x = 3.6,  y = 4.6,  z = 0.0, h = -20 },
        { model = 'prop_generator_03b',        x = 5.4,  y = 7.4,  z = 0.0, h = 160 },   -- light tower: its lamps face +Y, so 160 aims them at the camp
        { model = 'prop_parachute',            x = 3.6,  y = 0.4,  z = 0.0, h = 30 },
        { model = 'prop_bodyarmour_03',        x = 3.9,  y = 6.6,  z = 0.95, h = 20 },
    },
    -- Big status screen: a real screen prop (solid, lit by the game) with the
    -- board drawn on its glass at full resolution. glass = the share of the
    -- prop's bounding box trimmed off each side to reach the screen area.
    -- If the prop can't load, a drawn billboard of `size` stands in for it.
    wall = {
        x = 0.0, y = 5.6, z = 1.4,                 -- z = bottom edge of the fallback billboard above the ground
        screen = {
            model = 'prop_huge_display_01', target = 'big_disp', lift = 0.0, h = 0,
            glass = { left = 0.058, right = 0.072, top = 0.075, bottom = 0.10 },
        },
        brightness = 225,                          -- 0-255; the picture is unlit, full white is glaring
        size = { w = 5.6, h = 3.15 },              -- fallback billboard, metres
        housing = { margin = 0.16, depth = 0.22, leg = 0.18 },
    },
    -- Deployment terminals: desk + monitor + keyboard + mouse + tower per station.
    -- All monitors share one render target, so they show the same terminal page.
    computers = {
        desk = 'prop_table_03b',
        monitor = { model = 'prop_monitor_w_large', target = 'tvscreen', y = 0.12, h = 0, brightness = 150 },
        extras = {
            { model = 'prop_keyboard_01a', x = 0.0,  y = -0.16, h = 0 },
            { model = 'prop_mouse_01a',    x = 0.30, y = -0.16, h = 0 },
            { model = 'prop_pc_01a',       x = 0.62, y = 0.10,  h = 0, floor = true },
        },
        stations = { { x = -1.2, y = 2.5 }, { x = 0.0, y = 2.5 }, { x = 1.2, y = 2.5 } },
        stand = -0.75,                             -- where the player stands, relative to the desk (y)
        radius = 1.5,
        -- Using a terminal: the camera moves onto the monitor and you click the
        -- lobby on its screen with the mouse. Right click / Esc / Backspace leaves.
        camera = { fov = 38.0, fill = 0.92, blend = 650 },
        anim = { dict = 'anim@heists@prison_heiststation@cop_reactions', clip = 'cop_b_idle' },   -- standing, typing (false = none)   -- fill = share of the view height the screen takes
        screen = { width = 0.94, top = 0.03 },     -- monitor glass: share of the model width, gap from the top (m)
        lobby = { w = 1280, h = 720, brightness = 235 },
    },
    instructor = { x = 2.7, y = 1.6, z = 0.0, h = 180 },
}

-- ── Modes ───────────────────────────────────────────────────────────────────
Config.Modes = {
    solo  = { label = 'Solo',  teamSize = 1 },
    duo   = { label = 'Duo',   teamSize = 2 },
    squad = { label = 'Squad', teamSize = 4 },
}

-- ── Match ───────────────────────────────────────────────────────────────────
Config.Match = {
    bucketBase = 7300,              -- routing buckets 7301, 7302 … (one per match)
    warmup = 30,                    -- seconds of free-fire warm-up at the map's staging area
    warmupWeapon = 'WEAPON_PISTOL',
    endDelay = 15,                  -- seconds on the results screen before everyone is sent home
    maxDuration = 30 * 60,          -- hard stop
    friendlyFire = false,
    knockedBleedout = 30,           -- duo/squad: seconds a downed player lasts
    reviveTime = 6,                 -- seconds holding E to pick a teammate up
    reviveHealth = 30,
    spectate = true,
    spectateBots = true,            -- keep a bots-only match running while an eliminated player is still watching
    teamRadio = true,               -- pma-voice radio channel per team (no-op without pma-voice)
    radioBase = 900,                -- channels 900+ are reserved for match teams
    aiDamage = 0.6,                 -- damage bots deal to players (1.0 = full GTA damage)
}

-- Damage model on top of GTA's own numbers (applied on the victim's client).
Config.Damage = {
    headshot = 1.5,                 -- extra multiplier for head hits
    helmet = { [1] = 0.30, [2] = 0.45, [3] = 0.60 },   -- head damage removed per helmet level
    vest = { [1] = 0.30, [2] = 0.45, [3] = 0.60 },     -- body damage removed per vest level, while it has durability
    vestDurability = 100,           -- durability shown as the armour bar; absorbed damage wears it down
    fall = 0.5,                     -- fall damage multiplier
    -- Damage this client deals with each weapon class (SetWeaponDamageModifier).
    classes = { pistol = 1.0, smg = 0.9, shotgun = 1.0, rifle = 1.0, sniper = 1.4, melee = 1.6, rocket = 1.0 },
}

-- Warm-up practice targets: local bots only you see, respawning, weak aim.
Config.Practice = {
    enabled = true,
    count = 6,
    radius = 45.0,
    accuracy = 8,
    respawn = 4,                    -- seconds
    models = { 's_m_y_marine_03', 'g_m_y_lost_01', 'a_m_y_hiker_01', 's_m_y_blackops_01' },
    weapons = { 'WEAPON_PISTOL', 'WEAPON_MICROSMG', 'WEAPON_PUMPSHOTGUN' },
}

-- HUD helpers that make fights readable.
Config.Hud = {
    gunfire = true,                 -- enemy shots: red ticks on the compass + a gun mark on the minimap and the pause map
    gunfireRange = 250.0,           -- unsilenced shots are heard this far
    -- "Visual sound": what you would hear shows up on the minimap, the pause map and the compass.
    sound = {
        silencedRange = 45.0,       -- silenced shots are heard this close (0 = never)
        footsteps = true,           -- orange footprints for enemies moving near you
        sprintRange = 45.0,
        runRange = 30.0,
        walkRange = 12.0,           -- crouched / stealth movement is silent
        vehicles = true,            -- enemy vehicles with the engine running
        vehicleRange = 140.0,
        blur = 0.08,                -- far marks sit up to this share of the distance off the real spot
        pauseMap = true,            -- false = minimap only
    },
    minimap = true,                 -- keep the minimap on during a match (some city HUDs hide it on foot)
    crosshair = true,               -- small crosshair while aiming (replaces GTA's reticle dot, which some servers hide)
    aimTags = true,                 -- name + distance of the enemy you aim at
    damageDirection = true,         -- red arc showing where hits come from
    teammateMarkers = true,         -- chevrons over teammates + minimap blips
    lootBeams = true,               -- light beam over rare+ loot (visible from 70 m)
    mph = false,                    -- speedometer in MPH instead of KM/H
}

-- City HUD on/off (client). The match has its own HUD, so hunger/thirst/money bars
-- should hide. Every player in a match carries the `lumo_quiet` statebag; HUDs without
-- a switch need the one-line guard in docs/INSTALL.md (qbx_hud, qb-hud, ps-hud).
-- For a HUD that has an export or event, call it here (show = false on entry, true on exit):
Config.CityHud = function(show)
    -- exports['my-hud']:toggleHud(show)
end

-- ── Item images ─────────────────────────────────────────────────────────────
-- Pictures in the loadout, HUD and pickup list. Every item in config/loot.lua has
-- an `image`: a file name (joined to `base`) or a full link, used as is.
-- A picture that is missing or fails to load shows a lettered tile instead.
Config.Images = {
    -- 'auto'  the installed inventory's image folder (lumo-inventory, ox_inventory,
    --         qb / ps / lj / qs-inventory, core_inventory, codem-inventory), else 'local'
    -- 'local' this resource's html/images/ folder: drop your own PNG / WEBP / JPG there
    -- a link  any folder URL ending in '/': 'https://cdn.example.com/items/'
    --         or 'nui://my-inventory/html/images/'
    -- false   lettered tiles only
    base = 'auto',
    -- Per-item pictures without touching config/loot.lua: file name or full link.
    items = {
        -- helmet1 = 'helmet_light.png',
        -- heavysniper = 'https://cdn.example.com/items/heavysniper.webp',
    },
}

-- Loot amount: multiplies every map's POI loot and scatter counts.
Config.LootDensity = 2.2

-- Small caches on a grid over the whole map, so the ground between named places has
-- loot too (spots that land in water are skipped by the client).
Config.LootCaches = {
    spacing = 220.0,                -- metres between grid cells
    chance = 0.7,                   -- share of cells that get a cache
    items = { 2, 4 },               -- items per cache (× LootDensity)
    spread = 14.0,                  -- cache radius
}

-- Visible skydivers: bots leaving the aircraft are shown falling and gliding away
-- (cosmetic, local peds; the real bot takes over near the ground).
Config.Skydivers = {
    enabled = true,
    max = 14,                       -- shown at once
    range = 1100.0,                 -- only drops this close to your camera
}

-- Health model: the ped is kept far above GTA's death threshold so the framework's
-- death/last-stand never fires inside a match. Match HP = ped health - floor.
Config.Health = { max = 800, floor = 700 }

-- ── Aircraft & drop ─────────────────────────────────────────────────────────
Config.Plane = {
    model = 'titan',
    altitude = 600.0,               -- metres above sea level
    speed = 75.0,                   -- m/s
    edge = 1.25,                    -- path starts/ends this many map radii from the centre
    autoOpenAt = 160.0,             -- forced parachute height above ground
}

-- ── Zone ────────────────────────────────────────────────────────────────────
-- radius is a fraction of the map radius. wait = circle shown, shrink = closing time.
Config.Zone = {
    phases = {
        { wait = 120, shrink = 60, radius = 0.62, dps = 1 },
        { wait = 75,  shrink = 50, radius = 0.38, dps = 2 },
        { wait = 60,  shrink = 45, radius = 0.22, dps = 4 },
        { wait = 45,  shrink = 40, radius = 0.12, dps = 6 },
        { wait = 35,  shrink = 35, radius = 0.05, dps = 9 },
        { wait = 25,  shrink = 40, radius = 0.0,  dps = 14 },
    },
    poiBias = 0.7,                  -- chance the next circle centres near a named location (keeps finals on land)
    colour = { r = 60, g = 130, b = 255, a = 70 },
}

-- ── Airdrops ────────────────────────────────────────────────────────────────
Config.Airdrops = {
    enabled = true,
    fromPhase = 2,                  -- first phase that drops one
    perPhase = 1,
    fallTime = 40,                  -- seconds from announcement to landing
    items = 3,                      -- rolls from the 'airdrop' pool
}

-- ── Vehicles ────────────────────────────────────────────────────────────────
Config.Vehicles = {
    enabled = true,
    spawnRadius = 250.0,            -- spawned when a player first comes this close
    models = {
        { model = 'sanchez', type = 'bike' },
        { model = 'bf400',   type = 'bike' },
        { model = 'mesa',    type = 'automobile' },
        { model = 'bodhi2',  type = 'automobile' },
        { model = 'kalahari',type = 'automobile' },
        { model = 'rebel',   type = 'automobile' },
    },
}

-- ── Bots ────────────────────────────────────────────────────────────────────
Config.Bots = {
    enabled = true,
    maxBots = 99,
    fillTeams = true,               -- duo/squad: bot teammates fill empty seats, follow you, ride along and revive you
    materializeRadius = 350.0,      -- a bot becomes a real ped when a player is this close
    dematerializeRadius = 420.0,    -- keep below OneSync's ~424 m culling radius
    simTick = 1000,                 -- ms between server simulation steps
    skirmishRange = 80.0,           -- off-screen bots this close can eliminate each other
    skirmishChance = 0.02,          -- per pair per tick, scaled up as the zone closes
    lootChance = 0.35,              -- chance a real bot detours to nearby loot (unarmed bots always look)
    landWithPistol = 0.08,          -- bots land empty-handed like players; this share happens to have a pistol
    hunterShare = 0.35,             -- share of bots that drift toward the nearest player (so fights find you)
    hunterRange = 700.0,
    chuteRange = 450.0,             -- bots landing this close to a player arrive by visible parachute
    -- Story/ambient models only: freemode peds spawn without clothes unless dressed.
    models = {
        's_m_y_marine_03', 's_m_m_marine_01', 's_m_y_blackops_01', 's_m_y_armymech_01', 'csb_mweather',
        'g_m_y_lost_01', 'g_m_y_mexgoon_02', 'g_m_y_ballasout_01', 'g_m_m_chicold_01', 'g_f_y_vagos_01',
        'a_m_y_hiker_01', 'a_f_y_hiker_01', 'a_m_m_hillbilly_01', 'a_m_y_hipster_02', 's_m_m_chemsec_01',
        'a_m_y_runner_01', 'u_m_y_militarybum',
    },
    -- hearing: reacts to unsilenced gunfire this close. shootRate: GTA shoot rate (100 = default).
    skills = {
        -- reaction: ms between spotting you and opening fire (they aim first). Armour comes from looted vests.
        { label = 'rookie',  weight = 40, accuracy = 22, seeing = 80.0,  hearing = 70.0,  combatAbility = 0, shootRate = 300, reaction = 1400 },
        { label = 'regular', weight = 42, accuracy = 34, seeing = 110.0, hearing = 100.0, combatAbility = 1, shootRate = 450, reaction = 1000 },
        { label = 'veteran', weight = 18, accuracy = 48, seeing = 140.0, hearing = 140.0, combatAbility = 2, shootRate = 650, reaction = 700 },
    },
    accuracyPerPhase = 2,           -- bots sharpen up as the circle closes
    landingGrace = 30,              -- seconds after landing with half accuracy and slower reactions
    healBelow = 55,                 -- a bot with no target patches up under this health %
    -- Called "<prefix><name>" in the kill feed so players can tell them apart.
    tag = '',
    names = {
        'Bishal', 'Aayush', 'Sagun', 'Prabin', 'Rojina', 'Sushant', 'Nirajan', 'Kritika', 'Anish',
        'Ghost', 'Viper', 'Nomad', 'Raven', 'Specter', 'Havoc', 'Bandit', 'Cinder', 'Talon',
        'Yeti', 'Everest', 'Kukri', 'Gorkha', 'Sherpa', 'Tenzing', 'Rhino', 'Monsoon', 'Lhotse',
        'Phantom', 'Saber', 'Drifter', 'Echo', 'Wraith', 'Juno', 'Orbit', 'Pulse', 'Rook',
        'Tundra', 'Vandal', 'Warden', 'Zenith', 'Comet', 'Blaze',
    },
}

-- ── Rewards ─────────────────────────────────────────────────────────────────
-- Cash needs a framework; XP and stats work everywhere.
Config.Rewards = {
    enabled = true,
    account = 'cash',
    participation = 50,
    perKill = 150,                  -- per real player eliminated
    perBotKill = 40,                -- per bot (kept low so farming bots isn't a money printer)
    placement = { [1] = 2500, [2] = 1200, [3] = 800, [5] = 400, [10] = 150 },  -- best matching tier
    items = { [1] = {} },           -- e.g. [1] = { { name = 'goldbar', amount = 1 } }
    xp = { participation = 20, perKill = 40, win = 300, perMinute = 4 },
}

-- ── Keys (players can rebind in Settings › Key Bindings › FiveM) ───────────
Config.Keys = {
    jump = 'F',         -- leave the aircraft
    pickup = 'E',
    inventory = 'TAB',
    heal1 = '7',        -- bandage
    heal2 = '8',        -- first aid / med kit (best you carry)
    heal3 = '9',        -- energy drink
    heal4 = '0',        -- painkillers
    plate = 'G',        -- armour plate
    ping = 'Z',         -- mark a spot for your team
    mark = 'MOUSE_MIDDLE', -- middle click: mark a spot · double click: enemy spotted
    leave = 'BACK',     -- while eliminated/spectating: return to the city
    spectateNext = 'RIGHT',
    spectatePrev = 'LEFT',
}

config/loot.lua ​

lua
-- ============================================================================
--  Match loot. None of this touches the framework inventory: it is a separate
--  loadout that exists only inside the match and is wiped when you leave.
--
--  type    weapon | ammo | heal | boost | plate | armor | helmet | backpack | throw | attach
--  slot    weapons: primary (2 slots) | side | melee
--  ammo    pistol | smg | rifle | shotgun | sniper | rocket
--  class   damage class for Config.Damage.classes
--  tier    1 common · 2 uncommon · 3 rare · 4 epic · 5 legendary
--  image   file name in the image folder (Config.Images.base) or a full https:// / nui:// link;
--          Config.Images.items can override any item without editing this file
-- ============================================================================
Loot = {}

Loot.tiers = {
    { label = 'Common',    color = '#c9d1c4' },
    { label = 'Uncommon',  color = '#7dff8a' },
    { label = 'Rare',      color = '#4fb4ff' },
    { label = 'Epic',      color = '#c77dff' },
    { label = 'Legendary', color = '#ffb53a' },
}

local function W(slot, weapon, ammo, class, tier, label)
    return { type = 'weapon', slot = slot, weapon = weapon, ammo = ammo, class = class, tier = tier, label = label, image = weapon:lower() .. '.png' }
end

Loot.items = {
    -- Sidearms
    pistol          = W('side', 'WEAPON_PISTOL', 'pistol', 'pistol', 1, 'Pistol'),
    combatpistol    = W('side', 'WEAPON_COMBATPISTOL', 'pistol', 'pistol', 2, 'Combat Pistol'),
    heavypistol     = W('side', 'WEAPON_HEAVYPISTOL', 'pistol', 'pistol', 3, 'Heavy Pistol'),
    revolver        = W('side', 'WEAPON_REVOLVER', 'pistol', 'pistol', 4, 'Heavy Revolver'),
    -- SMGs
    microsmg        = W('primary', 'WEAPON_MICROSMG', 'smg', 'smg', 1, 'Micro SMG'),
    smg             = W('primary', 'WEAPON_SMG', 'smg', 'smg', 2, 'SMG'),
    assaultsmg      = W('primary', 'WEAPON_ASSAULTSMG', 'smg', 'smg', 3, 'Assault SMG'),
    -- Shotguns
    sawnoff         = W('primary', 'WEAPON_SAWNOFFSHOTGUN', 'shotgun', 'shotgun', 1, 'Sawed-Off'),
    pumpshotgun     = W('primary', 'WEAPON_PUMPSHOTGUN', 'shotgun', 'shotgun', 2, 'Pump Shotgun'),
    assaultshotgun  = W('primary', 'WEAPON_ASSAULTSHOTGUN', 'shotgun', 'shotgun', 4, 'Assault Shotgun'),
    -- Rifles
    compactrifle    = W('primary', 'WEAPON_COMPACTRIFLE', 'rifle', 'rifle', 2, 'Compact Rifle'),
    assaultrifle    = W('primary', 'WEAPON_ASSAULTRIFLE', 'rifle', 'rifle', 2, 'Assault Rifle'),
    carbinerifle    = W('primary', 'WEAPON_CARBINERIFLE', 'rifle', 'rifle', 3, 'Carbine Rifle'),
    specialcarbine  = W('primary', 'WEAPON_SPECIALCARBINE', 'rifle', 'rifle', 4, 'Special Carbine'),
    -- Long range
    marksmanrifle   = W('primary', 'WEAPON_MARKSMANRIFLE', 'sniper', 'sniper', 3, 'Marksman Rifle'),
    sniperrifle     = W('primary', 'WEAPON_SNIPERRIFLE', 'sniper', 'sniper', 4, 'Sniper Rifle'),
    heavysniper     = W('primary', 'WEAPON_HEAVYSNIPER', 'sniper', 'sniper', 5, 'Heavy Sniper'),
    rpg             = W('primary', 'WEAPON_RPG', 'rocket', 'rocket', 5, 'RPG'),
    -- Melee
    knife           = W('melee', 'WEAPON_KNIFE', nil, 'melee', 1, 'Knife'),
    bat             = W('melee', 'WEAPON_BAT', nil, 'melee', 1, 'Bat'),
    machete         = W('melee', 'WEAPON_MACHETE', nil, 'melee', 2, 'Machete'),
    -- Throwables (one stack, one kind at a time)
    grenade         = { type = 'throw', weapon = 'WEAPON_GRENADE',      tier = 3, label = 'Grenade',     amount = 2, image = 'weapon_grenade.png' },
    molotov         = { type = 'throw', weapon = 'WEAPON_MOLOTOV',      tier = 2, label = 'Molotov',     amount = 2, image = 'weapon_molotov.png' },
    smoke           = { type = 'throw', weapon = 'WEAPON_SMOKEGRENADE', tier = 1, label = 'Smoke',       amount = 2, image = 'weapon_smokegrenade.png' },
    stickybomb      = { type = 'throw', weapon = 'WEAPON_STICKYBOMB',   tier = 4, label = 'Sticky Bomb', amount = 1, image = 'weapon_stickybomb.png' },
    -- Ammo
    ammo_pistol     = { type = 'ammo', ammo = 'pistol',  amount = 40, tier = 1, label = 'Pistol Ammo',   image = 'pistol_ammo.png' },
    ammo_smg        = { type = 'ammo', ammo = 'smg',     amount = 60, tier = 1, label = 'SMG Ammo',      image = 'smg_ammo.png' },
    ammo_rifle      = { type = 'ammo', ammo = 'rifle',   amount = 50, tier = 1, label = 'Rifle Ammo',    image = 'rifle_ammo.png' },
    ammo_shotgun    = { type = 'ammo', ammo = 'shotgun', amount = 14, tier = 1, label = 'Shells',        image = 'shotgun_ammo.png' },
    ammo_sniper     = { type = 'ammo', ammo = 'sniper',  amount = 12, tier = 2, label = 'Sniper Rounds', image = 'mg_ammo.png' },
    ammo_rocket     = { type = 'ammo', ammo = 'rocket',  amount = 2,  tier = 5, label = 'Rockets',       image = 'weapon_rpg.png' },
    -- Healing. heal = HP restored, cap = will not heal past this, time = seconds to use.
    bandage         = { type = 'heal', heal = 20,  cap = 75,  time = 3, amount = 3, tier = 1, label = 'Bandage',      image = 'bandage.png' },
    firstaid        = { type = 'heal', heal = 75,  cap = 100, time = 5, amount = 1, tier = 2, label = 'First Aid Kit', image = 'firstaid.png' },
    medkit          = { type = 'heal', heal = 100, cap = 100, time = 7, amount = 1, tier = 3, label = 'Med Kit',      image = 'medkit.png' },
    -- Boosts heal over time (regen HP/s for duration s); speed = sprint multiplier while active.
    energy          = { type = 'boost', regen = 1.5, duration = 25, speed = 1.12, time = 2, amount = 2, tier = 1, label = 'Energy Drink', image = 'kurkakola.png' },
    painkillers     = { type = 'boost', regen = 3,   duration = 20, time = 4, amount = 1, tier = 2, label = 'Painkillers',  image = 'painkillers.png' },
    -- Armour plate: restores vest durability (needs a vest).
    plate           = { type = 'plate', armour = 34, time = 2, amount = 2, tier = 2, label = 'Armour Plate', image = 'armor_plate.png' },
    -- Vests (reduce body damage while durable), helmets (reduce head damage), packs (carry limits)
    vest1           = { type = 'armor',  level = 1, tier = 2, label = 'Light Vest',    image = 'armor.png' },
    vest2           = { type = 'armor',  level = 2, tier = 3, label = 'Tactical Vest', image = 'armor.png' },
    vest3           = { type = 'armor',  level = 3, tier = 4, label = 'Heavy Vest',    image = 'heavyarmor.png' },
    helmet1         = { type = 'helmet', level = 1, tier = 2, label = 'Light Helmet' },
    helmet2         = { type = 'helmet', level = 2, tier = 3, label = 'Combat Helmet' },
    helmet3         = { type = 'helmet', level = 3, tier = 4, label = 'Spec-Ops Helmet' },
    bag1            = { type = 'backpack', level = 1, tier = 2, label = 'Small Pack', image = 'backpack_small.png' },
    bag2            = { type = 'backpack', level = 2, tier = 3, label = 'Field Pack', image = 'backpack_medium.png' },
    bag3            = { type = 'backpack', level = 3, tier = 4, label = 'Raid Pack',  image = 'backpack_large.png' },
    -- Attachments: one per kind, auto-fitted to every weapon that takes it.
    att_reddot      = { type = 'attach', kind = 'scope',  level = 1, tier = 2, label = 'Red Dot Sight',   image = 'holoscope_attachment.png' },
    att_scope       = { type = 'attach', kind = 'scope',  level = 2, tier = 3, label = '4x Scope',        image = 'medscope_attachment.png' },
    att_suppressor  = { type = 'attach', kind = 'muzzle', level = 1, tier = 3, label = 'Suppressor',      image = 'suppressor_attachment.png' },
    att_grip        = { type = 'attach', kind = 'grip',   level = 1, tier = 2, label = 'Vertical Grip',   image = 'grip_attachment.png' },
    att_extmag      = { type = 'attach', kind = 'mag',    level = 1, tier = 2, label = 'Extended Mag',    image = 'clip_attachment.png' },
    att_drum        = { type = 'attach', kind = 'mag',    level = 2, tier = 4, label = 'Drum Mag',        image = 'drum_attachment.png' },
    att_flashlight  = { type = 'attach', kind = 'light',  level = 1, tier = 1, label = 'Flashlight',      image = 'flashlight_attachment.png' },
}

-- Which GTA components each attachment level tries, first match wins
-- (checked with DoesWeaponTakeWeaponComponent). Mags are per-weapon:
-- COMPONENT_<NAME>_CLIP_02 / _CLIP_03 are tried automatically.
Loot.components = {
    scope = {
        [1] = { 'COMPONENT_AT_SCOPE_MACRO', 'COMPONENT_AT_SCOPE_MACRO_02', 'COMPONENT_AT_PI_RAIL' },
        [2] = { 'COMPONENT_AT_SCOPE_MEDIUM', 'COMPONENT_AT_SCOPE_SMALL', 'COMPONENT_AT_SCOPE_SMALL_02', 'COMPONENT_AT_SCOPE_MACRO', 'COMPONENT_AT_SCOPE_MACRO_02' },
    },
    muzzle = { [1] = { 'COMPONENT_AT_PI_SUPP', 'COMPONENT_AT_PI_SUPP_02', 'COMPONENT_AT_AR_SUPP', 'COMPONENT_AT_AR_SUPP_02', 'COMPONENT_AT_SR_SUPP', 'COMPONENT_AT_SR_SUPP_03' } },
    grip = { [1] = { 'COMPONENT_AT_AR_AFGRIP' } },
    light = { [1] = { 'COMPONENT_AT_PI_FLSH', 'COMPONENT_AT_AR_FLSH' } },
}

-- Carry limits by backpack level (0 = none).
Loot.capacity = {
    [0] = { ammo = 150, bandage = 8,  firstaid = 2, medkit = 1, energy = 3, painkillers = 2, plate = 3, throw = 3 },
    [1] = { ammo = 210, bandage = 12, firstaid = 3, medkit = 2, energy = 4, painkillers = 3, plate = 5, throw = 4 },
    [2] = { ammo = 280, bandage = 16, firstaid = 4, medkit = 3, energy = 5, painkillers = 4, plate = 7, throw = 5 },
    [3] = { ammo = 360, bandage = 20, firstaid = 5, medkit = 4, energy = 6, painkillers = 5, plate = 9, throw = 6 },
}
Loot.ammoCapScale = { pistol = 1.0, smg = 1.25, rifle = 1.0, shotgun = 0.3, sniper = 0.2, rocket = 0.03 }

-- Ammo handed out with a freshly picked-up weapon.
Loot.startAmmo = { pistol = 36, smg = 60, rifle = 45, shotgun = 12, sniper = 8, rocket = 1 }

-- Weighted pools. A weapon roll also drops a matching ammo stack beside it.
Loot.pools = {
    ground = {
        pistol = 7, combatpistol = 5, heavypistol = 2.5, revolver = 0.8,
        microsmg = 6, smg = 5, assaultsmg = 2,
        sawnoff = 4, pumpshotgun = 5, assaultshotgun = 0.8,
        compactrifle = 4, assaultrifle = 4.5, carbinerifle = 2.5, specialcarbine = 0.9,
        marksmanrifle = 1.5, sniperrifle = 0.7,
        knife = 1.5, bat = 1.5, machete = 1,
        smoke = 2, molotov = 2, grenade = 2, stickybomb = 0.4,
        ammo_pistol = 8, ammo_smg = 8, ammo_rifle = 9, ammo_shotgun = 6, ammo_sniper = 3,
        bandage = 11, firstaid = 5, medkit = 1.5, energy = 7, painkillers = 5, plate = 7,
        vest1 = 5, vest2 = 2.5, vest3 = 0.7,
        helmet1 = 5, helmet2 = 2.5, helmet3 = 0.7,
        bag1 = 4, bag2 = 2, bag3 = 0.6,
        att_reddot = 3, att_scope = 1.5, att_suppressor = 1.5, att_grip = 2.5, att_extmag = 3, att_drum = 0.6, att_flashlight = 2,
    },
    airdrop = {
        heavysniper = 3, rpg = 1.5, specialcarbine = 3, assaultshotgun = 2, revolver = 2,
        vest3 = 3, helmet3 = 3, bag3 = 2, medkit = 3, plate = 2, stickybomb = 2,
        att_scope = 2, att_drum = 2, att_suppressor = 2, ammo_sniper = 2, ammo_rocket = 1,
    },
}

-- Bot loadouts by skill index (Config.Bots.skills). One primary + one sidearm.
Loot.botLoadouts = {
    { primary = { 'microsmg', 'sawnoff', 'pumpshotgun', 'compactrifle' }, side = { 'pistol' } },
    { primary = { 'smg', 'pumpshotgun', 'assaultrifle', 'compactrifle' }, side = { 'pistol', 'combatpistol' } },
    { primary = { 'carbinerifle', 'assaultsmg', 'specialcarbine', 'marksmanrifle' }, side = { 'heavypistol' } },
}

-- World props for loot that is not a weapon (weapons use their own world model).
Loot.props = {
    ammo = 'prop_ld_ammo_pack_01',
    heal = 'prop_ld_health_pack',
    boost = 'prop_cs_pills',
    plate = 'prop_ld_armour',
    armor = 'prop_armour_pickup',
    helmet = 'ba_prop_battle_sports_helmet',
    backpack = 'p_michael_backpack_s',
    attach = 'prop_ld_case_01',
    airdrop = 'prop_drop_armscrate_01',
    chute = 'p_cargo_chute_s',
}
-- Per-item prop overrides.
Loot.itemProps = { energy = 'prop_energy_drink' }

config/maps.lua ​

lua
-- ============================================================================
--  Arenas. Every map here is part of the base game (no download, no licence):
--  the circle, loot and vehicles are placed at runtime, so a map is only a
--  centre, a radius and some named locations.
--
--  center/radius  playable circle (x, y) in metres
--  warmup         staging point for the pre-match warm-up (ground Z found at runtime)
--  pois           named drops: loot clusters there and late circles prefer them
--                 (keeps the final circle on land). loot = item spots in that POI.
--  scatter        extra loot spots spread over the whole map
--  vehicles       vehicles placed near POIs
--  island         'HeistIsland' loads Cayo Perico for match players only
--
--  To add a map: stand at a spot, /brdebug coords (Config.Debug = true) prints it.
-- ============================================================================
Config.Maps = {
    {
        id = 'cayo',
        label = 'Cayo Perico',
        description = 'Private island: airstrip, compound, jungle and docks.',
        center = vec2(4900.0, -5150.0), radius = 1050.0,
        warmup = vec3(4455.0, -4480.0, 4.2),
        island = 'HeistIsland',
        weather = 'EXTRASUNNY', hour = 15,
        vehicleModels = {
            { model = 'winky', type = 'automobile' }, { model = 'manchez2', type = 'bike' },
            { model = 'squaddie', type = 'automobile' }, { model = 'vetir', type = 'automobile' },
        },
        scatter = 120, vehicles = 10,
        pois = {
            { label = 'Airstrip',        coords = vec2(4460.0, -4500.0), r = 110.0, loot = 26 },
            { label = 'El Rubio Compound', coords = vec2(5010.0, -5750.0), r = 120.0, loot = 40 },
            { label = 'Main Dock',       coords = vec2(4900.0, -5180.0), r = 90.0,  loot = 22 },
            { label = 'North Dock',      coords = vec2(5130.0, -4640.0), r = 80.0,  loot = 18 },
            { label = 'Radio Tower',     coords = vec2(5260.0, -5420.0), r = 70.0,  loot = 14 },
            { label = 'Crop Fields',     coords = vec2(5330.0, -5260.0), r = 100.0, loot = 18 },
            { label = 'West Beach',      coords = vec2(4290.0, -4580.0), r = 90.0,  loot = 12 },
            { label = 'Jungle Ruins',    coords = vec2(5150.0, -5050.0), r = 90.0,  loot = 14 },
            { label = 'Drainage Outfall', coords = vec2(5045.0, -5815.0), r = 45.0, loot = 8 },
        },
    },
    {
        id = 'sandy',
        label = 'Grand Senora',
        description = 'Desert open ground: Sandy Shores, airfield, prison and Harmony.',
        center = vec2(1400.0, 3300.0), radius = 1250.0,
        warmup = vec3(1720.0, 3255.0, 41.0),
        weather = 'CLEAR', hour = 17,
        scatter = 140, vehicles = 14,
        pois = {
            { label = 'Sandy Shores',    coords = vec2(1860.0, 3700.0), r = 160.0, loot = 40 },
            { label = 'Sandy Airfield',  coords = vec2(1720.0, 3260.0), r = 120.0, loot = 26 },
            { label = 'Bolingbroke',     coords = vec2(1690.0, 2600.0), r = 150.0, loot = 34 },
            { label = 'Harmony',         coords = vec2(590.0, 2730.0),  r = 110.0, loot = 24 },
            { label = 'Yellow Jack',     coords = vec2(1990.0, 3050.0), r = 60.0,  loot = 10 },
            { label = 'Senora Scrapyard',coords = vec2(2400.0, 3110.0), r = 90.0,  loot = 18 },
            { label = 'Alamo Shore',     coords = vec2(1300.0, 3650.0), r = 100.0, loot = 14 },
        },
    },
    {
        id = 'paleto',
        label = 'Paleto Bay',
        description = 'Small town, forest and the sawmill. Close-quarters fights.',
        center = vec2(-300.0, 6000.0), radius = 850.0,
        warmup = vec3(-560.0, 5330.0, 70.0),
        weather = 'OVERCAST', hour = 12,
        scatter = 90, vehicles = 10,
        pois = {
            { label = 'Paleto Town',     coords = vec2(-150.0, 6380.0), r = 170.0, loot = 44 },
            { label = 'Sawmill',         coords = vec2(-560.0, 5320.0), r = 110.0, loot = 26 },
            { label = 'Bay Care',        coords = vec2(-250.0, 6320.0), r = 60.0,  loot = 12 },
            { label = 'Chicken Farm',    coords = vec2(-80.0, 6220.0),  r = 90.0,  loot = 20 },
            { label = 'Paleto Forest',   coords = vec2(-700.0, 5800.0), r = 150.0, loot = 14 },
        },
    },
    {
        id = 'port',
        label = 'Port of Los Santos',
        description = 'Containers, cranes and warehouses. Tight lines of sight.',
        center = vec2(950.0, -3050.0), radius = 650.0,
        warmup = vec3(850.0, -2950.0, 6.0),
        weather = 'SMOG', hour = 19,
        scatter = 90, vehicles = 8,
        pois = {
            { label = 'Terminal',        coords = vec2(1100.0, -3100.0), r = 140.0, loot = 36 },
            { label = 'Container Yard',  coords = vec2(1000.0, -3220.0), r = 110.0, loot = 30 },
            { label = 'Crane Row',       coords = vec2(800.0, -3000.0),  r = 100.0, loot = 24 },
            { label = 'Elysian Island',  coords = vec2(470.0, -2850.0),  r = 130.0, loot = 28 },
        },
    },
    {
        id = 'zancudo',
        label = 'Fort Zancudo',
        description = 'Military base: hangars, runway and the control tower.',
        center = vec2(-2250.0, 3150.0), radius = 800.0,
        warmup = vec3(-1820.0, 2960.0, 32.8),
        weather = 'CLEAR', hour = 9,
        scatter = 100, vehicles = 10,
        vehicleModels = {
            { model = 'crusader', type = 'automobile' }, { model = 'mesa3', type = 'automobile' },
            { model = 'sanchez', type = 'bike' },
        },
        pois = {
            { label = 'Hangars',         coords = vec2(-1850.0, 3000.0), r = 140.0, loot = 36 },
            { label = 'Control Tower',   coords = vec2(-2360.0, 3250.0), r = 90.0,  loot = 22 },
            { label = 'Runway',          coords = vec2(-2300.0, 3050.0), r = 160.0, loot = 18 },
            { label = 'Barracks',        coords = vec2(-2100.0, 3260.0), r = 100.0, loot = 26 },
            { label = 'Lago Zancudo',    coords = vec2(-2600.0, 2900.0), r = 110.0, loot = 12 },
        },
    },
}