ItemBeaconAsset — Horde Beacon Wave Mechanics
Overview
ItemBeaconAsset extends ItemBarricadeAsset and represents horde beacon items. A horde beacon is a placed barricade that, when activated, spawns waves of enemies for PvE challenge events. The beacon defines a wave tier (determining enemy type and count), a reward count, a reward spawn table reference, and a participant scaling flag that adjusts difficulty based on how many players are present.
Beacons are a core PvE mechanic in vanilla Unturned — players craft or find a beacon, place it in a defensible location, activate it, and fight waves of zombies or other enemies. After all waves are defeated, rewards are distributed to surviving participants.
Inheritance Chain
Asset
└── ItemAsset
└── ItemBarricadeAsset
└── ItemBeaconAssetBeacons are barricades, not held items. Once placed, they occupy world space, have health, can be damaged by enemies, and are subject to barricade save/load and networking rules. The beacon inherits all barricade properties: placement requirements, ownership, salvage, and the barricade destruction pipeline.
Fields
Wave (_wave)
csharp
private ushort _wave;
public ushort wave => _wave;The wave tier number. This determines which enemy types spawn, how many spawn per wave, and potentially the total number of waves. The wave tier is used as an index into the game mode's wave configuration table.
Parsed from .dat key Wave:
csharp
_wave = p.data.ParseUInt16("Wave");The wave tier is a ushort with range 0-65535. Practical values are typically 1-10. The exact meaning of each tier is defined by the game mode config:
| Tier | Vanilla Example |
|---|---|
| 1 | 5-10 normal zombies, 1 wave |
| 2 | 10-15 normal zombies, 2 waves |
| 3 | 15-20 mixed zombies, 3 waves, 1 mega |
| 4 | 20-30 mixed zombies, 4 waves, 2 megas |
| 5 | 30-50 zombies, 5 waves, 3+ megas, special enemies |
The wave tier is the STARTING wave, not the total wave count. A beacon with Wave 3 starts at wave 3 of the configured wave sequence. The sequence may define waves 1-5; starting at 3 skips waves 1 and 2 and begins at the harder waves.
Rewards (_rewards)
csharp
private byte _rewards;
public byte rewards => _rewards;The number of reward rolls granted after all waves are defeated. Each roll independently calls SpawnTableTool.Resolve(rewardID) to select a random item from the reward spawn table.
Parsed from .dat key Rewards:
csharp
_rewards = p.data.ParseUInt8("Rewards");As a byte, the max reward count is 255. Each reward roll is independent — two rolls can produce the same item. If _rewards is 3, the player receives 3 items from the reward table (with possible duplicates).
Reward ID (_rewardID)
csharp
private ushort _rewardID;
public ushort rewardID => _rewardID;The spawn table ID for reward items. After all waves are defeated, the beacon calls SpawnTableTool.Resolve(rewardID) for each reward roll.
Parsed from .dat key Reward_ID:
csharp
_rewardID = p.data.ParseUInt16("Reward_ID");If _rewardID is 0, no rewards are granted — the beacon is purely for challenge/prestige with no item payout. If _rewards is also 0, completing the beacon has no reward effect beyond clearing the placed barricade.
ShouldScaleWithNumberOfParticipants
csharp
public bool ShouldScaleWithNumberOfParticipants { get; private set; }When true, the enemy count per wave scales up based on the number of players within the beacon's activation radius. When false, the beacon uses a fixed enemy count regardless of how many players participate.
Parsed from .dat key Enable_Participant_Scaling:
csharp
ShouldScaleWithNumberOfParticipants = p.data.ParseBool("Enable_Participant_Scaling", defaultValue: true);The default is true — beacons scale by default. Setting Enable_Participant_Scaling false fixes the difficulty at the base values.
Beacon Activation Flow
Phase 1: Placement
- The player places the beacon barricade in the world (standard barricade placement).
- The beacon enters a "ready" state — it can be activated but hasn't started.
- The beacon shows a UI prompt (e.g., "Press F to activate beacon").
Phase 2: Activation
- A player interacts with the beacon (presses use key).
- A confirmation prompt may appear (configurable per game mode).
- On confirmation, the beacon transitions to "active" state.
Phase 3: Pre-Wave Delay
- A configurable delay elapses before the first wave spawns (typically 10-30 seconds).
- During the delay, players can reposition, build defenses, or prepare equipment.
- A countdown timer displays to all participants.
Phase 4: Wave Spawning Loop
For each wave in the sequence (starting from _wave):
Enemy count calculation:
baseEnemyCount = waveConfig[waveTier].enemyCount; if (ShouldScaleWithNumberOfParticipants) { participantCount = countPlayersInRadius(beacon.position, activationRadius); multiplier = 1.0 + (participantCount - 1) * scalingFactor; finalEnemyCount = Mathf.RoundToInt(baseEnemyCount * multiplier); } else { finalEnemyCount = baseEnemyCount; }Enemy spawning: For
i = 0; i < finalEnemyCount; i++:- Select a random spawn position within the beacon's spawn radius.
- Ensure the position has valid navmesh (enemies can pathfind from it).
- Spawn the enemy type defined by the wave tier.
- Apply any health/stat modifications from participant scaling.
Wave active phase: All enemies must be defeated for the wave to complete.
- The beacon checks the enemy count each frame (or on enemy death events).
- If all enemies are dead, the wave completes.
- If a timer expires and enemies remain, the wave may auto-complete or fail (config-dependent).
Inter-wave delay: A brief delay between waves (typically 5-15 seconds) for players to heal and reload.
Loop: If
waveTier < maxWaveTier, increment and repeat from step 1. If this was the final wave, proceed to reward phase.
Phase 5: Reward Distribution
- All waves defeated — the beacon transitions to "completed" state.
- For
i = 0; i < _rewards; i++:SpawnTableTool.Resolve(_rewardID)selects a random reward item.- The item is force-given to each participant (or distributed round-robin depending on game mode config).
- Quest conditions tied to the beacon ("complete a beacon of wave tier X") are updated for each participant.
Phase 6: Beacon Destruction
- The beacon barricade is destroyed (same as a player dismantling or an enemy destroying it).
- If the barricade has a salvage table, salvage items may drop.
- The beacon's placement spot is freed for new barricades.
Participant Scaling — Detailed Algorithm
The scaling formula adjusts enemy count based on participant count:
finalCount = baseCount * (1.0 + (participantCount - 1) * scalingFactor)Where:
baseCount: The base enemy count for the wave tier from game mode config.participantCount: Number of players within the beacon's activation radius.scalingFactor: Configurable multiplier from game mode config (typically 0.25 to 1.0).
Example Calculations
Scaling factor = 0.5, base count = 10:
| Participants | Multiplier | Final Count |
|---|---|---|
| 1 | 1.0 | 10 |
| 2 | 1.5 | 15 |
| 3 | 2.0 | 20 |
| 4 | 2.5 | 25 |
| 5 | 3.0 | 30 |
Each additional player adds baseCount * scalingFactor enemies (e.g., 5 more per player at scaling factor 0.5).
Scaling factor = 0.25, base count = 10:
| Participants | Multiplier | Final Count |
|---|---|---|
| 1 | 1.0 | 10 |
| 2 | 1.25 | 12 |
| 3 | 1.5 | 15 |
| 4 | 1.75 | 17 |
| 5 | 2.0 | 20 |
With a lower scaling factor, adding players increases difficulty more gently.
Participant Count Determination
Players are counted if they are within a configurable radius of the beacon at the start of each wave. The count is typically snapshotted when the wave begins — players joining or leaving during a wave do not change the enemy count for that wave, but the next wave re-counts.
Health Scaling
In addition to count scaling, enemy health may scale with participant count:
finalHealth = baseHealth * (1.0 + (participantCount - 1) * healthScalingFactor)Health scaling is defined in game mode config, not in the asset. The ShouldScaleWithNumberOfParticipants flag gates both count and health scaling.
Enemy Spawning Details
Position Selection
Enemies spawn at random positions within a spawn radius centered on the beacon:
- A random angle and distance are selected (polar coordinates).
- The resulting world position is checked for validity:
- Must be on navmesh (enemies can pathfind from it).
- Must not be inside geometry (walls, terrain).
- Must not be within a minimum distance of any player (spawn camping prevention).
- Must not be within a minimum distance of the beacon itself.
- If the position is invalid, a new position is retried (up to a configurable max attempts).
- If no valid position is found after max attempts, the enemy spawns at the edge of the spawn radius (fallback position).
Spawn Staggering
Enemies in a wave do not spawn simultaneously. A spawn interval (typically 0.5-2 seconds between enemies) staggers the spawns to prevent all enemies popping in at once and to spread out the initial aggro.
Enemy Type Selection
The wave tier determines the enemy type:
- Tier 1-2: Normal zombies only.
- Tier 3-4: Mixed normal zombies and mega zombies.
- Tier 5+: Mega zombies, fire zombies, spitter zombies, flamers, and other special enemy types.
- Custom game modes can define any enemy type per tier, including modded enemies.
The _wave field is an index into the tier configuration. If the game mode defines 5 tiers but the beacon specifies Wave 7, the behavior depends on bounds-checking:
- Clamped: treated as tier 5 (the max available).
- Extended: treated as tier 7 with default enemy types.
- Error: the beacon fails to activate.
Vanilla game modes clamp; modded modes may extend or error.
Wave Completion Detection
The system tracks active enemies spawned by the beacon. When an enemy dies:
- The beacon's active enemy counter decrements.
- If the counter reaches zero, the wave is complete.
- If enemies despawn (teleport away, unload due to distance), they may not count as "defeated." The wave may stall indefinitely if enemies unload without dying.
Most game mode configs include a despawn-timeout: enemies that haven't taken damage for N seconds are force-removed, counting them as defeated for wave progression.
Reward Distribution Models
The beacon's reward system supports several distribution models (configurable per game mode):
Per-Player Model (Default)
Each participant receives the full _rewards count. If _rewards is 3 and 4 players complete the beacon, each player receives 3 items (12 total items distributed). This is the most generous model and the vanilla default.
Split Model
The _rewards count is divided among participants. If _rewards is 8 and 4 players complete the beacon, each player receives 2 items. If the division has a remainder (e.g., 8 rewards / 3 players = 2 each with 2 remainder), the remaining rewards are:
- Assigned to the beacon activator.
- Assigned to the player who dealt the most damage.
- Randomly distributed.
- Lost (no one receives them).
Round-Robin Model
Rewards are assigned one at a time to each participant in turn. Participant order is determined by:
- Activation order (activator first, then others).
- Alphabetical player name.
- Random order.
MVP Model
A percentage of rewards goes to the top performer (most damage dealt, most enemies killed). The remaining rewards are distributed evenly or per-player.
The ItemBeaconAsset itself does not encode the distribution model — it only provides the _rewards count and _rewardID reference. The game mode is responsible for the distribution logic.
Network and Save Behavior
Network Replication
The beacon's state is replicated via the barricade networking system:
- Active/inactive state.
- Current wave number (displayed in UI).
- Enemies remaining count (displayed in UI).
- Time remaining in current wave or inter-wave delay.
Clients receive state updates at the barricade relevancy rate (typically every 0.5-2 seconds for non-critical state). The wave progress UI uses this data to show the wave counter and enemy countdown.
Save/Load Persistence
Active beacons are not persisted across server restarts. When a server restarts:
- Placed beacon barricades are saved (position, health, ownership).
- The "active" state is NOT saved — all beacons reset to "ready" state on load.
- Wave progress, enemy counts, and partial rewards are lost.
This is intentional: beacons are meant to be completed in a single session. A server restart during a beacon event resets the challenge.
Cargo Data Export
ItemBeaconAsset overrides BuildCargoData to export beacon-specific fields to the Beacon Cargo table:
csharp
internal override void BuildCargoData(CargoBuilder builder)
{
base.BuildCargoData(builder);
CargoDeclaration data = builder.GetOrAddDeclaration("Beacon");
data.Append("GUID", GUID); // Key
data.Append("Wave", wave);
data.Append("Rewards", rewards);
data.Append("Reward_ID", rewardID);
data.Append("Enable_Participant_Scaling", ShouldScaleWithNumberOfParticipants);
}The Beacon Cargo table exports:
GUID: Asset GUID (primary key).Wave: Starting wave tier.Rewards: Reward roll count.Reward_ID: Reward spawn table ID.Enable_Participant_Scaling: Whether participant scaling is enabled.
Modding Guide
Creating a Basic Horde Beacon
ID 58300
ItemName "Horde Beacon"
Rarity Rare
Size_X 2
Size_Y 2
Slot None
Wave 1
Rewards 3
Reward_ID 150This creates a tier-1 beacon that grants 3 reward rolls from spawn table 150. Participant scaling is on by default (the true default for Enable_Participant_Scaling).
Creating a Fixed-Difficulty Beacon
ID 58301
ItemName "Solo Challenge Beacon"
Wave 3
Rewards 5
Reward_ID 151
Enable_Participant_Scaling falseThis beacon spawns the same number of enemies whether 1 player or 10 players are present. Useful for solo challenges or leaderboard events where difficulty must be identical for all attempts.
Creating a Prestige Beacon (No Rewards)
ID 58302
ItemName "Proving Grounds Beacon"
Wave 5
Rewards 0
Reward_ID 0No rewards are granted — the beacon exists purely for the challenge. Quest conditions can still track completion.
Common Pitfalls
Default scaling without understanding: The default
Enable_Participant_Scaling truemeans a beacon tuned for solo play becomes overwhelming with a group. If you design a beacon for solo play, explicitly setEnable_Participant_Scaling false.Zero Rewards with non-zero Reward_ID: Setting
Rewards 0but providing aReward_IDmeans the spawn table is referenced but never resolved. No items are generated. This wastes the reward table reference but causes no errors.Reward_ID with no spawn table: If
Reward_IDreferences a spawn table that doesn't exist,SpawnTableTool.Resolvereturns null or a default. No rewards are granted. Validate spawn table IDs against the asset registry.Wave tier beyond game mode max: If the game mode defines 5 wave tiers and the beacon specifies
Wave 7, the behavior is game-mode-dependent. Test in the target game mode. In vanilla, the wave tier is clamped to the max available.Beacon placement in unreachable areas: If a beacon is placed where enemies cannot pathfind (e.g., on a rooftop with no access), enemies spawn but cannot reach players. The wave never completes because enemies never die. Always place beacons in areas with valid navmesh access.
Beacon destruction during event: If the beacon barricade is destroyed by enemies during the event, the event may cancel (enemies stop spawning, no rewards). Protect the beacon with barricades or ensure it has enough health to survive stray hits.
Participant leaving mid-event: If a player leaves the beacon radius or disconnects during the event, the participant count for the next wave adjusts. This can make a hard wave easier if several players leave — or harder if the scaling was compensating for the group size.
Wave Configuration and Tier Tables
Wave Tier Structure
Each wave tier in the game mode config defines a complete wave profile. The _wave field on the beacon selects the starting tier. A typical tier configuration:
WaveTier 1:
EnemyType: Zombie_Normal
EnemyCount: 8
WaveCount: 2
SpawnRadius: 30
InterWaveDelay: 10
SpawnInterval: 1.5
RewardMultiplier: 1.0
WaveTier 2:
EnemyType: Zombie_Normal, Zombie_Runner
EnemyCount: 12
WaveCount: 3
SpawnRadius: 35
InterWaveDelay: 8
SpawnInterval: 1.2
RewardMultiplier: 1.5
WaveTier 3:
EnemyType: Zombie_Normal, Zombie_Runner, Zombie_Mega
EnemyCount: 18
WaveCount: 4
SpawnRadius: 40
InterWaveDelay: 6
SpawnInterval: 1.0
RewardMultiplier: 2.0Sequential Tier Advancement
When a beacon starts at tier N, it plays through tiers N, N+1, N+2, ... up to the configured maximum. A beacon with Wave 2 starting in a mode with 5 tiers plays tiers 2, 3, 4, and 5 (4 waves total).
If the starting tier exceeds the maximum, the beacon fails to activate or clamps to the max. The exact behavior is game-mode-dependent.
Tier-Specific Enemy Composition
Higher tiers introduce harder enemy types:
- Tier 1-2: Normal zombies only.
- Tier 3: Mix of normal and runner zombies (runners are faster).
- Tier 4: Megas appear (high HP, high damage).
- Tier 5: Special enemies (fire zombies with AoE, spitters with ranged attacks, flamers that set terrain on fire).
Modded game modes can define custom tier compositions with any enemy type, including modded enemies.
Wave Count vs. Wave Tier
The _wave field is the STARTING tier, not the total wave count. The total number of waves is maxTier - startingTier + 1. A beacon with Wave 3 in a 5-tier mode plays 3 waves (tiers 3, 4, 5).
The wave count is implicitly defined by the game mode's tier table. Modders cannot set an arbitrary wave count without also defining the corresponding tiers. A beacon that should play 7 waves needs 7 defined tiers in the game mode config.
NavMesh and Spawn Validation
Spawn Position Selection Algorithm
Each enemy spawn position is chosen via rejection sampling:
Generate a random point within the spawn radius using polar coordinates:
angle = Random.Range(0, 360) distance = Random.Range(minSpawnDistance, spawnRadius) x = beacon.x + cos(angle) * distance z = beacon.z + sin(angle) * distance y = terrain.GetHeightAt(x, z)Validate the position:
- Must be on NavMesh (pathfinding reachable).
- Must not intersect with level geometry (walls, inside terrain).
- Must be at least
minPlayerDistancemeters from any player (spawn camping prevention). - Must be on walkable terrain (slope < maxSlopeAngle).
- Must not be inside water (unless the enemy type is aquatic).
If validation fails, retry with a new random point (up to
maxSpawnAttempts, typically 10-20).If all attempts fail, use the fallback position: the edge of the spawn radius in a random direction.
NavMesh Baking Requirements
For beacon spawning to work correctly, the level must have a baked NavMesh covering the beacon's spawn radius. Levels without proper NavMesh baking cause spawn failures. The fallback positions (spawn radius edge) may be off-NavMesh, causing enemies to spawn in place without pathfinding.
Modders creating custom levels should ensure:
- NavMesh covers all areas within beacon spawn radius.
- NavMesh includes walkable surfaces around common beacon placement locations.
- NavMesh obstacle carve is enabled for dynamic barricades near beacons.
- NavMesh links connect separated walkable areas (rooftops, platforms).
Performance: Mass Spawn Events
A tier-5 beacon with 4 players (scaling factor 0.5, base count 30) spawns 30 * (1 + 3*0.5) = 75 enemies per wave. Each spawn performs NavMesh validation (raycast + sample position), which involves physics queries.
At 75 spawns with up to 20 retries each, the worst case is 1,500 NavMesh queries. Each query costs ~0.01-0.05ms, so total spawn setup is ~15-75ms. This is a one-time cost at wave start, not per-frame. The game typically staggers spawns over 1-2 seconds to spread this cost.
Beacon Event Lifecycle Events
Game modes can hook into beacon lifecycle events for custom behavior:
OnBeaconPlaced(beacon): Fired when a beacon barricade is placed in the world.OnBeaconActivated(beacon, activator): Fired when a player activates the beacon.OnWaveStarted(beacon, waveIndex): Fired at the start of each wave.OnEnemySpawned(beacon, enemy, waveIndex): Fired for each enemy spawned.OnEnemyKilled(beacon, enemy, killer, waveIndex): Fired for each enemy death.OnWaveCompleted(beacon, waveIndex): Fired when all enemies in a wave are dead.OnBeaconCompleted(beacon, participants): Fired when all waves are done.OnRewardsDistributed(beacon, participants, rewards): Fired after rewards are granted.OnBeaconDestroyed(beacon, reason): Fired when the beacon barricade is destroyed.
These events enable custom plugins to:
- Announce beacon starts/completions in chat.
- Grant custom rewards beyond the spawn table.
- Track player statistics (beacons completed, enemies killed).
- Trigger world events (boss spawns, map changes) on beacon milestones.
- Integrate with faction/territory systems (beacons in claimed territory give bonus rewards).
