Left 4 Dead 2/Scripting
Left 4 Dead 2 vscripts are Squirrel language-based scripts that can be run in-game. For examples, please see L4D2 Vscript Examples.
Description
Left 4 Dead 2 vscripts are written in Squirrel, a compilable scripting language similar to Lua. The file extensions are .nut and .nuc, where .nuc denotes encryption of plain text .nut.
Usage
- Director manipulation - onslaughts, CI wanderer options, complete emptiness/silence, prohibition of boss infected (tanks and witches), direction of mobs, specific spawning behavior, etc.
- Finale - Custom sequences of events, Gauntlet and Scavenge Logic
- Complex entity scripting - entity I/O, logic, arithmetic, loops, counters, timers, prop spawning, etc. (Example: Dark Carnival mini-games)
- Game modes such as Mutations and Scavenge
- Miscellaneous!
Location
- Official .nuc script files are located in scripts/vscripts in multiple locations
- left 4 dead 2\left4dead2\pak01_dir.vpk
- left 4 dead 2\left4dead2_dlc1\pak01_dir.vpk
- April 22, 2010 The Passing update
- left 4 dead 2\left4dead2_dlc2\pak01_dir.vpk
- October 5, 2010 The Sacrifice update
- left 4 dead 2\left4dead2_dlc3\pak01_dir.vpk
- March 22, 2011 Cold Stream Beta / L4D1 Transition Project update
- left 4 dead 2\update\pak01_dir.vpk
- This is where mutations were updated/replaced bi-weekly.
- left 4 dead 2\sdk_content\scripting\scripts\vscripts\
- Plaintext versions of many of the scripts introduced in the EMS update.
Decrypting NUC files
.nuc files are ICE encrypted .nut files. The encryption key is SDhfi878. You can use VICE to decode them.
Packages with deciphered official scripts are also available.
Loading vscripts
- Entities
- Any entity is capable of loading a script on map spawn.
- The vscript can be reloaded with console command
ent_fire <name of entity> runscriptfile <relative vscript path>
. This is useful for quick script reloading. - Script functions can be called with entity inputs like
RunScriptCode
. - Features thinkfunction, a keyvalue that calls a user-defined function every 0.1 seconds. While it has the potential to become expensive, a programmer is able to limit the amount of code executed. The shoot gallery in Dark Carnival relies on this.
- Some functions are specialized for certain entity classes, such as point_template and env_entity_maker.
- Also available is logic_script, an entity that registers multiple entities as an array EntityGroup.
- info_director
- Loads DirectorOptions such as onslaughts, panic events, infected limits, etc.
- Custom DirectorOptions are used frequently to tweak director behavior that best suits the environment. c1_mall_ambient.nut is one such example.
- Uses script scope
DirectorScript

ScriptedPanicEvent
will not work if they are in a subdirectory, even though you can use subdirectories in other script contexts. They must reside under the vscripts folder, or they will simply act as a 1 stage 1 second delay.- Game Modes
- Mode Specific Script
- Will automatically run the script with the same name as the game mode.
- For example, running a map
map <map name> mutation12
will automatically load the Realism Versus script, mutation12.nuc. - Uses script scope
g_ModeScript
Note:Adding a script for a mode will enable Scripted Mode on it. This works on the base modes as well, and will enable EMS game event callbacks and hooks.
Todo: Does this have any other consequences?
- Map Specific Script
- Per-map scripts can be created that run when the map is loaded in the specified game mode.
- The script naming syntax is <map name>_<mode name>.nut.
- For example, c1m4_atrium_mutation12.nut.
- Uses script scope
g_MapScript
- Mode Specific Script
- Finale
- Loaded via trigger_finale, either automatically or pointing to a specific vscript.
- Automatically loads <map name>_finale.nut script on finale start.
- Scavenge, Custom, and Gauntlet finales are dependent on vscripts.
- Uses script scope
DirectorScript
Example scripts
Please see L4D2 Vscript Examples.
Scripting environment
Please see List of L4D2 Script Functions for built in classes and functions.
Table structure
DirectorScript = //Director scripts get loaded here.
{
DirectorOptions //Active when a director script is, and during scripted events and finales.
MapScript = //Initial values for g_MapScript
{
BaseScriptedDOTable //Hardcoded DirectorOptions.
ChallengeScript = //Initial values for g_ModeScript.
{
MutationState //Populated whith the the mode and map names.
}
LocalScript =
{
DirectorOptions //Current DirectorOptions(?).
}
}
}
g_MapScript = //Map specific scripts get loaded here.
{
MapOptions //Intial values for SessionOptions.
MapState //Intial values for SessionState.
}
g_ModeScript = //Mode specific scripts get loaded here (Scripted mode only).
{
DirectorOptions //Current Director options, outside scripted events.
MutationState //Intial values for SessionState.
MutationOptions //Intial values for SessionOptions.
}
g_rr
g_RoundState
SessionOptions //Global Director options (Scripted mode only).
SessionState //State variables for game modes (Scripted mode only).
Director scripts

