A comprehensive SourceMod plugin for Left 4 Dead 2 that ensures reliable and balanced Special Infected (SI) spawn rotation in competitive gameplay, solving the inherent unpredictability of the AI Director's spawn system.
The AI Director in Left 4 Dead 2 uses an unreliable spawn rotation system that can create unfair advantages in competitive scenarios. This plugin implements a queue-based FIFO system with priority handling to guarantee consistent and predictable SI spawns while respecting configured limits and dominator rules.
- SourceMod 1.11+ - Plugin framework
- Left4DHooks - L4D2 game integration and hooks
- l4d2_dominatorscontrol.sp (recommended) - For advanced dominator configuration
- Versus
- Scavenge
- Any mode with player-controlled infected
- Download the latest release from Releases
- Extract to your
addons/sourcemod/directory - Restart the server or reload the plugin
addons/sourcemod/
โโโ plugins/
โ โโโ l4d2_fix_spawn_order.smx
โโโ scripting/
โ โโโ l4d2_fix_spawn_order.sp
โ โโโ fix_spawn_order/
โ โ โโโ fso_config.sp
โ โ โโโ fso_queue_limits.sp
โ โ โโโ fso_events.sp
โ โ โโโ fso_api.sp
โ โโโ include/
โ โโโ fix_spawn_order.inc
In the competitive ZoneMod configuration of Left 4 Dead 2, the Special Infected spawn system is controlled and predictable. All playable Special Infected are numbered from 1 to 6 in a fixed order:
| Index | Class | Role |
|---|---|---|
| 1 | Smoker | Long-range grabber |
| 2 | Boomer | AOE debuffer |
| 3 | Hunter | High-mobility damage dealer |
| 4 | Spitter | Area denial |
| 5 | Jockey | Mobility controller |
| 6 | Charger | Displacement grabber |
- Round Start: The Director randomly selects four of these six classes for the "first hit" wave
- Queue Formation: The remaining two classes are queued for later use, ordered by their numerical index
- Cyclical Selection: If the Director starts from index 4 (Spitter), the first wave includes:
- Spitter (4) โ Jockey (5) โ Charger (6) โ Smoker (1)
- Death Re-queue: When an SI dies, their class is added to the end of the queue
- Predictable Rotation: The composition rotates in a fixed pattern that can be completely predicted by tracking death order
This logic is implemented in l4d2_fix_spawn_order.sp, which uses an internal queue to manage the SI sequence reliably.
In normal gameplay, Left 4 Dead 2 restricts to a maximum of 3 "dominator" infected (those capable of incapacitating survivors: Smoker, Hunter, Jockey, Charger) simultaneously. The game engine marks these four classes as dominators and prevents more than three from spawning at once (any attempt to spawn a fourth would result in "OverLimit_Dominator").
ZoneMod allows the possibility of "Quad-Cap" - where all four spawns can be grabbing infected simultaneously (all four survivors could be trapped at once). This is achieved by modifying the internal limitation through the l4d2_dominatorscontrol.sp plugin.
How it works:
- Dominator Flag Control: The plugin allows changing the
bIsDominatorflag for each SI class - Bitmask Configuration: Uses ConVar
l4d2_dominatorswith a bitmask to indicate which classes count as dominators- Default "53": Binary
110101= Smoker(1) + Hunter(1) + Jockey(1) + Charger(1), excluding Boomer(0) and Spitter(0) - Setting to "0": No class is treated as a dominator
- Default "53": Binary
- Memory Modification: ZoneMod effectively uses this technique to eliminate the 3-grabber limit
- Individual Limits Preserved: While respecting individual class limits (e.g., max 2 Hunters), the Director no longer blocks four-grabber combinations
Result: As the plugin author describes: "Changes bIsDominator flag... Allows to have native-order quad-caps" - enabling quad-caps in the native spawn order.
Competitive matches typically feature 4v4 gameplay, but ZoneMod also supports 3v3 modes and other incomplete team situations.
For 3v3 matches, the official ZoneMod configuration makes the following adjustments:
survivor_limit 3 // Only 3 survivors
z_max_player_zombies 3 // Only 3 infected per wave
z_versus_boomer_limit 0 // Disable Boomer
z_versus_spitter_limit 0 // Disable SpitterRationale: With one fewer infected, Boomer and Spitter are disabled to avoid "wasting" a slot on classes that cannot incapacitate survivors alone. This ensures that all three infected spawns are always grabbers (e.g., 1 Smoker, 1 Charger, 1 Hunter/Jockey), maintaining appropriate threat level with fewer players.
The l4d2_fix_spawn_order.sp plugin includes special handling for these scenarios:
- Automatic Detection: The plugin automatically detects team size via
z_max_player_zombies.IntValue - Queue Size Scaling: Minimum queue size adapts to team composition
- Initial Wave Adjustment: When the infected team isn't full, the system reduces maximum simultaneous infected and redefines initial rotation
- Invalid Combination Prevention: Avoids invalid combinations or empty slots
Code Implementation:
// Dynamic team size calculation
int teamBasedQueueSize = z_max_player_zombies.IntValue / 2;
int minQueueSize = (teamBasedQueueSize > MIN_QUEUE_SIZE) ? teamBasedQueueSize : MIN_QUEUE_SIZE;These improvements ensure spawn order compatibility even with reduced team sizes, maintaining the philosophy of balanced and predictable gameplay regardless of whether teams are complete or incomplete.
l4d2_fix_spawn_order.sp # Main coordinator with logging system
โโโ fso_config.sp # Configuration and state management
โโโ fso_queue_limits.sp # Queue operations and limit validation
โโโ fso_events.sp # Player event handling and state tracking
โโโ fso_api.sp # Native/forward API implementation
graph TD
A[Round Start] --> B[Initialize Queue]
B --> C[Fill Queue with SI Classes]
C --> D[Player Spawns]
D --> E[Pop Class from Queue]
E --> F[Validate Limits]
F --> G{Limits OK?}
G -->|Yes| H[Assign Class]
G -->|No| I[Try Next in Queue]
I --> F
H --> J[Player Dies/Leaves]
J --> K[Re-queue Class]
K --> D
// Individual class limits (set by gamemode configs)
z_versus_smoker_limit "1" // Maximum Smokers
z_versus_boomer_limit "1" // Maximum Boomers
z_versus_hunter_limit "1" // Maximum Hunters
z_versus_spitter_limit "1" // Maximum Spitters
z_versus_jockey_limit "1" // Maximum Jockeys
z_versus_charger_limit "1" // Maximum Chargers
// Dominator configuration (requires l4d2_dominatorscontrol)
l4d2_dominators "53" // Bitmask: 53 = Smoker|Hunter|Jockey|ChargerThe plugin supports configurable dominator classes through a bitmask system, enabling or disabling quad-cap functionality:
| Class | Bit Position | Binary Value | Decimal | Role in Quad-Caps |
|---|---|---|---|---|
| Smoker | 1 | 000001 |
1 | Primary grabber |
| Boomer | 2 | 000010 |
2 | AOE debuff (non-grabber) |
| Hunter | 3 | 000100 |
4 | High-mobility grabber |
| Spitter | 4 | 001000 |
8 | Area denial (non-grabber) |
| Jockey | 5 | 010000 |
16 | Mobility grabber |
| Charger | 6 | 100000 |
32 | Displacement grabber |
// Standard ZoneMod (allows quad-caps)
l4d2_dominators "53" // Binary: 110101 = Smoker + Hunter + Jockey + Charger
// Result: 1+4+16+32 = 53 (4 grabbers as dominators)
// Disable quad-caps (vanilla L4D2 behavior)
l4d2_dominators "53" // Keep dominators, max 3 simultaneous
// + ensure l4d2_dominatorscontrol enforces limits
// No dominator limits (experimental)
l4d2_dominators "0" // Binary: 000000 = No classes as dominators
// Result: Unlimited grabbers (not recommended)
// 3v3 Configuration (grabbers only)
l4d2_dominators "53" // Same bitmask
z_versus_boomer_limit "0" // Disable non-grabbers
z_versus_spitter_limit "0" // Force grabber-only spawnsTo create a custom dominator configuration:
- Choose desired dominator classes
- Add their decimal values:
- Smoker + Hunter + Jockey = 1 + 4 + 16 = 21
- All grabbers = 1 + 4 + 16 + 32 = 53 (ZoneMod default)
- All classes = 1 + 2 + 4 + 8 + 16 + 32 = 63
Pro Tip: Use binary representation to visualize: 53 = 110101 shows positions 1,3,5,6 are set (Smoker, Hunter, Jockey, Charger).
The plugin provides several administrative commands for managing spawn behavior and safe area control:
// Force survivors to be considered as having left safe area
sm_fso_force_safearea_exit
// Reset safe area status (disable bot spawning)
sm_fso_reset_safearea
// Check current safe area status
sm_fso_check_safeareaBy default, the plugin prevents infected bots from spawning until survivors leave the safe area. This prevents immediate bot spawns at round start and ensures proper game flow.
Command Examples:
// Admin forces early bot spawning
] sm_fso_force_safearea_exit
[FSO] Safe area status set to: Left (Bot spawning enabled)
// Admin disables bot spawning temporarily
] sm_fso_reset_safearea
[FSO] Safe area status set to: In Safe Area (Bot spawning disabled)
// Check current status
] sm_fso_check_safearea
[FSO] Safe Area Status:
Plugin tracking: Left
Game state: Left
Bot spawning: Enabled
The plugin continuously monitors safe area status and provides detailed logging:
[SO][Events] Round started - Survivors left safe area: Yes
[SO][Events] First survivor PlayerName left safe area - Bot spawning now enabled
[SO][Events] Admin PlayerName forced safe area exit - Bot spawning enabled
-
Different Game Modes:
- 4v4 Versus (standard competitive)
- 3v3 Versus (reduced team size)
- 8v8 modes (if supported)
-
Dominator Configurations:
- Standard ZoneMod (
l4d2_dominators "53") - Verify quad-caps work - Vanilla L4D2 (
l4d2_dominators "53"with limit enforcement) - Max 3 grabbers - Custom configurations - Test specific class combinations
- Standard ZoneMod (
-
Edge Cases:
- Player disconnections during spawns
- Tank replacement scenarios
- Rapid rebalances and limit changes
- Safe area admin command interactions
-
Performance Testing:
- Multiple consecutive rounds
- High player turnover scenarios
- Debug logging overhead verification
Deploy l4d2_fix_spawn_order_test.sp alongside the main plugin and use the available commands to test quad-cap functionality and other features. Monitor test results in chat and console logs for verification.
When using the companion test plugin (l4d2_fix_spawn_order_test.sp):
// Start comprehensive API testing
sm_fso_test_start
// Stop continuous testing
sm_fso_test_stop
// Test specific components
sm_fso_test_natives // Test all native functions
sm_fso_test_queue // Test queue operations
sm_fso_test_players // Test player operations
sm_fso_test_state // Test game state
sm_fso_test_safearea // Test safe area functionality
sm_fso_test_all // Run complete test suiteSymptom: Players spawning as random classes
Cause: Queue exhausted without refill
Solution: Check limit configuration and dominator settings
Symptom: "Dominator limit" or "Class limit" in logs
Cause: Configuration mismatch between static and dynamic limits
Solution: Verify gamemode config matches server settings
Symptom: Players not getting proper classes after Tank
Cause: Tank replacement logic not handling class restoration
Solution: Update to latest version with improved Tank handling
Enable debug logging to diagnose issues:
// Set DEBUG flags to 1 and recompile
#define DEBUG 1
#define DEBUG_QUEUE 1 // For queue issues
#define DEBUG_LIMITS 1 // For limit problems
#define DEBUG_EVENTS 1 // For player event issues[SO][Queue] Filled queue (Smoker, Hunter, Boomer, Spitter, Jockey, Charger)
[SO][Queue] Popped (Hunter) after 1 try
[SO][Limits] Popping (Smoker) but over limit (reason: Dominator limit)
[SO][Events] Player connected to infected team as (Hunter)
- Sir - Original concept and implementation
- Forgetest - Core algorithm development and optimization
- lechuga - Modular refactoring and API design
- AlliedModders - SourceMod framework and community support
- L4D2 Competitive Community - Testing, feedback, and feature requests
- SirPlease - L4D2-Competitive-Rework project integration
- โ Complete modular refactoring - Separated into logical components
- โ Fixed IsDominator function - Corrected bitmask evaluation for proper quad-caps
- โ Dynamic team size adaptation - Automatically scales to 3v3, 4v4, and other configurations
- โ Initialization order correction - Config โ Queue โ API for reliable startup
- โ Detailed queue composition logging - Shows "2xSmoker, 1xHunter, 1xCharger" format
- โ Improved configuration reporting - "Infected: 2 humans + 2 bots = 4/8"
- โ Administrative commands - Safe area control and status checking
- โ Comprehensive test suite - API testing plugin with 8 test categories
- โ Modern GlobalForward methodmap system - Future-proof API design
- โ 9 natives and 12 forwards - Complete API coverage for integration
- โ Categorized debug logging - Queue, Limits, Events, Rebalance categories
- โ Memory optimization - Efficient string handling and reduced allocations
- โ Basic queue-based spawn ordering
- โ Simple limit validation
- โ Core dominator support
- ZoneMod Documentation - Official competitive configuration
- L4D2 SI Class Reference - Valve's official class documentation
- L4D2-Competitive-Rework - Complete competitive L4D2 server package including ZoneMod
- Left4DHooks - Essential L4D2 SourceMod extension for game integration
- l4d2_dominatorscontrol - Advanced dominator class configuration for quad-caps