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

# Config

```lua
Config = {}

-- ════════════════════════════════════════════════════════════════════════════════
-- CORE SETTINGS
-- ════════════════════════════════════════════════════════════════════════════════

Config.enableLoot = true            -- Enable zombie loot drops (set to false to disable all loot)
Config.lootMode = "interact"        -- Loot pickup mode:
--   "instant"  = item goes directly into inventory on kill
--   "interact" = search the corpse (progress bar), then opens a stash UI to browse and take items

Config.lootDuration = 2000 -- Base time in ms to search a zombie corpse (only used in "interact" mode)
Config.lootAnims = {       -- Random animation played while looting (one picked at random)
    { dict = 'mini@repair',            clip = 'fixing_a_ped' },
    { dict = 'missarmenian3_gardener', clip = 'idle_a' },
}
Config.lootStashSlots = 5            -- Number of slots in loot stash (only used in "interact" mode)
Config.lootStashWeight = 10000       -- Max weight of loot stash in grams (only used in "interact" mode)

Config.enableJacking = "twowheeler"  -- false = disabled, "all" = any vehicle, "twowheeler" = bikes/quads only
Config.canRagdoll = true             -- Allow zombies to enter ragdoll state

Config.zombiePathing = {       -- Navigation abilities applied to every zombie
    climbOverObstacles = true, -- Vault/climb over low obstacles (fences, low walls) instead of getting stuck on them
    useLadders = true,         -- ALL ladder use: navmesh ladder routes and the scripted system
    -- (zombies taking ladders that players were seen using)
    dropFromHeight = true,     -- Drop down from ledges/heights while chasing
    climbOntoProps = true,     -- Scripted climbing the navmesh can't do: onto low props the target
    -- stands on (dumpsters, crates, ~up to 3m) and over low blockers straight toward the
    -- target instead of running a long detour. false restores "any elevation = safe"
    -- behavior (zombies gather below and claw)
}

Config.ignoreMeleeDamageMult = true  -- Zombies ignore fist/melee weapon damage multiplier, prevents melee from being OP
Config.disableBloodOnGround = true   -- Disable blood on the ground under dead zombies
Config.replaceGasParticle = true     -- Replace gas particle with custom particle
Config.disableWeaponWhip = true      -- Disable weapon whip
Config.zombiesBlips = true           -- Enable blips for zombies on the minimap
Config.playerAffectRadiusBlip = true -- Enable an circle overlay showing the radius in which player is detected by zombies

Config.debug = false                 -- Enable debug mode: on-screen overlay (location, zombie counts, player state, config),
-- ox_lib zone boundary visualization, and verbose console logging
-- (spawn/despawn events, aggro triggers, zone enter/exit)

Config.gameBucket = 0 -- Routing bucket for zombie spawns (must match the bucket your players are in)

Config.serverManagedStatebags = false -- OneSync compatibility. Leave false unless your server.cfg has
-- sv_stateBagStrictMode set to true. In strict mode clients can't write entity state, which breaks zombie
-- tracking and loot; turning this on makes the server write the zombie spawn state instead. Costs
-- extra network traffic per spawn, so only enable it when you actually run sv_stateBagStrictMode true.
-- WARNING: this can be heavy on bigger servers.

Config.disableAmbientPopulation = false -- Disable ambient NPC/vehicle population completly, no vehicle and NPC
-- should spawn. Also mutes GTA's ambient audio bed (distant traffic, honking, sirens) -
-- an empty apocalypse map shouldn't sound like a living city.

-- ════════════════════════════════════════════════════════════════════════════════
-- COMBAT LIMITS
-- Controls how many zombies can attack the player simultaneously
-- ════════════════════════════════════════════════════════════════════════════════

Config.combatLimits = {
    primaryCount = 8,        -- Max zombies performing melee attacks at once
    secondaryCount = 2,      -- Max zombies surrounding/taunting the player
    populationPedCount = 10, -- Max ambient NPCs that may join combat
}

-- ════════════════════════════════════════════════════════════════════════════════
-- CUSTOM MELEE SYSTEM
-- Scripted zombie attacks instead of GTA combat AI (so the undead animation plays on
-- any ped model, with full control over reach and damage).
-- ════════════════════════════════════════════════════════════════════════════════

Config.customMelee = {
    -- Reach & pacing
    range = 1.5,               -- Horizontal hit distance (m)
    coneAngle = 120,           -- Frontal arc (deg) the zombie must face you within to hit. 360 = no cone
    verticalReach = 0.7,       -- Max height above the zombie you can still be hit at (m). Higher props = safe
    requireLineOfSight = true, -- A zombie blocked behind others can't hit through them
    stopDistance = 1.2,        -- Distance kept while chasing (m)
    followSpeed = 2.0,         -- Chase move ratio (1 walk, 2 run, 3 sprint)
    cooldown = 2000,           -- Ms between a zombie's own attacks
    cooldownJitter = 700,      -- Random extra ms (0-this) so a pack doesn't hit in sync

    -- Damage
    damage = 5,                -- Per hit, before the type's unarmedDamageModifier
    damageDelay = 350,         -- Ms into the swing when it connects
    applyToArmorFirst = true,  -- true = armor first then health, false = ignore armor
    maxDamage = 100,           -- Server clamp per relayed hit (anti-cheat)
    maxDamagePerSecond = 10,   -- Cap on melee damage/s a player takes from zombies. 0 = uncapped

    -- Simultaneous attackers
    maxAttackersPerTarget = 8, -- Max zombies hitting one player at once (0 = unlimited)

    -- Animation
    -- Upper-body overlay (flag 48) so chasing never stutters. Random clip per swing;
    -- clips that don't exist are skipped, so listing extras is safe.
    anims = {
        { dict = 'anim@ingame@melee@unarmed@streamed_core_zombie', clip = 'short_0_punch' },
        { dict = 'anim@ingame@melee@unarmed@streamed_core_zombie', clip = 'shove' },
        { dict = 'anim@ingame@melee@unarmed@streamed_core_zombie', clip = 'low_attack_0' },
    },
    animFlag = 48,         -- 48 = upper-body overlay. 0 = full-body (roots the zombie)
    strikeDuration = -1,   -- Clip play time (ms). -1 = full clip, once

    -- Hit feedback (local player)
    feedback = {
        screenEffect = true,               -- Red edge vignette when hurt
        screenEffectColor = { 135, 8, 8 }, -- RGB
        screenEffectIntensity = 0.5,       -- Peak opacity, 0-1
        screenEffectDuration = 450,        -- Fade out (ms)
        camShake = false,                  -- Optional camera shake
        camShakeName = 'SMALL_EXPLOSION_SHAKE',
        camShakeIntensity = 0.15,
    },

    -- Attack shout: a vocalization the instant a zombie swings, so combat feels reactive instead of
    -- just the ambient growl loop. Played from the striking zombie; naturally spread out because each
    -- zombie attacks on its own jittered cooldown.
    attackSound = {
        enabled = true,
        speech = "UNDEAD_WAR_CRY", -- Ped speech context (force-shouted), e.g. UNDEAD_WAR_CRY / UNDEAD_IDLE
        chance = 20,               -- % of swings that shout. Also globally throttled (see minVocalizationGap)
    },

    -- Attack vehicles
    -- Zombies pound the vehicle when the target is in/on one. Damage scales with the
    -- type's unarmedDamageModifier.
    attackVehicles = {
        enabled = true,
        range = 2.0,            -- Reach to the bodywork (m)
        bodyDamage = 25,        -- Body health per hit (0-1000)
        -- deform/deformRadius are SetVehicleDamage's scale, not meters: ~15 shows nothing,
        -- ~100 warps the whole car. Bump deformRadius if dents are too faint.
        deform = 100,           -- Dent depth per hit
        deformRadius = 65,      -- Dent spread per hit
        engineDamage = 12,      -- Engine health per hit (0-1000), kills the engine over time
        occupantDamage = 0,     -- Damage to a seated occupant per hit (0 = safe inside)
        breakWindows = true,
        breakWindowChance = 40, -- % chance per hit to smash the nearest window
    },

    -- Internals (rarely touched)
    tickInterval = 200,       -- Ms between melee updates
    followRefresh = 2000,     -- Ms between re-issuing the chase task when out of range
    validateProximity = true, -- Server drops relayed hits from a zombie nowhere near the victim
    proximityTolerance = 5.0, -- Slack for that check (m)
}

-- ════════════════════════════════════════════════════════════════════════════════
-- SPAWN SETTINGS
-- Night time multiplier and zone archetype system
-- ════════════════════════════════════════════════════════════════════════════════

Config.maxTotalZombies = false -- Server-wide ceiling on living zombies (all players combined),
-- false = no limit. World spawning pauses while the total is at/above the ceiling;
-- danger zone spawns are unaffected. Every zombie is a networked entity the server
-- has to sync - on high-population servers (50+) set this (e.g. 1000-1500) as the
-- main lever against server load.

Config.nightTimeSpawnMultiplier = 1.15 -- Spawn count multiplied by this during night time (stacks with zone multiplier)
Config.nightTime = {                   -- Define night time hours for spawn multiplier (game time, not real time)
    beginHour = 21,
    beginMinute = 0,
    endHour = 5,
    endMinute = 0
}

Config.defaultArchetype = "urban" -- Fallback archetype for GTA zones not listed in Config.zoneMapping
Config.affectDangerZones = false  -- If true, zone archetype multipliers also apply inside danger zones

-- ════════════════════════════════════════════════════════════════════════════════
-- ZONE ARCHETYPES
-- Define spawn profiles by environment type instead of per-zone.
-- Each archetype controls: spawn multiplier, type distribution, loot table,
-- and default zombie model pool (defaultZombieModels).
--
-- NOTE: spawnChances values are WEIGHTS, not percentages - they do NOT need to
-- sum to 100. The system sums all weights and picks proportionally.
-- Example: { ["default"] = 70, ["tank"] = 10 } means 87.5% default, 12.5% tank.
-- ════════════════════════════════════════════════════════════════════════════════

Config.zoneArchetypes = {
    --[[
        Military / Secure Facilities
        Fort Zancudo, Humane Labs, N.O.O.S.E, Bolingbroke Penitentiary.
    ]]
    military = {
        multiplier = 1.4,
        maxZombies = 22,
        spawnChances = {
            ["default"]    = 58,
            ["tank"]       = 10,
            ["juggernaut"] = 10,
            ["exploder"]   = 10,
            ["skeleton"]   = 6,
            ["splitter"]   = 6,
        },
        lootTable = "military",
        defaultZombieModels = { "s_m_y_blackops_02", "s_m_y_blackops_03", "s_m_y_marine_01", "s_m_y_marine_02", "s_m_y_marine_03", "csb_ramp_marine", "csb_mweather" },
    },

    --[[
        Prison / Correctional Facilities
        Bolingbroke Penitentiary.
    ]]
    prison = {
        multiplier = 1.2,
        maxZombies = 18,
        spawnChances = {
            ["default"]  = 82,
            ["tank"]     = 12,
            ["exploder"] = 6,
        },
        lootTable = "default",
        defaultZombieModels = { "s_m_m_prisguard_01", "s_m_y_prismuscl_01", "s_m_y_prisoner_01", "s_m_y_ranger_01", "ig_rashcosvki", "u_m_y_prisoner_01" },
    },

    --[[
        Industrial / Port Zones
        Airports, power stations, ports, container yards.
    ]]
    industrial = {
        multiplier = 1.2,
        maxZombies = 15,
        spawnChances = {
            ["default"]  = 82,
            ["screamer"] = 8,
            ["exploder"] = 6,
            ["tank"]     = 4,
        },
        lootTable = "industrial",
        defaultZombieModels = { "G_M_M_Zombie_01", "G_M_M_Zombie_05", "u_m_y_corpse_01", "ig_gustavo_02", "u_m_y_zombie_01" },
    },

    --[[
        Downtown / City Center
        Pillbox Hill, Legion Square, Mission Row, Downtown.
    ]]
    downtown = {
        multiplier = 1.2,
        maxZombies = 18,
        spawnChances = {
            ["default"]  = 88,
            ["screamer"] = 8,
            ["splitter"] = 2,
            ["tank"]     = 2,
        },
        lootTable = "downtown",
        defaultZombieModels = { "IG_Zombie_DJ_01", "u_f_y_corpse_01", "u_f_m_corpse_01", "u_m_y_corpse_01", "u_m_o_tramp_01", "a_f_m_tramp_01", "a_f_m_trampbeac_01", "a_m_m_tramp_01", "a_m_m_trampbeac_01", "a_m_o_tramp_01", "u_m_y_zombie_01", "g_m_m_zombie_05", "G_M_M_Zombie_01", "u_m_o_filmnoir", "u_m_m_prolsec_01" },
    },

    --[[
        Urban / City Neighborhoods
        Residential and commercial districts across Los Santos.
    ]]
    urban = {
        multiplier = 1.0,
        maxZombies = 15,
        spawnChances = {
            ["default"]  = 86,
            ["screamer"] = 8,
            ["exploder"] = 2,
            ["summoner"] = 2,
            ["tank"]     = 2,
        },
        lootTable = "default",
        defaultZombieModels = { "IG_Zombie_DJ_01", "u_f_y_corpse_01", "u_f_m_corpse_01", "u_m_y_corpse_01", "u_m_o_tramp_01", "a_f_m_tramp_01", "a_f_m_trampbeac_01", "a_m_m_tramp_01", "a_m_m_trampbeac_01", "a_m_o_tramp_01", "u_m_y_zombie_01", "g_m_m_zombie_05", "G_M_M_Zombie_01", "u_m_o_filmnoir", "u_m_m_prolsec_01" },
    },

    --[[
        Suburban / Coastal
        Beach areas, quieter neighborhoods, golf courses, bluffs.
    ]]
    suburban = {
        multiplier = 1.0,
        maxZombies = 12,
        spawnChances = {
            ["default"]  = 76,
            ["deer"]     = 9,
            ["coyote"]   = 8,
            ["screamer"] = 4,
            ["exploder"] = 2,
            ["skeleton"] = 1,
        },
        lootTable = "default",
        defaultZombieModels = { "a_f_y_runner_01", "a_m_y_runner_01", "a_m_y_golfer_01", "a_f_y_golfer_01", "a_m_y_yoga_01", "a_f_y_hipster_01", "a_m_y_hipster_01", "a_f_m_fatwhite_01", "a_m_m_socenlat_01", "a_f_y_bevhills_01", "a_m_y_bevhills_01", "u_f_y_corpse_01", "u_f_m_corpse_01", "u_m_y_corpse_01", "G_M_M_Zombie_01" },
    },

    --[[
        Coastal / Beach
        Vespucci Beach, Del Perro Beach, Procopio Beach.
    ]]
    coastal = {
        multiplier = 1.0,
        maxZombies = 12,
        spawnChances = {
            ["default"]  = 42,
            ["shambler"] = 16,
            ["screamer"] = 4,
            ["coyote"]   = 20,
            ["deer"]     = 18,
        },
        lootTable = "default",
        defaultZombieModels = { "a_m_o_beach_01", "a_m_y_beach_01", "a_m_y_beach_02", "a_f_y_beach_01", "a_f_m_beach_01", "a_f_y_runner_01", "a_m_y_runner_01", "a_m_y_runner_02", "ig_gustavo_02", "u_f_y_corpse_01", "u_m_y_corpse_01" },
    },

    --[[
        Small Town
        Paleto Bay, Sandy Shores, Grapeseed.
    ]]
    small_town = {
        multiplier = 1.0,
        maxZombies = 14,
        spawnChances = {
            ["default"]  = 40,
            ["deer"]     = 20,
            ["boar"]     = 16,
            ["coyote"]   = 12,
            ["screamer"] = 4,
            ["skeleton"] = 3,
            ["trainer"]  = 2,
            ["exploder"] = 3,
        },
        lootTable = "default",
        defaultZombieModels = { "a_m_m_hillbilly_01", "a_m_m_hillbilly_02", "a_m_m_rurmeth_01", "cs_old_man1a", "cs_old_man2", "u_m_o_tramp_01", "u_m_y_corpse_01", "u_m_y_zombie_01", "G_M_M_Zombie_01", "a_f_o_indian_01" },
    },

    --[[
        Rural / Wilderness
        Mountains, forests, deserts, rivers, farmland.
    ]]
    rural = {
        multiplier = 0.8,
        maxZombies = 15,
        spawnChances = {
            -- Deep wilderness is animal country: mostly wildlife, only a few zombies.
            ["default"]  = 18,
            ["deer"]     = 30,
            ["boar"]     = 26,
            ["coyote"]   = 22,
            ["skeleton"] = 2,
            ["trainer"]  = 2,
        },
        lootTable = "rural",
        defaultZombieModels = { "a_f_o_indian_01", "a_m_m_hillbilly_01", "a_m_m_hillbilly_02", "a_m_m_indian_01", "a_m_m_rurmeth_01", "a_m_y_hiker_01", "a_f_y_hiker_01", "cs_old_man1a", "cs_old_man2", "s_m_y_construct_01", "u_m_o_tramp_01", "u_m_y_zombie_01" },
    },
}

-- ════════════════════════════════════════════════════════════════════════════════
-- ZONE → ARCHETYPE MAPPING
-- Assign each GTA zone to an archetype.
-- Unmapped zones fall back to Config.defaultArchetype.
-- ════════════════════════════════════════════════════════════════════════════════

Config.zoneMapping = {
    -- Military / Secure
    ARMYB   = "military", -- Fort Zancudo
    HUMLAB  = "military", -- Humane Labs and Research
    NOOSE   = "military", -- N.O.O.S.E
    ISHeist = "military", -- Cayo Perico

    -- Prison
    JAIL    = "prison", -- Bolingbroke Penitentiary

    -- Industrial / Ports
    AIRP    = "industrial", -- Los Santos Airport
    CYPRE   = "industrial", -- Cypress Flats
    ELYSIAN = "industrial", -- Elysian Island
    PALMPOW = "industrial", -- Palmer-Taylor Power Station
    TERMINA = "industrial", -- Terminal
    ZP_ORT  = "industrial", -- Port of South Los Santos
    ZQ_UAR  = "industrial", -- Davis Quartz

    -- Downtown
    DOWNT   = "downtown", -- Downtown
    DTVINE  = "downtown", -- Downtown Vinewood
    LEGSQU  = "downtown", -- Legion Square
    PBOX    = "downtown", -- Pillbox Hill
    SKID    = "downtown", -- Mission Row
    TEXTI   = "downtown", -- Textile City

    -- Urban
    ALTA    = "urban", -- Alta
    BANNING = "urban", -- Banning
    BURTON  = "urban", -- Burton
    CHAMH   = "urban", -- Chamberlain Hills
    DAVIS   = "urban", -- Davis
    DELSOL  = "urban", -- La Puerta (Del Sol)
    EAST_V  = "urban", -- East Vinewood
    EBURO   = "urban", -- El Burro Heights
    HAWICK  = "urban", -- Hawick
    KOREAT  = "urban", -- Little Seoul
    LMESA   = "urban", -- La Mesa
    LOSPUER = "urban", -- La Puerta
    MURRI   = "urban", -- Murrieta Heights
    RANCHO  = "urban", -- Rancho
    SANAND  = "urban", -- San Andreas
    STAD    = "urban", -- Maze Bank Arena
    STRAW   = "urban", -- Strawberry
    VINE    = "urban", -- Vinewood
    WVINE   = "urban", -- West Vinewood
    MOVIE   = "urban", -- Richards Majestic

    -- Suburban
    CHIL    = "suburban", -- Vinewood Hills
    CHU     = "suburban", -- Chumash
    DELPE   = "suburban", -- Del Perro
    GOLF    = "suburban", -- GWC and Golfing Society
    HORS    = "suburban", -- Vinewood Racetrack
    LDAM    = "suburban", -- Land Act Dam
    MIRR    = "suburban", -- Mirror Park
    MORN    = "suburban", -- Morningwood
    OCEANA  = "suburban", -- Pacific Ocean
    PBLUFF  = "suburban", -- Pacific Bluffs
    RGLEN   = "suburban", -- Richman Glen
    RICHM   = "suburban", -- Richman
    ROCKF   = "suburban", -- Rockford Hills
    RTRAK   = "suburban", -- Redwood Lights Track
    VCANA   = "suburban", -- Vespucci Canals
    VESP    = "suburban", -- Vespucci

    -- Coastal / Beach
    BEACH   = "coastal", -- Vespucci Beach
    DELBE   = "coastal", -- Del Perro Beach
    PROCOB  = "coastal", -- Procopio Beach

    -- Small Town
    GRAPES  = "small_town", -- Grapeseed
    PALETO  = "small_town", -- Paleto Bay
    SANDY   = "small_town", -- Sandy Shores

    -- Rural / Wilderness
    ALAMO   = "rural", -- Alamo Sea
    BANHAMC = "rural", -- Banham Canyon Dr
    BHAMCA  = "rural", -- Banham Canyon
    BRADP   = "rural", -- Braddock Pass
    BRADT   = "rural", -- Braddock Tunnel
    CALAFB  = "rural", -- Calafia Bridge
    CANNY   = "rural", -- Raton Canyon
    CCREAK  = "rural", -- Cassidy Creek
    CMSW    = "rural", -- Chiliad Mountain
    DESRT   = "rural", -- Grand Senora Desert
    ELGORL  = "rural", -- El Gordo Lighthouse
    GALFISH = "rural", -- Galilee
    GREATC  = "rural", -- Great Chaparral
    HARMO   = "rural", -- Harmony
    LACT    = "rural", -- Land Act Reservoir
    LAGO    = "rural", -- Lago Zancudo
    MTCHIL  = "rural", -- Mount Chiliad
    MTGORDO = "rural", -- Mount Gordo
    MTJOSE  = "rural", -- Mount Josiah
    NCHU    = "rural", -- North Chumash
    PALCOV  = "rural", -- Paleto Cove
    PALFOR  = "rural", -- Paleto Forest
    PALHIGH = "rural", -- Palomino Highlands
    SANCHIA = "rural", -- San Chianski Mountain Range
    SLAB    = "rural", -- Stab City
    TATAMO  = "rural", -- Tataviam Mountains
    TONGVAH = "rural", -- Tongva Hills
    TONGVAV = "rural", -- Tongva Valley
    WINDF   = "rural", -- Ron Alternates Wind Farm
    ZANCUDO = "rural", -- Zancudo River
}

-- ════════════════════════════════════════════════════════════════════════════════
-- THREAD & SPAWN SETTINGS
-- Core performance and spawn mechanic parameters
-- ════════════════════════════════════════════════════════════════════════════════

Config.threadSettings = {
    threadInterval = 2000,      -- Main script loop interval in ms (2s)
    speedVariance = 0.12,       -- Per-zombie random speed spread (here +/-12%) so a horde moves as a ragged
                                -- stream, not one rigid blob. Stable per zombie. 0 = every zombie identical speed
    loseTargetTimeoutJitter = 8000, -- Random extra ms (0-this), per zombie, added to loseTargetTimeout so a
                                    -- horde doesn't all give up chasing on the exact same tick. 0 = no jitter

    spawnPedsAllOverMap = true, -- true  = zombies spawn across the entire map
    -- false = zombies only spawn inside danger zones

    spawnOnRoofs = false,       -- false = don't spawn zombies on rooftops/balconies
    roadNodeMaxDistance = 40.0, -- max distance (m) a spawn may be placed onto a nearby road
    roofSpawnHeight = 4.0,      -- spawns more than this many metres above the player count as roofs (raise for hilly areas)

    replenishInterval = 5000,       -- Zone population check interval in ms (continuous trickle)
    replenishCooldown = 50000,      -- Cooldown in ms after spawning before replenishing again
    initialFillCooldown = 20000,    -- Cooldown in ms before respawning in empty sector (breathing room after clearing)
    replenishThreshold = 0.35,      -- Only replenish when deficit exceeds this fraction of target (0.35 = 35%)
    playerScalingFactor = 0.3,      -- Multi-player boost: target * (1 + factor * sqrt(players - 1))
    maxZombiesCountInSquare = 40,   -- Hard cap on zombies in a single sector regardless of multiplier

    particlesRenderDistance = 70.0, -- Particle effect render distance
    soundsListenDistance = 20.0,    -- Zombie sound hearing distance
    soundInterval = 14,             -- Avg seconds between ONE zombie's idle/combat growls (each zombie is
                                    -- independently staggered + jittered, so they never all shout in unison)
    maxVocalizationsPerScan = 1,    -- Max growls played per ~0.75s scan: a horde murmurs instead of screaming at once
    -- Global throttle across ALL zombie vocalizations (ambient growls, attack shouts AND spawn cries).
    -- Stops a pack from stacking cries on top of each other, which is what sounds harsh with a crowd nearby.
    minVocalizationGap = 1200,      -- Min ms between ANY two zombie shouts (raise = fewer total cries)
    minSameVocalizationGap = 4000,  -- Min ms before the SAME shout can replay (raise = no doubling of one sound)
    unreachableSoundMultiplier = 3.0, -- Zombies that have a target but can't path to it (you're on a wall/ledge)
                                      -- growl this many times LESS often, and switch from war-cry to a low growl
    painSoundCooldown = 5000,       -- Pain sound cooldown per zombie in ms

    trickleSpawnBatchSize = 4,      -- Zombies per batch during trickle spawning
    trickleSpawnDelay = 1500,       -- Delay in ms between trickle spawn batches
    initialFillBatchSize = 8,       -- Zombies per batch during initial fill (empty sector)

    chanceToSpawn = {               -- Fallback spawn weights per type (same system as archetype spawnChances)
        ["default"]  = 75,          -- Used when no zone archetype provides spawnChances
        ["screamer"] = 8,
        ["exploder"] = 6,
        ["splitter"] = 5,
        ["summoner"] = 3,
        ["tank"]     = 3,
    }
}

-- ════════════════════════════════════════════════════════════════════════════════
-- MAP SECTORS
-- The map is divided into rectangular grids. Each grid is split into square
-- sectors of `size` meters. When a player enters a sector, zombies spawn
-- around them. You can define multiple grids (e.g. main island + Cayo Perico).
--
-- height           - Vertical size of each sector (covers bridges, tunnels, etc.)
--
-- Per grid, define the rectangle ONE of two ways:
--   pointA + pointB  - Two OPPOSITE corners of the rectangle (vector3). Easiest:
--                      stand in one corner of the area, copy your coords, then do
--                      the same in the diagonally opposite corner.
--   center + totalSizeX/totalSizeY
--     center         - Center point of the grid (vector3)
--     totalSizeX     - Half-width of the grid in meters (east-west, extends ±X from center)
--     totalSizeY     - Half-length of the grid in meters (north-south, extends ±Y from center)
--
-- Common per-grid fields:
--   size           - Sector size in meters (each grid cell is size × size)
--   onlyPavement   - Only spawn on paved surfaces (roads, sidewalks)
--   dontSpawnInInterior - Prevent spawns inside interiors
--   dontSpawnInWater    - Prevent spawns in water
-- ════════════════════════════════════════════════════════════════════════════════

Config.squares = {
    drawDebug = false, -- true = outline the grid squares in-world (current square red, rest green)
                       -- so you can check the grids cover the area you want
    height = 1000,
    grids = {
        {
            -- Los Santos & Blaine County
            center = vector3(28.8900, 1744.4650, -50),
            totalSizeX = 4550,
            totalSizeY = 5950,
            size = 350,
            onlyPavement = true,
            dontSpawnInInterior = true,
            dontSpawnInWater = true,
        },
        --[[ The same rectangle defined by two opposite corners instead:
        {
            pointA = vector3(-4521.1, -4205.5, -50), -- one corner
            pointB = vector3(4578.9, 7694.5, -50),   -- diagonally opposite corner
            size = 350,
            onlyPavement = true,
            dontSpawnInInterior = true,
            dontSpawnInWater = true,
        },
        ]]
        {
            -- Cayo Perico
            center = vector3(4818.7, -5031.9, 20),
            totalSizeX = 2000,
            totalSizeY = 1200,
            size = 200,
            onlyPavement = false,
            dontSpawnInInterior = true,
            dontSpawnInWater = true,
        }
    }
}

-- ════════════════════════════════════════════════════════════════════════════════
-- DISTANCES
-- Spawn and despawn distance control
-- ════════════════════════════════════════════════════════════════════════════════

Config.distances = {
    spawnDistance = {
        min = 40.0,             -- Minimum spawn distance from player
        max = 140.0,            -- Maximum spawn distance from player
    },
    spawnRadiusIncrement = 5.0, -- Radius increase (in meters) per failed canSpawnZombieAtCoords check
    despawnDistance = 170.0,    -- Zombies further than this from all players get deleted (must be > max spawn)
}

-- ════════════════════════════════════════════════════════════════════════════════
-- DETECTION DISTANCES
-- Global stimulus detection ranges. These are properties of the stimulus
-- (how loud a horn is), not properties of the zombie type.
-- ════════════════════════════════════════════════════════════════════════════════

Config.detectionDistances = {
    -- Movement noise: how far footsteps carry, by gait (sound-based, ignores walls).
    walk             = 0.0,   -- Walking is silent by default (raise to make walking heard)
    run              = 18.0,  -- Jogging
    sprint           = 30.0,  -- Sprinting
    crouchMultiplier = 0.5,   -- Crouching/sneaking shrinks the heard radius (see isPlayerCrouched in editable)
    vehicle          = 40.0,  -- Vehicle with engine running detection
    horn             = 110.0, -- Vehicle horn detection
}

-- ════════════════════════════════════════════════════════════════════════════════
-- DEAD BODY MANAGEMENT
-- Controls how dead zombie bodies are handled
-- ════════════════════════════════════════════════════════════════════════════════

Config.deadBody = {
    deleteTime = 60000,      -- How long dead bodies and their loot persist in ms (60s)
    deleteOnSafeZone = true, -- Delete zombie bodies when they're inside a safe zone
}

-- ════════════════════════════════════════════════════════════════════════════════
-- ZOMBIE TYPE DEFAULTS
-- Base values applied to every type. Types only need to specify fields that
-- differ from these defaults.
-- ════════════════════════════════════════════════════════════════════════════════

Config.typeDefaults = {
    health = 200,
    armor = 0,
    speed = 1.0,
    instantHeadShotKill = false,
    lootTable = "default",
    reactToPlayerDistance = 20.0, -- Range (m) to detect a player and enter combat (also the minimap aggro radius)
    keepChasingDistance = 35.0,   -- Active-pursuit range (m): a zombie within this of its target stays locked on and
                                  -- the give-up timer does NOT run, so close pursuers never bail. Should be >= reactToPlayerDistance
    loseTargetDistance = 70.0,    -- Distance (m) at which the target is forgotten outright (no grace timer)
    loseTargetTimeout = 15000,    -- Ms a zombie keeps chasing AFTER you pull past keepChasingDistance before giving up.
                                  -- The timer resets when you get back within keepChasingDistance. 0 = give up instantly when beyond it
    reactToShots = true,
    reactToSprint = true,
    reactToPlayerInVehicle = true,
    reactToPlayerInVehicleHonk = true,
    wanderInArea = true,
    nightSpeedMultiplier = 1.0, -- Speed multiplied by this during night time (e.g. 1.5 = 50% faster at night)
    unarmedDamageModifier = 1.0, -- Zombie fist damage multiplier (0.5 = half, 2.0 = double, 3.0 = triple, etc.)
    customMelee = true,          -- Scripted melee (false = GTA combat AI; for ranged/fleeing types)
    -- moveStyle: lock movement animation style (applied every frame)
    -- nil/false = no override, "walk" = walking, "jog" = jogging, "fastsprint" = very fast run
}

-- ════════════════════════════════════════════════════════════════════════════════
-- ZOMBIE TYPES
-- Each type defines stats, detection ranges, and special abilities.
-- Fields not listed here use Config.typeDefaults.
--
-- Only include fields you want to change - omitted fields use Config.typeDefaults.
-- Set a detection field to false to disable it entirely (e.g. reactToShots = false).
--
-- All available fields:
--
--   ["example"] = {
--       -- Required
--       model = { "model_name" },            -- Ped model(s) to spawn (random pick)
--
--       -- Base stats (override typeDefaults)
--       health = 200,                         -- Max health
--       armor = 0,                            -- Armor value
--       speed = 1.0,                          -- Movement speed multiplier
--       nightSpeedMultiplier = 1.0,          -- Speed multiplied by this during night time (e.g. 1.5 = 50% faster at night)
--       moveStyle = "walk",                   -- Lock animation: "walk", "jog", "fastsprint" (nil=no override)
--       unarmedDamageModifier = 1.0,          -- Zombie fist damage multiplier (0.5 = half, 2.0 = double, 3.0 = triple, etc.)
--       instantHeadShotKill = false,          -- One-shot headshot kill
--       lootTable = "default",                -- Loot table name (from Config.lootTables, or "default" for zone-based)
--
--       -- Wild animals (living fauna, not undead - they keep GTA's own animal AI)
--       wildlife = true,                      -- Spawn as a normal animal: native relationship group, no zombie behaviour/VFX
--       relationshipGroup = "WILD_ANIMAL",    -- GTA group driving its nature (WILD_ANIMAL/DEER flee, COUGAR/GUARD_DOG hunt)
--
--       -- Detection (override typeDefaults, set to false to disable)
--       -- Detection distances (sprint, gunfire, vehicle, horn, wander) are global - see Config.detectionDistances
--       reactToPlayerDistance = 20.0,         -- Range to detect player on foot and enter combat
--       keepChasingDistance = 35.0,           -- Active-pursuit range: within this the zombie stays locked on (give-up timer paused)
--       loseTargetDistance = 70.0,            -- Distance at which the target is forgotten outright (no timer)
--       loseTargetTimeout = 15000,             -- Ms chasing once you pull past keepChasingDistance before giving up (resets within it; 0 = instant)
--       reactToShots = true,                  -- React to nearby gunshots
--       reactToSprint = true,                 -- React to sprinting players
--       reactToPlayerInVehicle = true,        -- React to vehicles with engine running
--       reactToPlayerInVehicleHonk = true,    -- React to horn
--       wanderInArea = true,                  -- Wander when idle (no target)
--
--       -- Visual
--       damagePacks = { "Fall", "SCR_Shark" }, -- Ped damage overlays applied on spawn
--       damagePackCount = { min = 1, max = 2 }, -- How many random damage packs to apply
--       movementClipset = "clipset_name",     -- Custom movement clipset (overrides model default)
--       strafeClipset = "clipset_name",       -- Custom strafe clipset (overrides model default)
--       noZombieClipset = true,               -- Skip zombie walk/strafe animations entirely
--       screenEffect = "CayoMage",           -- Screen effect when player is near this zombie
--       screenEffectDistance = 15.0,          -- Range for screen effect
--
--       -- Combat
--       customMelee = true,                   -- Scripted melee (false = GTA combat AI; for ranged/fleeing types)
--       meleeDamage = 12,                     -- Flat scripted-melee damage per hit (overrides customMelee.damage * unarmedDamageModifier)
--       weapon = "WEAPON_MUSKET",             -- Weapon to give (default: weapon_unarmed)
--       combatRange = 0,                      -- GTA combat range: 0=short/defensive, 1=medium, 2=long
--       shootRate = 40,                       -- GTA shoot rate (lower = slower)
--       maxEngageDistance = 12,               -- Max distance AI will engage from
--       flee = true,                          -- Flee from player instead of fighting
--       maxShootingDistance = 12.0,           -- Max distance AI will fire weapon from
--       weaponDamageModifier = 0.4,          -- Weapon damage multiplier (0.4 = 40% damage)
--
--       -- Abilities (only include the ones this type uses)
--       explodeOnDeath = true,                -- Explode when killed (dead man's switch)
--       explodeDamageScale = 1.0,             -- Explosion damage multiplier
--       scream = true,                        -- Scream to attract nearby zombies
--       screamDistance = 30.0,                -- Range to trigger scream
--       screamAggroRadius = 110.0,            -- Radius to aggro existing zombies
--       summon = true,                        -- Summon new zombies via ritual animation
--       summonDistance = 20.0,                -- Range to trigger summoning
--       summonPeds = { ["default"] = 2 },     -- Types and counts to summon per ritual
--       toxic = true,                         -- Emit toxic gas cloud
--       toxicDistance = 2.0,                  -- Gas damage radius
--       toxicDelay = 20000,                   -- Delay between gas attacks (ms)
--       toxicDamageScale = 1.0,               -- Gas damage multiplier
--       splitter = true,                      -- Split into copies on death
--       splitCount = 2,                       -- Number of copies
--       splitZombieType = "splitter",         -- Type of the copies
--       replicate = true,                     -- Summon animals (trainer)
--       replicateDistance = 35.0,             -- Detection range (replaces reactToPlayerDistance)
--       replicateCount = 2,                   -- Animals per summon wave
--       maxReplicateStages = 3,               -- Max summon waves
--       replicateCoolDown = 10000,            -- Cooldown between waves (ms)
--       replicateZombieType = "animal",       -- Type of summoned creatures
--       replicateSpeedMultiplier = 1.8,       -- Speed boost while summoning
--   },
-- ════════════════════════════════════════════════════════════════════════════════

Config.types = {
    --[[
        Default Zombie
        Standard enemy with no special abilities.
        Medium durability and speed.
    ]]
    ["default"] = {
        model = { "G_M_M_Zombie_05", "G_M_M_Zombie_01", "IG_Zombie_DJ_01", "u_f_y_corpse_01", "u_f_m_corpse_01", "u_m_m_prolsec_01", "u_m_o_filmnoir", "u_m_y_corpse_01", "u_m_o_tramp_01", "u_m_y_zombie_01", "ig_gustavo_02" },
        reactToPlayerDistance = 15.0,
        damagePacks = { "Fall", "BigHitByVehicle", "Explosion_Large" },
        damagePackCount = { min = 1, max = 2 }
    },

    --[[
        Shambler
        Slow zombie locked to walk animation. Higher health compensates for lack of speed.
    ]]
    ["shambler"] = {
        model = { "G_M_M_Zombie_05", "G_M_M_Zombie_01", "u_f_y_corpse_01", "u_f_m_corpse_01", "u_m_y_corpse_01", "u_m_o_tramp_01" },
        health = 200,
        speed = 1.0,
        moveStyle = "walk",
        reactToPlayerDistance = 12.0,
        damagePacks = { "Fall", "BigHitByVehicle", "Explosion_Large" },
        damagePackCount = { min = 1, max = 2 },
    },


    --[[
        Splitter
        Splits into copies when damaged.
        Lower health but harder to eliminate completely.
    ]]
    ["splitter"] = {
        model = { "G_M_M_Zombie_04" },
        speed = 1.15,
        splitter = true,
        splitCount = 2,
        splitZombieType = "splitter",
    },

    --[[
        Screamer
        Emits a terrifying scream that attracts nearby zombies.
        High durability, priority target.
    ]]
    ["screamer"] = {
        model = { "mp_f_deadhooker" },
        health = 250,
        speed = 1.1,
        scream = true,
        screamDistance = 30.0,
        screamAggroRadius = 110.0,
    },

    --[[
        Summoner
        Uses a magic staff to summon new zombies.
        Medium durability, slower movement.
    ]]
    ["summoner"] = {
        model = { "G_F_M_UndeadMage", "G_M_M_Zombie_03" },
        health = 300,
        summon = true,
        summonDistance = 20.0,
        summonPeds = { ["default"] = 2 },
        screenEffect = "CayoMage",
        screenEffectDistance = 15.0,
    },

    --[[
        Exploder
        Explodes on death (dead man's switch).
        Medium durability, dangerous in close quarters.
    ]]
    ["exploder"] = {
        model = { "G_M_M_Zombie_02" },
        health = 200,
        speed = 1.1,
        explodeOnDeath = true,
        explodeDamageScale = 0.5,
    },

    --[[
        Tank
        Massive zombie with huge HP pool.
        Slow but extremely durable.
    ]]
    ["tank"] = {
        model = { "G_M_M_Zombie_02" },
        health = 550,
        armor = 50,
        speed = 0.9,
        lootTable = "tank",
        reactToPlayerDistance = 15.0,
    },

    --[[
        Juggernaut
        Armored zombie emitting toxic fumes.
        Highest durability, deals area damage.
    ]]
    ["juggernaut"] = {
        model = { "u_m_y_juggernaut_01" },
        health = 450,
        armor = 100,
        speed = 0.85,
        lootTable = "juggernaut",
        toxic = true,
        toxicDistance = 2.0,
        toxicDelay = 15000,
        toxicDamageScale = 1.0,
        screenEffect = "CayoToxicWater",
        screenEffectDistance = 5.0,
    },

    --[[
        Skeleton
        Undead warrior armed with a musket.
        Defensive ranged attacker - stays back and shoots, walks normally (no zombie shamble).
    ]]
    ["skeleton"] = {
        model = { "IG_Skeleton_01" },
        instantHeadShotKill = true,
        lootTable = "skeleton",
        weapon = "WEAPON_MUSKET",
        customMelee = false, -- ranged (musket), keep GTA combat AI
        noZombieClipset = true,
        combatRange = 0,
        shootRate = 40,
        maxEngageDistance = 12,
        maxShootingDistance = 12.0,  -- Max distance AI will fire from
        weaponDamageModifier = 0.4, -- Weapon damage multiplier (musket hits hard, scale down)
    },

    --[[
        Animal
        Zombie animals - coyotes, boars, deer.
        Low health, melee only, doesn't react to vehicles.
    ]]
    ["animal"] = {
        model = { "a_c_coyote_02", "a_c_boar_02", "a_c_deer_02" },
        health = 100,
        weapon = "WEAPON_ANIMAL",
        customMelee = false, -- animal bite anims are fine, keep GTA melee
        reactToPlayerInVehicle = false,
        reactToPlayerInVehicleHonk = false,
    },

    --[[
        Trainer
        Summons and controls animals. When the trainer dies, all its animals die too.
        Does not react to gunshots, sprinting or vehicles - only detects players within replicateDistance.
        Moves toward the player at increased speed while summoning.
    ]]
    ["trainer"] = {
        model = { "ig_hunter" },
        health = 300,
        customMelee = false, -- flees, never melees
        reactToPlayerDistance = false,
        reactToShots = false,
        reactToSprint = false,
        reactToPlayerInVehicle = false,
        reactToPlayerInVehicleHonk = false,
        flee = true,
        replicate = true,
        replicateDistance = 35.0,
        replicateCount = 2,
        maxReplicateStages = 3,
        replicateCoolDown = 10000,
        replicateZombieType = "animal",
        replicateSpeedMultiplier = 1.8,
    },

    --[[
        Wild Animals (deer, boar, coyote)
        Living fauna, NOT undead. They keep their native GTA relationship group, so
        they behave like normal wildlife (deer flee, predators hunt) and stay out of
        the zombie systems. Killing one drops the "wildlife" loot table.
        Add your own: copy a block, pick a model + relationshipGroup + lootTable, then
        add the type name to an archetype's spawnChances (see rural/coastal/small_town).
    ]]
    ["deer"] = {
        model = { "a_c_deer" },
        wildlife = true,
        relationshipGroup = "DEER", -- flees from players
        health = 150,
        customMelee = false,
        lootTable = "wildlife",
    },

    ["boar"] = {
        model = { "a_c_boar" },
        wildlife = true,
        relationshipGroup = "WILD_ANIMAL", -- roams, can charge when cornered
        health = 200,
        customMelee = false,
        lootTable = "wildlife",
    },

    ["coyote"] = {
        model = { "a_c_coyote" },
        wildlife = true,
        relationshipGroup = "WILD_ANIMAL",
        health = 120,
        customMelee = false,
        lootTable = "wildlife",
    },

    -- More animals (uncomment, then add the name to an archetype's spawnChances to use):
    -- ["cougar"]    = { model = { "a_c_mtlion" },     wildlife = true, relationshipGroup = "COUGAR",    health = 260, customMelee = false, lootTable = "wildlife", weapon = "WEAPON_ANIMAL" }, -- hunts players
    -- ["guard_dog"] = { model = { "a_c_rottweiler" }, wildlife = true, relationshipGroup = "GUARD_DOG", health = 180, customMelee = false, lootTable = "wildlife", weapon = "WEAPON_ANIMAL" },
}

-- ════════════════════════════════════════════════════════════════════════════════
-- WEAPON AGGRO RADIUS
-- Per-weapon radius that determines how far gunshots attract zombies.
-- ════════════════════════════════════════════════════════════════════════════════

Config.weaponAggroRadius = {
    default                       = 80.0, -- Used for any weapon not listed below

    -- Melee (quiet)
    [`WEAPON_UNARMED`]            = 3.0,
    [`WEAPON_BAT`]                = 5.0,
    [`WEAPON_KNIFE`]              = 5.0,
    [`WEAPON_MACHETE`]            = 5.0,
    [`WEAPON_HATCHET`]            = 5.0,
    [`WEAPON_SWITCHBLADE`]        = 5.0,
    [`WEAPON_BOTTLE`]             = 5.0,
    [`WEAPON_DAGGER`]             = 5.0,
    [`WEAPON_NIGHTSTICK`]         = 5.0,
    [`WEAPON_WRENCH`]             = 5.0,
    [`WEAPON_HAMMER`]             = 5.0,
    [`WEAPON_CROWBAR`]            = 5.0,
    [`WEAPON_GOLFCLUB`]           = 5.0,
    [`WEAPON_POOLCUE`]            = 5.0,
    [`WEAPON_FLASHLIGHT`]         = 5.0,
    [`WEAPON_KNUCKLE`]            = 5.0,
    [`WEAPON_STONE_HATCHET`]      = 5.0,
    [`WEAPON_BATTLEAXE`]          = 5.0,
    [`WEAPON_CANDYCANE`]          = 5.0,

    -- Pistols
    [`WEAPON_PISTOL`]             = 60.0,
    [`WEAPON_PISTOL_MK2`]         = 60.0,
    [`WEAPON_COMBATPISTOL`]       = 60.0,
    [`WEAPON_PISTOL50`]           = 70.0,
    [`WEAPON_HEAVYPISTOL`]        = 70.0,
    [`WEAPON_APPISTOL`]           = 55.0,
    [`WEAPON_SNSPISTOL`]          = 50.0,
    [`WEAPON_SNSPISTOL_MK2`]      = 50.0,
    [`WEAPON_VINTAGEPISTOL`]      = 60.0,
    [`WEAPON_REVOLVER`]           = 80.0,
    [`WEAPON_REVOLVER_MK2`]       = 80.0,
    [`WEAPON_DOUBLEACTION`]       = 80.0,
    [`WEAPON_NAVYREVOLVER`]       = 80.0,
    [`WEAPON_MARKSMANPISTOL`]     = 65.0,
    [`WEAPON_CERAMICPISTOL`]      = 55.0,
    [`WEAPON_PISTOLXM3`]          = 60.0,
    [`WEAPON_GADGETPISTOL`]       = 55.0,
    [`WEAPON_STUNGUN`]            = 15.0,
    [`WEAPON_STUNGUN_MP`]         = 15.0,
    [`WEAPON_FLAREGUN`]           = 70.0,
    [`WEAPON_RAYPISTOL`]          = 60.0,

    -- SMGs
    [`WEAPON_MICROSMG`]           = 70.0,
    [`WEAPON_SMG`]                = 75.0,
    [`WEAPON_SMG_MK2`]            = 75.0,
    [`WEAPON_ASSAULTSMG`]         = 75.0,
    [`WEAPON_MINISMG`]            = 65.0,
    [`WEAPON_COMBATPDW`]          = 70.0,
    [`WEAPON_MACHINEPISTOL`]      = 65.0,
    [`WEAPON_TECPISTOL`]          = 70.0,
    [`WEAPON_RAYCARBINE`]         = 75.0,

    -- Shotguns
    [`WEAPON_PUMPSHOTGUN`]        = 90.0,
    [`WEAPON_PUMPSHOTGUN_MK2`]    = 90.0,
    [`WEAPON_SAWNOFFSHOTGUN`]     = 80.0,
    [`WEAPON_ASSAULTSHOTGUN`]     = 90.0,
    [`WEAPON_BULLPUPSHOTGUN`]     = 85.0,
    [`WEAPON_HEAVYSHOTGUN`]       = 95.0,
    [`WEAPON_MUSKET`]             = 100.0,
    [`WEAPON_DBSHOTGUN`]          = 90.0,
    [`WEAPON_COMBATSHOTGUN`]      = 90.0,
    [`WEAPON_AUTOSHOTGUN`]        = 85.0,

    -- Assault Rifles
    [`WEAPON_ASSAULTRIFLE`]       = 100.0,
    [`WEAPON_ASSAULTRIFLE_MK2`]   = 100.0,
    [`WEAPON_CARBINERIFLE`]       = 100.0,
    [`WEAPON_CARBINERIFLE_MK2`]   = 100.0,
    [`WEAPON_ADVANCEDRIFLE`]      = 95.0,
    [`WEAPON_SPECIALCARBINE`]     = 100.0,
    [`WEAPON_SPECIALCARBINE_MK2`] = 100.0,
    [`WEAPON_BULLPUPRIFLE`]       = 95.0,
    [`WEAPON_BULLPUPRIFLE_MK2`]   = 95.0,
    [`WEAPON_COMPACTRIFLE`]       = 90.0,
    [`WEAPON_MILITARYRIFLE`]      = 105.0,
    [`WEAPON_HEAVYRIFLE`]         = 105.0,
    [`WEAPON_TACTICALRIFLE`]      = 100.0,

    -- LMGs
    [`WEAPON_MG`]                 = 110.0,
    [`WEAPON_COMBATMG`]           = 110.0,
    [`WEAPON_COMBATMG_MK2`]       = 110.0,
    [`WEAPON_GUSENBERG`]          = 100.0,

    -- Snipers
    [`WEAPON_SNIPERRIFLE`]        = 105.0,
    [`WEAPON_HEAVYSNIPER`]        = 110.0,
    [`WEAPON_HEAVYSNIPER_MK2`]    = 110.0,
    [`WEAPON_MARKSMANRIFLE`]      = 105.0,
    [`WEAPON_MARKSMANRIFLE_MK2`]  = 105.0,
    [`WEAPON_PRECISIONRIFLE`]     = 105.0,

    -- Heavy / Explosives
    [`WEAPON_RPG`]                = 110.0,
    [`WEAPON_GRENADELAUNCHER`]    = 105.0,
    [`WEAPON_MINIGUN`]            = 110.0,
    [`WEAPON_RAILGUN`]            = 110.0,
    [`WEAPON_RAILGUNXM3`]         = 110.0,
    [`WEAPON_HOMINGLAUNCHER`]     = 110.0,
    [`WEAPON_COMPACTLAUNCHER`]    = 105.0,
    [`WEAPON_FIREWORK`]           = 100.0,
    [`WEAPON_EMPLAUNCHER`]        = 100.0,
    [`WEAPON_RAYMINIGUN`]         = 110.0,
    [`WEAPON_GRENADE`]            = 100.0,
    [`WEAPON_PIPEBOMB`]           = 100.0,
    [`WEAPON_MOLOTOV`]            = 90.0,
    [`WEAPON_STICKYBOMB`]         = 100.0,
    [`WEAPON_PROXMINE`]           = 100.0,
    [`WEAPON_BZGAS`]              = 80.0,
    [`WEAPON_SMOKEGRENADE`]       = 50.0,
    [`WEAPON_FLARE`]              = 60.0,

    -- Add any weapon hash here with custom radius
}

-- ════════════════════════════════════════════════════════════════════════════════
-- SUPPRESSOR SETTINGS
-- Weapons with suppressors attached get their aggro radius multiplied by this value.
-- The suppressor components list covers all vanilla GTA V suppressors.
-- ════════════════════════════════════════════════════════════════════════════════

Config.suppressorMultiplier = 0.5 -- Weapon aggro radius is multiplied by this when a suppressor is attached (0.5 = half the noise)

Config.suppressorComponents = {
    `COMPONENT_AT_PI_SUPP`,         -- Pistol suppressor (Combat Pistol, AP Pistol, Heavy Pistol, etc.)
    `COMPONENT_AT_PI_SUPP_02`,      -- Pistol suppressor 2 (Pistol, Pistol MK2, SNS Pistol MK2)
    `COMPONENT_AT_AR_SUPP`,         -- Rifle suppressor (Carbine, Advanced, Bullpup, Marksman, etc.)
    `COMPONENT_AT_AR_SUPP_02`,      -- Rifle suppressor 2 (Assault Rifle, Special Carbine, Sniper, etc.)
    `COMPONENT_AT_SR_SUPP`,         -- Shotgun suppressor (Pump Shotgun)
    `COMPONENT_AT_SR_SUPP_03`,      -- Sniper suppressor (Pump Shotgun MK2, Heavy Sniper MK2)
    `COMPONENT_CERAMICPISTOL_SUPP`, -- Ceramic Pistol suppressor
    `COMPONENT_PISTOLXM3_SUPP`,     -- WM 29 Pistol suppressor
}


-- ════════════════════════════════════════════════════════════════════════════════
-- LOOT TABLES
-- Define items dropped by zombies.
-- ════════════════════════════════════════════════════════════════════════════════

Config.lootTables = {
    -- IMPORTANT: All item names below MUST match your inventory system exactly.
    -- Default - balanced survival loot (used by urban and suburban archetypes)
    ["default"] = {
        maxItems = 2,
        chance = 40, -- (1-100)% chance if the zombie drops anything at all
        items = {
            { item = "bread",        min = 1, max = 1, chance = 18 }, -- chance to get a specific item when zombie drops anything
            { item = "water_bottle", min = 1, max = 1, chance = 18 },
            { item = "bandage",      min = 1, max = 2, chance = 15 },
            { item = "cloth",        min = 1, max = 3, chance = 15 },
            { item = "ammo-9",       min = 2, max = 8, chance = 12 },
            { item = "wood",         min = 2, max = 6, chance = 10 },
            { item = "stone",        min = 1, max = 4, chance = 8 },
            { item = "metal",        min = 1, max = 2, chance = 4 },
        }
    },

    -- Military - combat supplies, ammo, and advanced materials
    ["military"] = {
        maxItems = 3,
        chance = 50,
        items = {
            { item = "ammo-rifle",   min = 5, max = 15, chance = 22 },
            { item = "ammo-9",       min = 5, max = 20, chance = 18 },
            { item = "ammo-shotgun", min = 3, max = 10, chance = 12 },
            { item = "bandage",      min = 1, max = 2,  chance = 12 },
            { item = "metal",        min = 2, max = 4,  chance = 12 },
            { item = "bread",        min = 1, max = 1,  chance = 10 },
            { item = "hqm",          min = 1, max = 2,  chance = 8 },
            { item = "medkit",       min = 1, max = 1,  chance = 6 },
        }
    },

    -- Industrial - crafting materials and tools
    ["industrial"] = {
        maxItems = 2,
        chance = 40,
        items = {
            { item = "metal",      min = 2, max = 5,  chance = 22 },
            { item = "stone",      min = 2, max = 6,  chance = 18 },
            { item = "ammo-9",     min = 3, max = 10, chance = 12 },
            { item = "wood",       min = 2, max = 5,  chance = 12 },
            { item = "cloth",      min = 1, max = 3,  chance = 10 },
            { item = "ammo-rifle", min = 2, max = 8,  chance = 10 },
            { item = "bandage",    min = 1, max = 2,  chance = 8 },
            { item = "hqm",        min = 1, max = 1,  chance = 5 },
        }
    },

    -- Downtown - scavenged civilian supplies
    ["downtown"] = {
        maxItems = 2,
        chance = 40,
        items = {
            { item = "bread",        min = 1, max = 2,  chance = 20 },
            { item = "water_bottle", min = 1, max = 2,  chance = 20 },
            { item = "bandage",      min = 1, max = 2,  chance = 15 },
            { item = "cloth",        min = 1, max = 3,  chance = 12 },
            { item = "ammo-9",       min = 2, max = 10, chance = 12 },
            { item = "ammo-rifle",   min = 2, max = 6,  chance = 8 },
            { item = "medkit",       min = 1, max = 1,  chance = 4 },
            { item = "metal",        min = 1, max = 2,  chance = 4 },
        }
    },

    -- Rural - natural resources and survival basics
    ["rural"] = {
        maxItems = 2,
        chance = 40,
        items = {
            { item = "wood",         min = 3, max = 10, chance = 25 },
            { item = "stone",        min = 2, max = 6,  chance = 20 },
            { item = "cloth",        min = 1, max = 4,  chance = 15 },
            { item = "bread",        min = 1, max = 1,  chance = 12 },
            { item = "water_bottle", min = 1, max = 1,  chance = 12 },
            { item = "bandage",      min = 1, max = 1,  chance = 8 },
            { item = "ammo-9",       min = 1, max = 5,  chance = 5 },
            { item = "metal",        min = 1, max = 1,  chance = 3 },
        }
    },

    -- Type-Specific Tables
    -- These override zone-based loot when a zombie type has lootTable = "<name>".
    -- Harder-to-kill types reward better loot regardless of where they died.

    -- Tank - armored brute, rewards heavy materials and ammo
    ["tank"] = {
        maxItems = 3,
        chance = 40,
        items = {
            { item = "metal",        min = 2, max = 5,  chance = 22 },
            { item = "ammo-rifle",   min = 5, max = 15, chance = 18 },
            { item = "ammo-9",       min = 5, max = 15, chance = 15 },
            { item = "hqm",          min = 1, max = 2,  chance = 12 },
            { item = "bandage",      min = 1, max = 3,  chance = 12 },
            { item = "medkit",       min = 1, max = 1,  chance = 10 },
            { item = "bread",        min = 1, max = 2,  chance = 8 },
            { item = "water_bottle", min = 1, max = 1,  chance = 3 },
        }
    },

    -- Juggernaut - toughest kill in the game, best possible drops
    ["juggernaut"] = {
        maxItems = 3,
        chance = 40,
        items = {
            { item = "hqm",          min = 1, max = 3,  chance = 18 },
            { item = "metal",        min = 3, max = 6,  chance = 18 },
            { item = "medkit",       min = 1, max = 1,  chance = 15 },
            { item = "ammo-rifle",   min = 8, max = 20, chance = 15 },
            { item = "bandage",      min = 1, max = 3,  chance = 12 },
            { item = "ammo-shotgun", min = 5, max = 12, chance = 10 },
            { item = "ammo-9",       min = 5, max = 15, chance = 8 },
            { item = "bread",        min = 1, max = 1,  chance = 4 },
        }
    },

    -- Skeleton - ranged undead, drops ammo-focused loot
    ["skeleton"] = {
        maxItems = 2,
        chance = 40,
        items = {
            { item = "ammo-rifle",   min = 5, max = 15, chance = 25 },
            { item = "ammo-9",       min = 5, max = 15, chance = 20 },
            { item = "ammo-shotgun", min = 3, max = 8,  chance = 15 },
            { item = "metal",        min = 1, max = 3,  chance = 12 },
            { item = "bandage",      min = 1, max = 2,  chance = 10 },
            { item = "cloth",        min = 1, max = 3,  chance = 10 },
            { item = "hqm",          min = 1, max = 1,  chance = 5 },
            { item = "bread",        min = 1, max = 1,  chance = 3 },
        }
    },

    -- Wildlife - animal drops (meat, hide, bone). Map these to your inventory items.
    ["wildlife"] = {
        maxItems = 2,
        chance = 35,
        items = {
            { item = "meat",    min = 1, max = 3, chance = 30 },
            { item = "leather", min = 1, max = 2, chance = 22 },
            { item = "bone",    min = 1, max = 3, chance = 18 },
            { item = "fat",     min = 1, max = 2, chance = 10 },
        }
    },

}

-- ════════════════════════════════════════════════════════════════════════════════
-- WEAPON LOOT TABLES (optional)
-- Override loot drops based on the weapon used to kill the zombie.
-- Maps weapon hashes to loot table names from Config.lootTables above.
-- Priority: weapon loot > zombie type loot > zone archetype loot > default
--
-- To enable: uncomment the entries below and add matching loot tables to
-- Config.lootTables (e.g. ["melee"] = { maxItems = 3, chance = 40, items = { ... } })
-- ════════════════════════════════════════════════════════════════════════════════

Config.weaponLootTables = {
    -- Melee weapons
    -- [`WEAPON_KNIFE`]          = "melee",
    -- [`WEAPON_MACHETE`]        = "melee",
    -- [`WEAPON_HATCHET`]        = "melee",
    -- [`WEAPON_SWITCHBLADE`]    = "melee",
    -- [`WEAPON_BAT`]            = "melee",
    -- [`WEAPON_CROWBAR`]        = "melee",
    -- [`WEAPON_HAMMER`]         = "melee",
    -- [`WEAPON_STONE_HATCHET`]  = "melee",

    -- Shotguns
    -- [`WEAPON_PUMPSHOTGUN`]    = "shotgun",
    -- [`WEAPON_SAWNOFFSHOTGUN`] = "shotgun",
    -- [`WEAPON_ASSAULTSHOTGUN`] = "shotgun",
    -- [`WEAPON_DBSHOTGUN`]      = "shotgun",
}

-- ════════════════════════════════════════════════════════════════════════════════
-- SAFE ZONE SETTINGS
-- Global safe zone behavior settings
-- ════════════════════════════════════════════════════════════════════════════════

Config.safeZoneGracePeriod = 10000 -- How long the player stays invincible after leaving a safe zone in ms (10s, 0 to disable)

-- ════════════════════════════════════════════════════════════════════════════════
-- SAFE ZONE DEFAULTS
-- Base values applied to every safe zone. Zones only need to specify fields
-- that differ from these defaults (coords is always required).
-- Sub-tables (blip, zoneBlip, zoneMarker) are merged field-by-field - you can
-- override just the label without repeating sprite/color/scale.
-- ════════════════════════════════════════════════════════════════════════════════

Config.safeZoneDefaults = {
    zoneRadius = 100.0,
    disableWeapons = true,
    invincible = true,
    disablePvP = true,
    ghostMode = false,
    passThroughPlayers = false,
    zoneMarker = { enabled = false, color = { 0, 255, 0 }, alpha = 100 },
    blip = { enabled = true, sprite = 176, color = 2, scale = 1.0, label = "Safe Zone" },
    zoneBlip = { enabled = true, color = 2 },
}

-- ════════════════════════════════════════════════════════════════════════════════
-- DANGER ZONE DEFAULTS
-- Base values applied to every danger zone. Zones only need to specify fields
-- that differ from these defaults (coords and pedsToSpawn are always required).
-- Sub-tables (blip, zoneBlip, zoneMarker) are merged field-by-field - you can
-- override just the label without repeating sprite/color/scale.
-- ════════════════════════════════════════════════════════════════════════════════

Config.dangerZoneDefaults = {
    zoneRadius = 100.0,
    healthMultiplier = 1.0,
    respawnCooldown = 200000, -- in ms (200s)
    zoneMarker = { enabled = false, color = { 255, 0, 0 }, alpha = 100 },
    blip = { enabled = true, sprite = 630, color = 1, scale = 1.0, label = "Danger Zone" },
    zoneBlip = { enabled = true, color = 1 },
}

-- ════════════════════════════════════════════════════════════════════════════════
-- ZONES
-- Safe and danger zone definitions
-- ════════════════════════════════════════════════════════════════════════════════

Config.zones = {
    drawDebug = false, -- Draw ox_lib debug outlines for safe zones and danger zones
    --[[
        Safe Zones - areas where zombies cannot spawn.

        Each zone inherits ALL fields from Config.safeZoneDefaults above.
        You only need to specify what's different. Every property can be
        overridden per zone: zoneRadius, disableWeapons, invincible,
        disablePvP, ghostMode, passThroughPlayers, blip, zoneBlip, zoneMarker.
        Sub-tables (blip, zoneBlip, zoneMarker) are merged field-by-field,
        so { blip = { label = "Shop" } } keeps the default sprite/color/scale.

        Required: coords
    ]]
    safeZones = {
        {
            -- zoneRadius = 50.0, (You can override default - 100.0)
            -- disableWeapons = false, (You can override default - true)
            -- invincible = false, (You can override default - true)
            -- disablePvP = false, (You can override default - true)
            -- ghostMode = true, (You can override default - false)
            -- passThroughPlayers = true, (You can override default - false)
            coords = vector3(-2292.4773, 370.4102, 174.6017),
            blip = { enabled = true, label = "Observatory" },
            -- Blip control:
            --   blip = false                              -> remove the blip completely (icon AND the green map circle)
            --   blip = { enabled = false, label = "..." } -> remove the icon, but keep the green circle on the map
        }
    },

    --[[
        Danger Zones - areas with increased zombie spawns and unique types.

        Each zone inherits ALL fields from Config.dangerZoneDefaults above.
        You only need to specify what's different. Every property can be
        overridden per zone: zoneRadius, healthMultiplier, respawnCooldown,
        blip, zoneBlip, zoneMarker.
        Sub-tables (blip, zoneBlip, zoneMarker) are merged field-by-field,
        so { blip = { label = "Labs" } } keeps the default sprite/color/scale.

        SHAPE - circle (coords + optional zoneRadius) or polygon (points = list of
        vector3, 3+ vertices, optional thickness; coords/zoneRadius derived from them).

        typeLimits (optional) - per-type spawn cap, applied after the multipliers.
        Handy for boss fights, e.g. typeLimits = { tank = 1 }.

        Required: coords OR points, and pedsToSpawn
    ]]
    dangerZones = {
        --[[ Polygon boss arena example (uncomment to use):
        {
            points = {
                vector3(1970.0, 2950.0, 58.0),
                vector3(2030.0, 2950.0, 58.0),
                vector3(2030.0, 3010.0, 58.0),
                vector3(1970.0, 3010.0, 58.0),
            },
            thickness = 40.0,
            blip = { label = "Boss Arena" },
            pedsToSpawn = { ["default"] = 6, ["tank"] = 1 },
            typeLimits = { ["tank"] = 1 }, -- one tank max
            healthMultiplier = 1.5,
        },
        ]]
        { -- Humane Labs
            coords = vector3(3525.9617, 3716.9829, 36.6426),
            -- zoneRadius = 150.0, (You can override default - 100.0)
            -- respawnCooldown = 120000, (You can override default - 200000ms)
            blip = { label = "Humane Labs" },
            pedsToSpawn = {
                ["default"]    = 3,
                ["juggernaut"] = 2,
                ["exploder"]   = 2,
                ["tank"]       = 1,
                ["summoner"]   = 1,
            },
            healthMultiplier = 1.3,
        },

        { -- Bolingbroke Penitentiary
            coords = vector3(1714.4968, 2560.5166, 45.5648),
            blip = { label = "Bolingbroke Penitentiary" },
            pedsToSpawn = {
                ["default"]  = 3,
                ["tank"]     = 2,
                ["exploder"] = 1,
                ["summoner"] = 1,
            },
            healthMultiplier = 1.3,
        },

        { -- Fort Zancudo
            coords = vector3(-2131.0125, 3140.4380, 32.8101),
            blip = { label = "Fort Zancudo" },
            pedsToSpawn = {
                ["tank"]       = 3,
                ["skeleton"]   = 3,
                ["juggernaut"] = 1,
                ["exploder"]   = 1,
            },
            healthMultiplier = 1.4,
        },

        {                       -- LS Airport
            coords = vector3(-1251.8574, -2938.4165, 13.9469),
            zoneRadius = 120.0, -- Larger than default 100.0
            blip = { label = "LS Airport" },
            pedsToSpawn = {
                ["default"]  = 4,
                ["exploder"] = 2,
                ["splitter"] = 2,
                ["tank"]     = 1,
            },
            healthMultiplier = 1.3,
        },

        { -- N.O.O.S.E
            coords = vector3(2521.6023, -384.2304, 93.0671),
            blip = { label = "N.O.O.S.E" },
            pedsToSpawn = {
                ["tank"]       = 2,
                ["juggernaut"] = 2,
                ["skeleton"]   = 1,
                ["exploder"]   = 1,
            },
            healthMultiplier = 1.3,
        },
    }
}
```