Warning:NEVER use the = operator when trying to influence the director. Use <-. The difference is semantics. If you use =, it will throw an error if the table slot isn't defined (like if a previous script didn't define it). <-, on the other hand, will create the variable if it does not exist.
- Director Enumerations
Note:These are (or some are) script specific, hence the duplicate values.
- FINALE_CUSTOM_DELAY = 10
- FINALE_CUSTOM_PANIC = 7
- FINALE_CUSTOM_SCRIPTED = 9
- FINALE_CUSTOM_TANK = 8
- FINALE_FINAL_BOSS = 5
- FINALE_GAUNTLET_1 = 0
- FINALE_GAUNTLET_2 = 3
- FINALE_GAUNTLET_BOSS = 15
- FINALE_GAUNTLET_BOSS_INCOMING = 14
- FINALE_GAUNTLET_ESCAPE = 16
- FINALE_GAUNTLET_HORDE = 12
- FINALE_GAUNTLET_HORDE_BONUSTIME = 13
- FINALE_GAUNTLET_START = 11
- FINALE_HALFTIME_BOSS = 2
- FINALE_HORDE_ATTACK_1 = 1
- FINALE_HORDE_ATTACK_2 = 4
- FINALE_HORDE_ESCAPE = 6
- SPAWN_ABOVE_SURVIVORS = 6
- SPAWN_ANYWHERE = 0
- SPAWN_BEHIND_SURVIVORS = 1
- SPAWN_FAR_AWAY_FROM_SURVIVORS = 5
- SPAWN_IN_FRONT_OF_SURVIVORS = 7
- SPAWN_LARGE_VOLUME = 9
- SPAWN_NEAR_IT_VICTIM = 2
- SPAWN_NO_PREFERENCE = -1
- SPAWN_SPECIALS_ANYWHERE = 4
- SPAWN_SPECIALS_IN_FRONT_OF_SURVIVORS = 3
- SPAWN_VERSUS_FINALE_DISTANCE = 8
- ZOMBIE_TANK = 8
- ZOMBIE_WITCH = 7
Director options
This is by no means an exhaustive list of all the director options. Additions and updates can be found in the Steam Forums
- A_CustomFinaleX = stage type (enumerated PANIC, ONSLAUGHT, DELAY, TANK); where X is the stage number. For scripted panic event or custom finale.
- A_CustomFinaleValueX = Value depends on the stage type. Please see example article or official decrypted scripts. For scripted panic event or custom finale.
- AllowCrescendoEvents = ??? (not sure what this does)
- AllowWitchesInCheckpoints = ??? (not sure what this does)
- AlwaysAllowWanderers = true|false
- BehindSurvivorsSpawnDistance = Appears to require PreferredSpecialDirection = SPAWN_BEHIND_SURVIVORS
- BileMobSize = number of commons that spawn when a bile bomb is thrown. Appears to only work in finale. Found in Dead Center and The Sacrifice finales.
- BoomerLimit = maximum number of boomers allowed
- BuildUpMinInterval
- ChargerLimit = maximum number of chargers allowed
- ClearedWandererRespawnChance = percent chance (0-100) that cleared nav areas will get re-populated with wanderers.
- CommonLimit = maximum number of commons allowed
- DisallowThreatType = ZOMBIE_WITCH, ZOMBIE_TANK (other values???) Ex: c8m1_apartment.nut
- FallenSurvivorPotentialQuantity = int
- FallenSurvivorSpawnChance = float [0...1]
- DominatorLimit Todo: confirm category/working
- GasCansOnBacks = true|false
- HunterLimit = maximum number of hunters allowed
- IgnoreNavThreatAreas = ??? (not sure what this does)
- InfectedFlags = INFECTED_FLAG_CANT_SEE_SURVIVORS, INFECTED_FLAG_CANT_HEAR_SURVIVORS, INFECTED_FLAG_CANT_FEEL_SURVIVORS
- IntensityRelaxAllowWanderersThreshold
- IntensityRelaxThreshold = All survivors must be below this intensity before a Peak is allowed to switch to Relax (in addition to the normal peak timer)
- IntensityThreshold
- JockeyLimit = maximum number of jockeys allowed
- LockTempo = (0/1) Endless panics consist of: spawn horde -> BUILD_UP -> SUSTAIN_PEAK -> RELAX -> spawn horde again. Locktempo = 1 removes the "BUILD_UP -> SUSTAIN_PEAK -> RELAX" bit making your hordes spawn constantly without a delay.
- MaxSpecials = number of specials allowed at once
- MegaMobMaxSize = maximum megamob size
- MegaMobMinSize = minimum megamob size
- MegaMobSize Todo: confirm category/working
- MinimumStageTime = in seconds Ex: c1m4_delay.nut Todo: possibly finale or custom panic event specific
- MobMaxPending = Guessing it's the maximum mob size that can be pending for spawn.
- MobMaxSize = max mob size
- MobMinSize = min mob size
- MobRechargeRate = Guessing it's the speed at which a mob regenerates (ie next mob)
- MobSpawnMaxTime = max time in seconds for mob spawn
- MobSpawnMinTime = min time in seconds? for mob spawn
- MobSpawnSize
- MusicDynamicMobScanStopSize = When see fewer than this many of a mob, music stops
- MusicDynamicMobSpawnSize = ???Spawning a mob this large can play music
- MusicDynamicMobStopSize = When a mob gets to this size we think about stopping the music
- NumReservedWanderers = the number of infected that cannot be absorbed
- PanicForever * this seems to only work in gauntlets
- PanicWavePauseMax (float) Todo: confirm category/working
- PanicWavePauseMin (float) Todo: confirm category/working
- PreferredMobDirection = SPAWN_ABOVE_SURVIVORS, SPAWN_ANYWHERE, SPAWN_BEHIND_SURVIVORS, SPAWN_FAR_AWAY_FROM_SURVIVORS, SPAWN_IN_FRONT_OF_SURVIVORS , SPAWN_LARGE_VOLUME, SPAWN_NEAR_IT_VICTIM, SPAWN_NO_PREFERENCE

