ZombieManager — Zombie AI and Spawning
The ZombieManager (1960 lines in Unturned/Managers/ZombieManager.cs) is the server-authoritative manager controlling the zombie population of an Unturned level. It manages zombie spawning from navmesh-defined regions, the per-zombie AI state machine, special ability replication (spit, charge, acid, boulder, stun, breath, spark, stomp, throw), loot table resolution on death, the wave/horde-beacon system, and the tick-based update loop that processes zombie behavior in time-sliced batches.
The zombie system is structured around several supporting types: Zombie (the runtime component), ZombieRegion (region container per navmesh bound), ZombieSpawnpoint (spawn location linked to a zombie table), ZombieTable (tiered slot configuration with loot and difficulty), ZombieSlot (clothing slot with chance-weighted item tables), and ZombieCloth (individual clothing item assignment).
Source files:
Unturned/Managers/ZombieManager.cs(1960 lines),Unturned/Level/ZombieSpawnpoint.cs(38 lines),Unturned/Level/ZombieTable.cs,Unturned/Level/ZombieSlot.cs,Unturned/Level/ZombieCloth.cs. This article references the zombie-related types inUnturned/Level/.
Who this article is for
This article is for module and plugin developers who need to spawn zombies, manage AI behavior slots, configure loot tables, work with horde beacons, or create custom wave encounters. It assumes familiarity with the navmesh bound system (LevelNavigation) and the Regions spatial partitioning model.
What you'll learn
- The zombie region model: one region per navmesh bound
- The tick-based AI processing loop with wandering, idle, target acquisition states
- Special zombie variants and their replicated abilities
- Horde spawning and wave management
- The weighted-random speciality selection system
- Loot drop resolution from zombie tables
- The prefab selection for dedicated server / listen server / client builds
Zombie region model
Zombies are grouped into ZombieRegion instances, one per navmesh navigation bound:
csharp
private static ZombieRegion[] _regions;
public static ZombieRegion[] regions => _regions;
internal static HashSet<int> regionsWithPlayers;Each ZombieRegion corresponds to a LevelNavigation bound — the same bounds used for navmesh baking. The regionsWithPlayers set tracks which regions currently have at least one player present, which drives the spawning and ticking decisions.
Zombie list
All zombies are accessible both per-region and globally:
csharp
public static List<Zombie> AllZombies { get; private set; }The per-region zombie list is regions[bound].zombies. Each zombie's bound field stores its navmesh bound index.
getZombiesInRadius
csharp
public static void getZombiesInRadius(Vector3 center, float sqrRadius, List<Zombie> result)Resolves the navmesh bound from the center point via LevelNavigation.tryGetNavigation, then iterates the zombies in that region only. This is a narrow, bound-scoped search — unlike the manager spatial queries that span multiple regions.
Zombie spawning
addZombie — Instantiation
csharp
public void addZombie(byte bound, byte type, byte speciality, byte shirt, byte pants,
byte hat, byte gear, byte move, byte idle, Vector3 position, float angle, bool isDead)The method selects the appropriate prefab based on the build context:
csharp
GameObject zombiePrefab;
if (Dedicator.IsDedicatedServer)
zombiePrefab = dedicatedZombiePrefab; // "Characters/Zombie_Dedicated"
else if (Provider.isServer)
zombiePrefab = serverZombiePrefab; // "Characters/Zombie_Server"
else
zombiePrefab = clientZombiePrefab; // "Characters/Zombie_Client"The dedicated server prefab has minimal visual components. The client prefab includes the full renderer, animation, and audio. The listen server prefab sits in the middle.
The zombie's id is assigned as the index in the region's zombie list:
csharp
character.id = (ushort) regions[bound].zombies.Count;After setting all fields (type, speciality, bound, slot, clothing, move/idle state, dead flag), character.init() is called to wire up the zombie's runtime state.
Initial replication to clients
When a client enters a region, the server sends all zombies in that region via SendZombies:
csharp
private static void SendZombies_Write(NetPakWriter writer, byte bound)Each zombie is serialized with: type, speciality, shirt, pants, hat, gear, move state, idle state, position, yaw, and isDead flag.
Difficulty asset resolution
csharp
public static ZombieDifficultyAsset getDifficultyInBound(byte bound)
public static ZombieDifficultyAsset GetDifficultyInBoundForTable(byte bound,
ZombieTable table, bool forSpawnOverrides)The difficulty for a bound can come from either the navmesh flag data (LevelNavigation.flagData[bound]) or the zombie table. The prioritization is controlled by Level.getAsset()?.ZombieDifficultyAssetPrioritization:
NavmeshOverridesTable(default): check navmesh first, fall back to tableTableOverridesNavmesh: check table first, fall back to navmesh
The forSpawnOverrides flag controls whether the result must have Overrides_Spawn_Chance set to be considered valid for spawn chance overrides.
AI state system
Tick processing
The AI update loop uses a time-slice approach:
csharp
private static int tickIndex;
private static List<Zombie> _tickingZombies;Only a subset of zombies are processed each frame, determined by tickIndex. The canSpareWanderer property controls whether new wandering zombies can be spawned:
csharp
public static bool canSpareWanderer => wanderingCount < 8 && tickingZombies.Count < 50;This caps wandering zombies at 8 and total ticking zombies at 50.
Wandering and idle
Zombies have move and idle byte fields that encode their wandering pattern and idle animation set. Movement is driven by the navmesh system — zombies pathfind toward targets using the Unturned navmesh (ASPFP or Unity NavMesh depending on the build).
Target acquisition
Zombies switch between wandering and chase states based on player proximity, noise events, and damage taken. The startle mechanism (replicated via SendZombieStartle) triggers a brief reaction animation and aggressive state.
Special zombie abilities
Zombie specialities are defined by the EZombieSpeciality enum. Each speciality has a dedicated RPC pair for its unique ability:
| Ability | RPC | Method on Zombie |
|---|---|---|
| Throw | SendZombieThrow / ReceiveZombieThrow | askThrow() |
| Spit | SendZombieSpit / ReceiveZombieSpit | askSpit() |
| Charge | SendZombieCharge / ReceiveZombieCharge | askCharge() |
| Stomp | SendZombieStomp / ReceiveZombieStomp | askStomp() |
| Breath | SendZombieBreath / ReceiveZombieBreath | askBreath() |
| Acid | SendZombieAcid / ReceiveZombieAcid | askAcid(origin, direction) |
| Spark (electrical) | SendZombieSpark / ReceiveZombieSpark | askSpark(target) |
| Boulder | SendZombieBoulder / ReceiveZombieBoulder | askBoulder(origin, direction) |
| Attack | SendZombieAttack / ReceiveZombieAttack | askAttack(attackType) |
All ability RPCs validate that the zombie index is within the region's list before dispatching. The ESteamCallValidation.ONLY_FROM_SERVER attribute ensures clients cannot forge these calls.
Speciality weighted random
The ZombieSpecialityWeightedRandom inner class provides weighted random selection of zombie specialities for spawn:
csharp
private class ZombieSpecialityWeightedRandom : IComparer<Entry>Entries are added with a weight value, sorted by weight ascending via binary search insertion. The get() method performs a weighted random draw by projecting a Random.value * totalWeight onto the sorted entry list. This is used to select which speciality a newly spawned zombie receives based on difficulty asset configuration.
Loot drops
csharp
public static void dropLoot(Zombie zombie)The drop resolution has three tiers:
| Zombie type | Drop count range | Config keys |
|---|---|---|
| Boss / BOSS_ALL | Min_Boss_Drops to Max_Boss_Drops | Zombies config |
| Mega | Min_Mega_Drops to Max_Mega_Drops | Zombies config |
| Normal | Min_Drops to Max_Drops | Zombies config |
Drops are clamped to [0, 100] to prevent crash exploits.
The loot resolution chain:
- If
LevelZombies.tables[zombie.type].lootID != 0, resolve viaSpawnTableTool.ResolveLegacyId. - Otherwise, if
lootIndexis valid, useLevelItems.getItem(lootIndex). - Each resolved item is dropped via
ItemManager.dropItemwithEItemOrigin.WORLD.
Mega zombie tracking: when a mega zombie dies, regions[zombie.bound].lastMega and hasMega are updated to prevent multiple mega zombies in the same region.
Wave and horde beacon system
Wave state
csharp
private static bool _waveReady;
public static bool waveReady => _waveReady;
private static int _waveIndex;
public static int waveIndex => _waveIndex;
private static int _waveRemaining;
public static int waveRemaining => _waveRemaining;The wave system is active when Level.info.type == ELevelType.HORDE. The lastWave timer and respawnZombiesBound variable control wave progression.
Beacon state per region
csharp
regions[reference].hasBeacon = hasBeacon;The SendBeacon / ReceiveBeacon RPC pair synchronizes the beacon-activated state per region. When a beacon is active, the region spawns waves of zombies targeting the beacon location.
Wave notifications
csharp
public static WaveUpdated onWaveUpdated;Fired on both server and client when wave state changes. The SendInitialGlobalState method sends the current wave state to newly connecting clients.
Custom cooldown system
csharp
private static Dictionary<string, double> cooldowns = new Dictionary<string, double>(
StringComparer.InvariantCultureIgnoreCase);The CheckCustomCooldown method provides a general-purpose timed cooldown check:
csharp
public static bool CheckCustomCooldown(string cooldownId, double duration)Returns true if the cooldown has expired (or doesn't exist), then resets the timer. Returns false if the cooldown is still active. Uses Time.timeAsDouble for precision.
Zombie speciality and state replication
Alive state
csharp
public static void sendZombieAlive(Zombie zombie, byte newType, byte newSpeciality,
byte newShirt, byte newPants, byte newHat, byte newGear,
Vector3 newPosition, byte newAngle)Broadcasts the full alive state (type, speciality, clothing, position, angle). Also fires regions[zombie.bound].onZombieLifeUpdated.
Dead state
csharp
public static void sendZombieDead(Zombie zombie, Vector3 newRagdoll,
ERagdollEffect newRagdollEffect = ERagdollEffect.None)Broadcasts death with ragdoll force and effect flag. Fires onZombieLifeUpdated.
State updates
csharp
public static void ReceiveZombieStates(in ClientInvocationContext context)The batched state update uses a monotonically increasing sequence number (seq) to reject stale packets. Each zombie's position and yaw are read via ReadClampedVector3 and ReadDegrees.
ZombieSpawnpoint
The ZombieSpawnpoint class (38 lines in Unturned/Level/ZombieSpawnpoint.cs) is a lightweight data holder:
csharp
public class ZombieSpawnpoint
{
public byte type;
public Vector3 point => _point;
public Transform node => _node;
}The type field indexes into LevelZombies.tables to determine which zombie table this spawnpoint uses. In the editor, node is created as a colored sphere marker (color from LevelZombies.tables[type].color).
ZombieTable data structure
LevelZombies loads zombie tables from Spawns/Zombies.dat. Each ZombieTable contains:
- Color and name for editor identification
isMegaflag- Health, damage, loot ID/index
- XP reward
- Regen rate
- Difficulty GUID
- 4
ZombieSlotslots (clothing layers), each with a spawn chance and a list ofZombieClothitems
Tables are identified by a tableUniqueId (int) for cross-referencing. The FindTableIndexByUniqueId method provides lookup by unique ID rather than by index.
Audio
csharp
public static AudioClip[] roars;
public static AudioClip[] groans;
public static AudioClip[] spits;
public static AudioClip[] dl_attacks;
public static AudioClip[] dl_deaths;
public static AudioClip[] dl_enemy_spotted;
public static AudioClip[] dl_taunt;Audio clips are loaded as static arrays and referenced by the Zombie component for ambient and combat sounds.
Plugin integration
ZombieManager exposes relatively few static delegate hooks compared to VehicleManager or BarricadeManager. The primary integration point is onWaveUpdated for wave-based game modes. Custom zombie behavior is typically implemented through the Zombie component's virtual methods or by replacing the zombie prefab.
Zombie init and runtime component
When addZombie creates a new zombie, the Zombie component's init() method configures:
- Slot system: Each zombie has 4 clothing slots (shirt, pants, hat, gear). These are resolved from the
ZombieSlotentries in the zombie table at spawn time by callingapplySlot(slotIndex), which performs a weighted random draw to select aZombieClothitem. - Difficulty scaling: The bound's
ZombieDifficultyAssetmay override health, damage, speed, and speciality weights. - Speciality assignment: The weighted random system selects the zombie's speciality based on the difficulty asset's speciality weights.
- Navmesh agent: The zombie's navmesh agent is configured with the correct speed, acceleration, and stopping distance from the difficulty asset.
- Visual appearance: The clothing items are applied to the zombie's renderer. Skin color, eye glow, and other visual overrides are applied from the table configuration.
The Zombie component tracks:
| Field | Type | Purpose |
|---|---|---|
id | ushort | Index in the region's zombie list |
bound | byte | Navmesh bound index |
type | byte | Index into LevelZombies.tables |
speciality | EZombieSpeciality | Variant type (NORMAL, MEGA, SPRINTER, CRAWLER, etc.) |
shirt, pants, hat, gear | byte | Clothing slot indices |
move | byte | Movement animation state |
idle | byte | Idle animation state |
isDead | bool | Alive/dead state |
zombieRegion | ZombieRegion | Reference to the owning region |
AI state machine
The Zombie component implements a state machine with these states:
| State | Trigger | Behavior |
|---|---|---|
| IDLE | No target detected | Stand still, play idle animation, play ambient sounds |
| PATROL | Random interval | Wander to a random point within wander radius |
| ALERT | Player detected, noise heard | Face target, play alert animation, transition to CHASE |
| CHASE | Target acquired | Pathfind toward target using navmesh |
| ATTACK | Within melee range | Melee attack on cooldown |
| SPECIAL | Ability cooldown ready | Use special ability (spit, charge, etc.) |
| STUN | Damage or flashbang | Stand still, play stun animation, recover after stun duration |
| STARTLE | Surprise damage | Play startle animation before transitioning to CHASE |
| DEAD | Health reaches zero | Play death animation, trigger ragdoll, spawn loot |
The tickZombie method (part of the tick time-slice system) evaluates transitions once per unit interval.
ZombieRegion
Each ZombieRegion manages:
csharp
// Not directly in source but derived from usage:
// - List<Zombie> zombies
// - bool isNetworked
// - bool hasBeacon
// - float lastMega
// - bool hasMega
// - event onZombieLifeUpdatedThe isNetworked flag is set when the full zombie list has been sent to a client. Clients do not create zombies until ReceiveZombies is processed for a region.
The hasBeacon flag triggers wave-based horde spawning in that region.
Horde wave spawning
When hasBeacon is true for a region:
- The
respawnZombiesBoundtimer controls how often new zombies are spawned. - Zombies are spawned at beacon spawnpoints with increased frequency.
- The
_waveReadyflag is set when a wave is prepared. _waveIndexincrements with each completed wave._waveRemainingtracks how many zombies remain in the current wave.
Wave spawning respects LevelZombies.tables[zombieType] configuration for zombie types, but may increase the proportion of special zombies based on the wave index.
Tick system — time-sliced processing
csharp
private static int tickIndex;
private static List<Zombie> _tickingZombies;The tick system distributes AI processing across frames:
tickIndexadvances byregionsWithPlayers.Counteach tick.- Zombies in player-active regions are added to
_tickingZombies. - Each tick, one zombie from
_tickingZombiesis processed. - The
wanderingCountcap ensures no more than 8 zombies wander simultaneously. - The
_tickingZombies.Count < 50cap prevents AI overload.
This time-slicing ensures that AI overhead scales with player count, not total zombie count.
Zombie clothing slot system
The ZombieSlot class holds:
csharp
public class ZombieSlot
{
public float chance; // Probability this slot is populated
public List<ZombieCloth> table; // Weighted clothing items
}Each of the 4 slots (shirt, pants, hat, gear) is independently rolled. ZombieCloth references an item ID:
csharp
public class ZombieCloth
{
public ushort item; // Item ID for the clothing asset
}The LevelZombies.load() method reads slot data from Zombies.dat:
csharp
ZombieSlot[] slots = new ZombieSlot[4];
byte slotCount = block.readByte();
for (byte slotIndex = 0; slotIndex < slotCount; slotIndex++)
{
float chance = block.readSingle();
byte clothCount = block.readByte();
List<ZombieCloth> cloths = new List<ZombieCloth>();
for (byte clothIndex = 0; clothIndex < clothCount; clothIndex++)
{
ushort item = block.readUInt16();
cloths.Add(new ZombieCloth(item));
}
slots[slotIndex] = new ZombieSlot(chance, cloths);
}Zombie spawn cycle (respawn system)
The tick method of ZombieManager handles zombie respawning:
csharp
private static byte respawnZombiesBound;
private static float lastWave;Each tick:
- If the level type is
HORDE, check wave timers and spawn horde zombies. - For each region with players (
regionsWithPlayers):- Check if the region is below its
maxZombiescap. - If below cap and enough time has passed since last spawn, spawn a new zombie.
- Select a random
ZombieSpawnpointfrom the region. - Resolve the zombie type and speciality from the table.
- Apply clothing slots via weighted random from the table's
ZombieSlotentries.
- Check if the region is below its
- For regions without players: zombies remain in their current state and are not actively respawned.
The respawnZombiesBound index round-robins across regions to distribute spawn processing across frames.
Server-side request validation
RPCs like ReceiveZombieThrow, ReceiveZombieCharge, etc. (all prefixed with askZombie) are marked with [SteamCall(ESteamCallValidation.SERVERSIDE)]. This attribute ensures they can only be called by the server — client-originated invocations are silently dropped.
However, ReceiveZombieAlive, ReceiveZombieDead, ReceiveZombieSpeciality, and the other state synchronization RPCs use [SteamCall(ESteamCallValidation.ONLY_FROM_SERVER)] with a client-side guard:
csharp
if (!Provider.isServer)
{
if (!regions[reference].isNetworked)
return;
}This ensures that even if the server sends state for a region the client hasn't fully received yet, the state is safely ignored.
ZombieSpawnpoint usage at spawn time
During zombie spawning, the manager iterates the LevelZombies.spawns[x, y] list for the player's current region and uses those positions as spawn locations:
csharp
public static List<ZombieSpawnpoint>[,] spawns => _spawns;Each ZombieSpawnpoint has:
type: Index into the zombie tablespoint: World position
The spawner filters spawnpoints by type to match the expected zombie table, and validates the spawn position is clear (not inside a barricade or structure).
BOSS_ALL speciality handling
The EZombieSpeciality.BOSS_ALL speciality is treated as a boss-type modifier rather than a specific ability. Zombies with BOSS_ALL:
- Use boss-level loot drops (
Min_Boss_Drops/Max_Boss_Drops). - Are not subject to the per-bound max zombie limit.
- The
regions[bound].lastMegaandhasMegatracking does not apply (BOSS_ALL is managed separately from mega zombies).
State update batching
The SendZombieStates packet uses a monotonic sequence number and compressed position:
csharp
reader.ReadUInt32(out newSeq);
if (newSeq <= seq) return; // Reject stale
seq = newSeq;
reader.ReadUInt16(out count);
for (ushort index = 0; index < count; ++index)
{
reader.ReadUInt16(out zombieIndex);
reader.ReadClampedVector3(out position);
reader.ReadDegrees(out yaw);
regions[reference].zombies[zombieIndex].tellState(position, yaw);
}This batch only includes zombies in the player's current region (determined by the reference byte).
Zombie speciality value propagation
When a zombie's speciality changes at runtime (e.g., a normal zombie transforms into a mega zombie), sendZombieSpeciality broadcasts the change:
csharp
public static void sendZombieSpeciality(Zombie zombie, EZombieSpeciality speciality)
{
SendZombieSpeciality.InvokeAndLoopback(ENetReliability.Unreliable,
GatherRemoteClientConnections(zombie.bound),
zombie.bound, zombie.id, speciality);
}This RPC is unreliable (fire-and-forget) because speciality changes are cosmetic on the receiving end.
Zombie breed configuration
The LevelZombies.tables list stores every zombie table for the current level. Each table contains:
| Field | Type | Description |
|---|---|---|
name | string | Editor-only identifier |
color | Color | Editor gizmo color |
isMega | bool | Mega zombie variant |
health | ushort | Base health |
damage | byte | Base melee damage |
loot | byte | Legacy loot index |
lootID | ushort | Spawn table ID for loot |
xp | uint | Experience reward |
regen | float | Health regen per second |
difficultyGUID | string | ZombieDifficultyAsset reference |
tableUniqueId | int | Persistent unique identifier |
slots[4] | ZombieSlot[] | Clothing slot configurations |
Server-side zombie throw handling
When the server receives a throw request (ReceiveZombieThrow), it:
- Validates the zombie index is within the region's list.
- Calls
zombie.askThrow()which:- Selects a target position near the nearest player.
- Launches a
Throwableprojectile (typically a rock or other damaging object). - Applies damage if the projectile hits a player.
- Broadcasts
SendZombieThrowto nearby clients for projectile visual sync.
Similar flows apply to askBoulder (boulder boss attack), askAcid (acid spray), and askSpark (electrical attack). These all carry position and direction data via the RPC.
Stun and startle mechanics
Startle
SendZombieStartle is triggered when a zombie takes damage from a source it was not already targeting. The startle animation plays briefly before the zombie transitions to CHASE state. The startle byte parameter selects the animation variant.
Stun
SendZombieStun is triggered by explosive damage, flashbangs, or environmental hazards. The stun byte parameter encodes the stun duration (in frames or seconds). While stunned, the zombie:
- Stops all movement and navigation.
- Plays the stun animation.
- Cannot attack or use special abilities.
- Recovers after the stun duration expires.
Per-region zombie limits
Each navmesh bound (via FlagData) configures:
csharp
byte maxZombies; // Default 64
bool spawnZombies; // Can zombies spawn here?
int maxBossZombies; // Boss count limit (-1 = no limit)The regionsWithPlayers hash set tracks which regions have active players. Only regions in this set process zombie spawning and AI ticking. The maxZombies cap per region prevents one area from accumulating all zombies.
Mega zombie spawn cooldown
csharp
regions[zombie.bound].lastMega = Time.realtimeSinceStartup;
regions[zombie.bound].hasMega = false;When a mega zombie dies, the region tracks the death time in lastMega and clears hasMega. A new mega zombie cannot spawn in that region until a cooldown period has elapsed. The cooldown prevents mega zombies from being farmed for loot too quickly.
Zombie loot resolution chain
The dropLoot method uses a two-path resolution:
if (LevelZombies.tables[type].lootID != 0)
path = SPAWN_TABLE_TOOL;
else if (LevelZombies.tables[type].lootIndex < LevelItems.tables.Count)
path = LEGACY_ITEM_TABLE;
else
path = NO_LOOT;The spawn table tool path uses SpawnTableTool.ResolveLegacyId which supports GUID-based redirectors. The legacy path uses LevelItems.getItem which is a weighted random draw from the legacy item spawn table.
Horde mode specific behavior
When Level.info.type == ELevelType.HORDE:
SendInitialGlobalStatesends the current wave state (waveReady,waveIndex).- The
lastWavetimer controls auto-advancement to the next wave. - Zombies only spawn at horde beacon spawnpoints.
- Wave completion triggers:
_waveIndex++.- New zombie batch spawns with increased difficulty.
- Loot rewards are distributed to wave participants.
- Boss zombies have increased spawn weight in later waves.
Zombie slot data loading
The slot system (ZombieSlot) supports weighted random clothing:
csharp
for (byte slotIndex = 0; slotIndex < slotCount; slotIndex++)
{
float chance = block.readSingle(); // Probability this slot is filled
byte clothCount = block.readByte();
List<ZombieCloth> cloths = new List<ZombieCloth>();
for (byte clothIndex = 0; clothIndex < clothCount; clothIndex++)
{
ushort item = block.readUInt16();
ItemAsset asset = Assets.find(EAssetType.ITEM, item) as ItemAsset;
if (asset == null) continue;
cloths.Add(new ZombieCloth(item));
}
slots[slotIndex] = new ZombieSlot(chance, cloths);
}Missing clothing items are silently skipped (not added to the clothing list). This prevents broken visuals when clothing assets are missing.
Example: Getting zombies in a region
csharp
byte bound;
if (LevelNavigation.tryGetBounds(player.transform.position, out bound))
{
List<Zombie> nearbyZombies = new List<Zombie>();
ZombieManager.getZombiesInRadius(player.transform.position, 10000, nearbyZombies);
// nearbyZombies now contains all zombies within 100m in the same nav bound
}Example: Triggering a custom zombie wave
csharp
// Manually trigger a wave for a horde beacon
if (ZombieManager.waveReady)
{
// The wave system will auto-spawn zombies at beacon locations
// Custom wave behavior can be added via onWaveUpdated
}Example: Forcing loot from a zombie
csharp
// After a zombie dies, the dropLoot method is called automatically
// To add custom loot:
ZombieManager.dropLoot(zombie);
// After calling dropLoot, the zombie's table decides what items are dropped