- PreferredSpecialDirection

SPAWN_SPECIALS_ANYWHERE
SPAWN_SPECIALS_IN_FRONT_OF_SURVIVORS- PreTankMobMax = int Todo: confirm category/working; possibly gauntlet specific
- ProhibitBosses = true|false - prohibit tanks/witches
- RelaxMaxFlowTravel = 600
- RelaxMaxInterval = 5
- RelaxMinInterval = 5
- ShouldAllowMobsWithTank = true|false
- ShouldAllowSpecialsWithTank = true|false
- ShouldConstrainLargeVolumeSpawn = true|false
- SmokerLimit = maximum number of smokers allowed
- SpawnDirectionMask = a bitfield (using SPAWNDIR_N, _NE, _E, etc) of directors to spawn from _relative to_ a map entity named "Compass" in your map. So the idea is that if you are making a mutation in a confined area, put the compass at the middle (angle determines north) and then you can spawn relative directions around it. Note this layers onto the SpawnSetRule - i.e. if you set a POSITIONAL rule south of the compass, and then set a SpawnDirectonMask of SPAWNDIR_N, the Director will never find a valid place to spawn (since the position is saying "pick from this radius south of here" and the mask is saying "now take that list of valid places and find one north")
- SpawnSetRule = SPAWN_FINALE, SPAWN_BATTLEFIELD, SPAWN_SURVIVORS, SPAWN_POSITIONAL
- SpawnSetRadius/SpawnSetPosition: A radius in units, a Vector(x,y,z) center point for POSITIONAL spawning
- SpecialInfectedAssault = ??? (not sure what this does)
- SpecialInitialSpawnDelayMin
- SpecialInitialSpawnDelayMax
- SpecialRespawnInterval = time in seconds for special respawns
- SpitterLimit = maximum number of spitters allowed
- SurvivorMaxIncapacitatedCount = Maximum amount of survivor incapacitating before dying
- SustainPeakMaxTime = in minutes
- SustainPeakMinTime = in minutes
- TankHitDamageModifierCoop = float (mutation1.nut Last Man on Earth) {todo|confirm category}}
- TankHitDamageModifierVersus = float Todo: confirm category
- TankLimit = maximum number of tanks allowed (for example, used in c7m3_port.nut)
- TankRunSpawnDelay = in seconds (mutation19.nut Taaannnkk!) Todo: confirm category
- TempHealthDecayRate = 0.27 // pain_pills_decay_rate default, higher values equals quicker decay
- TotalBoomers = number of boomers allowed in a wave
- TotalChargers = number of chargers allowed in a wave
- TotalHunters = number of hunters allowed in a wave
- TotalJockeys = number of jockeys allowed in a wave
- TotalSmokers = number of smokers allowed in a wave
- TotalSpecials = number of specials allowed in a wave
- TotalSpitters = number of spitters allowed in a wave
- WanderingZombieDensityModifier = float Todo: confirm category/working
- WitchLimit = maximum number of witches allowed (used in c7m3_port.nut)
- ZombieSpawnRange = How far away can zombies spawn?
- ZombieSpawnInFog = true|false
- ZombieTankHealth = tanks health
- function Update()
- If you define an Update() function in your vscript, it will run repeatedly much like a Think() function, but ONLY a finale via trigger_finale is triggered, if you have multiple scripts that are running that define it, they all will be called. The use of Update() is also found in the Bleed Out mutation (mutation3.nut), but needs further testing to see see if it behaves the same way like during a finale.
Finale Specific/Related
A normal finale consists of X number of stages. Some variables in DirectorOptions can only be used during finales.
- A_CustomFinale_StageCount = number of stages. Needs to be set for Versus scoring to function properly.
- A_CustomFinaleX = stage type (enumerated PANIC, ONSLAUGHT (AKA SCRIPTED), DELAY, TANK), where X is the stage number. Also used in scripted panic events.
- A_CustomFinaleValueX = Value depends on the stage type above. Also used in scripted panic events. Please see the example.
- A_CustomFinaleMusicX = Soundscript entry to play. For instance,
A_CustomFinaleMusic1 = "C2M5.BadManTank2"
. Note that c2m5_concert_finale.nut does not use this method and instead opted to use entities within the map instead. In the c2m5 scriptA_CustomFinaleMusic4 = ""
, suggesting that no song is actually played automatically via script.

- EnforceFinaleNavSpawnRules = ??? Likely used to enforce the finale spawning behavior without running a finale. Todo: confirm category/working
- EscapeSpawnTanks = true|false
- HordeEscapeCommonLimit = number of commons allowed when the escape vehicle has arrived
- function OnBeginCustomFinaleStage( num, type )
- If defined, will be called on every stage change with the number, and type, this is how you would change director options between stages (spawn directions, etc).
num
refers to the finale stage number passed by the director andtype
is the stage type (PANIC, TANK, etc.).
- function OnChangeFinaleMusic() Todo: confirm category/working
A stage can be one of 4 types (other values will break the finale and go right to ESCAPE):
- PANIC - a panic event, the value is the number of them (ie 2 would be 2 panic events in a row)
- TANK - spawn a tank(s), the value is the number of tanks to spawn
- DELAY - a delay, the value is the number of seconds to wait before proceeding to the next stage
- ONSLAUGHT/SCRIPTED - The value should be the name of a vscript to call or "" (which will do nothing), any bad value here will crash you to the desktop. Your onslaught script is responsible for sending an EndCustomScriptedStage input to the director (a goal of your choice, like a certain trigger volume, timer, random value, etc.). trigger_finale input AdvanceFinaleState may also work. Otherwise, the onslaught will not end.
An example custom finale script:
ERROR <- -1
PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3 // In some vscripts (c8m5_rooftop_finale), ONSLAUGHT is labeled as SCRIPTED
DirectorOptions <-
{
//-----------------------------------------------------
CommonLimit = 10
A_CustomFinale_StageCount = 8
A_CustomFinale1 = PANIC
A_CustomFinaleValue1 = 2 // two panic events
A_CustomFinale2 = DELAY
A_CustomFinaleValue2 = 12 // delay for twelve seconds in addition to stage delay
A_CustomFinale3 = TANK
A_CustomFinaleValue3 = 3 // 3 tanks!
A_CustomFinale4 = DELAY
A_CustomFinaleValue4 = 12 // wait some more
A_CustomFinale5 = ONSLAUGHT
A_CustomFinaleValue5 = "my_onslaught_script.nut" // run our onslaught script
A_CustomFinale6 = DELAY
A_CustomFinaleValue6 = 15 // wait 15 seconds
A_CustomFinale7 = TANK
A_CustomFinaleValue7 = 1 // one more tank
A_CustomFinale8 = DELAY
A_CustomFinaleValue8 = 10 // wait ten seconds ... rescue!
SpecialRespawnInterval = 25
//-----------------------------------------------------
}
function OnBeginCustomFinaleStage( num, type )
{
printl( "Beginning custom finale stage " + num + " of type " + type );
MapScript.DirectorOptions.CommonLimit = num * 10 // increase commons by 10 linearly with stages
}
Gauntlet Specific/Related
Most of these can be found in director_gauntlet.nut
- CustomTankKiteDistance (3000 is the default) Todo: confirm category/working
Movement Bonus related options. The Movement Bonus successively increases the delay between hordes when the survivors are not making progress toward the Gauntlet goal. The Current Bonus value ticks down every second, allows a horde to spawn when it reaches 0. When a horde spawns the Current Bonus is reset to the Movement Bonus value. The Movement Bonus increases at set intervals, and is reset when the survivors cross the Movement Threshold, which is then incremented. Use director_debug 1 to see the values.
- GauntletMovementThreshold = float The amount of flow units the survivors can advance before the Movement Bonus is reset.
- GauntletMovementTimerLength = float The interval between each Movement Bonus increase, in seconds.
- GauntletMovementBonus = float The initial value, and the amount the movement Bonus increases each interval, in seconds.
- GauntletMovementBonusMax = float The maximum value that the Movement Bonus can reach.
Mutation Specific/Related
Some of these values are mutation specific values of the global ones (cm_CommonLimit,cm_MaxSpecials, etc.), so use them if you are making a mutation, incase any map scripts are changing the global values.
- ActiveChallenge
- cm_AggressiveSpecials
- cm_AllowPillConversion
- cm_AllowSurvivorRescue
- cm_AutoReviveFromSpecialIncap Used by "The Last Man On Earth" and "Lone Gunman" mutations (mutation1.nut and mutation17.nut)
- cm_AutoSpawnInfectedGhosts
- cm_BaseCommonAttackDamage
- cm_BaseSpecialLimit
- cm_CommonLimit
- cm_DominatorLimit
- cm_FirstManOut Used by the "Room For One" mutation (mutation10.nut)
- cm_frustrationTimer
- cm_HeadshotOnly
- cm_HealingGnome
- cm_InfiniteFuel (mutation7.nut Chainsaw Massacre)
- cm_MaxSpecials
- cm_NoRescueClosets
- cm_NoSurvivorBots
- cm_ProhibitBosses
- cm_CommonLimit
- cm_ShouldEscortHumanPlayers
- cm_ShouldHurry
- cm_SingleScavengeCluster Used for scavenge cans to spawn one-by-one
- cm_SpecialRespawnInterval
- cm_SpecialSlotCountdownTime
- cm_SpecialsRetreatToCover
- cm_TankLimit
- cm_TankRun Used in Taaank! mutation (mutation19.nut)
- cm_TempHealthOnly Only temporary health
- cm_VIPTarget
- cm_WanderingZombieDensityModifier
- cm_WitchLimit
- function AllowWeaponSpawn()
- Returns true or false if the given classname is allowed to spawn, used by several mutations
- function ConvertWeaponSpawn()
- Converts a weapon spawn of given classname to another, used by several mutations
- function ConvertZombieClass()
- Converts one spawn into another, used by the tankss! mutation (mutation19.nut)
- function GetDefaultItem()
- ID starts from 0 and ends in an unknown point. Return a string of a weapon name to make it a default item for survivors. Used in several mutations
- function ShouldAvoidItem()
- Probably a bot related function or spawn related, not sure
Scavenge Specific/Related
- ScavengeClusterBonusTime = float Todo: confirm category/working
- ScavengeRoundInitialTime = float Todo: confirm category/working
- ScavengeScoreBonusTime = float (used in mutation13.nut Follow the Liter)
Survival Specific/Related
- SurvivalSetupTime = (Used in mutation15.nut for Survival Versus with setup time of 90 seconds)
Third party tools
See also
- L4D2 Vscript Examples
- List of L4D2 Script Functions
- Extended Mutation System
- L4D2 Level Design/Boss Prohibition
- Left 4 Dead 2 Tool Updates
- Mutation Gametype (L4D2)
- trigger_finale
- info_director
- logic_script
- vscripts
External links
- Alternative Documentation
- Director Scripts - .nuc files (Steam forums)
- It's the vscript'ing documentation FAQ! (Steam forums)
- Tutorial - Writing a Mini Game - Tic Tac Toe - Part One (Steam Forums)
- Writing a Mini Game - Tic Tac Toe - Part One - Author's Website
- l4d2 - Vscript example - Tic-Tac-Toe - Video of early Prototype
- l4d2 - Vscript example - Tic-Tac-Toe - updated - Video of current version with "brutally misanthropic AI"
- Mutation scripts (Steam forums)
- Squirrel Binary for Windows
- Squirrel (programming language) - Wikipedia Article on Squirrel
Squirrel: The Programming Language - Documentation and Sample Code
- The AI Systems of Left 4 Dead by Michael Booth (PDF)
"Creating a "Money"/Point System" - Swarm Armory
- Deciphered Official Scripts