Left 4 Dead 2/Scripting
Left 4 Dead 2 vscripts are Squirrel language-based scripts that can be run in-game to perform various in-game tasks and changes.

Description
Left 4 Dead 2 vscripts are written in Squirrel, a compilable scripting language similar to Lua.
Some uses of vscripts include:
- Mini-games found in Dark Carnival - counters, timers, prop spawning
- Some Scavenge Logic
- Director manipulation - onslaughts, reserved wanderers, complete emptiness/silence, prohibition of boss infected (tanks and witches), direction of mobs
- Model manipulation - green diesel cans attached to survivors in Hard Rain
- Much more!
The file extensions of vscripts are .nut and .nuc, where .nut files are human readable using a text editor. Official .nuc script files are located in scripts/vscripts within pak01_dir.vpk (you can open this file with third party programs like GCFScape).
Some entities that accept vscripts as inputs or properties include info_director, logic_script, and trigger_finale.
Decompiling nuc-files
.Nuc files are ICE encrypted .nut files. The encryption key is SDhfi878. You can use VICE to decode them or download already deciphered scripts here (includes all scripts from version 2.0.1.8 and all mutations).
Practicing the Squirrel language
Developers that would like to practice Squirrel can quickly set up an environment to do so. It can be as simple as opening up L4D2 and executing scripts from the console. One can also set up a light-weight squirrel interpretor from Windows Command Prompt:
Windows Command Prompt
- Download and extract the Squirrel interpretor binary (Version 2.2, 32-Bit) to any folder. sq.exe is the interpretor. sq.exe can also be created by compiling from source code.
- Any scripts within that folder are executed with the command
sq filename.nut
via Windows Command PromptTip:There are sample scripts included with the compiled binary. Try running something simple first, like the "Hello, World!" script.
sq hello.nut
- Optional: Set up a path to sq.exe within the Environment Variables in Windows. This allows convenient execution of .NUT scripts from any folder from the Command Prompt. Go to System Properties via the Control Panel, select the Advanced tab, and click the Environmental Variables button. Under System Variables select the Variable "Path" and click on the Edit button. Add the full path to sq.exe (for example, C:\Squirrel, where sq.exe is located in C:\Squirrel) to the other paths to the Variable Value. Restart the Command Prompt and
sq filename.nut
should be able to work from any folder.
Documentation and generic sample scripts can always be found at the official Squirrel Language site.
L4D2 Client
- info_director
- Loads DirectorOptions such as onslaughts, panic events, infected limits, etc.
- Can be loaded in console with ent_fire command
ent_fire <targetname of info_director> BeginScript <name of script>
- logic_script
- The vscript that is set up in the logic_script will load automatically on load
- The vscript can be reloaded with console command
script_reload_code
- Game Modes Todo: see if it also applies to coop, versus, etc.
- Scavenge
- A per-map script is loaded automatically during Scavenge Mode
- Name the script <map name>_scavenge.nut.
- For example, c1m4_atrium_scavenge.nut.
- Mutators
- Will automatically run the script with the same name as the game mode.
- For example, running a map
map <map name> realismversus
will automatically load realismversus script.
- Scavenge
- Events
- Finale
- "Script" KeyValue of trigger_finale does not need to be set up
- Automatically loads <map name>_finale.nut script on finale trigger
- Known to work in this fashion in Custom and Scavenge finale type (set up in trigger_finale)
- Finale
Scripting samples
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. Some of these samples are only portions of a full script, so make sure that the variables are created beforehand.
Targeting entities with a director script
Add something like this in the script for your minifinale when your horde wave gets triggered:
{ EntFire( "church_bell_relay", "Trigger", 0 ) }
Then place a logic_relay with that name to send an output to your ambient_generic.
Triggering a panic event though a script
If you want to start a panic event through a script (not an onslaught) then use this:
DirectorOptions <- { A_CustomFinale1 = PANIC A_CustomFinaleValue1 = 2 }
The number refers to the number of waves that you want in your minifinale. This is what was used at the ferry in the first map of Swamp Fever.
Handling a survivor
/*************************************** Get a handle on a specific survivor - This is pretty hackish, I can't just say Entities.FindByName(null, "playername"), (YET) because I don't know what it's expecting internally. And I can't "learn" it's name by saying survivior.GetName(), because what that returns is just some a function ref, it's probably the C++ side wrapped in a closure, OPAQUE, bummer. ***************************************/ //Get Nick Nick <- Entities.FindByModel(null,"models/survivors/survivor_gambler.mdl"); // Nick is class "player" (which must be related or superset of CBaseEntity) Nick.SetHealth(0); // Ouch but he won't incap until hit again, even below zero //or Super Nick! Nick.SetHealth(50000); // Tank gonna get slapped to death.
New Info!!!
You can get the entity for any of the survivors by using "!survivorname" as the entity name. If I wanted to mess up Nick, EntFire("!nick", "SetHealth", 1); <not sure if that's exactly right.
!nick !rochelle !coach !ellis point to the corresponding player class entities in game, do a few ent_fires on them, have a blast.
This was discovered by looking at the passing campaign decompiled levels where the designers used !zoey !louis and !francis in the actual level to teleport the corresponding l4d1 npcs around the map.
Some General DirectorOptions
You can start a script with an input to the director "BeginScript" and then the name of the script in the parameters field. Place the script as a ".nut" file in your vscripts directory. To end the script, send an input to the director "EndScript".
Here are some examples:
DirectorOptions <- { ProhibitBosses = 1 (default is 0) AlwaysAllowWanderers = 1 (default is 0) MobMinSize = 10 (default) MobMaxSize = 30 (default) SustainPeakMinTime = 3 (default) SustainPeakMaxTime = 5 (default) IntensityRelaxThreshold = 0.9 (default) RelaxMinInterval = 30 (default) RelaxMaxInterval = 45 (default) RelaxMaxFlowTravel = 3000 (default) SpecialRespawnInterval = 45.0 (default) NumReservedWanderers = 10 (default is 0) }
You can play around with some of these numbers for specific events in your levels. For example, some of our scripts reduce the SpecialRespawnInterval to 30 seconds or we have some that reduce the RelaxMaxFlowTravel to 1000 so that the director won't stay relaxed if the survivors have continued far enough.
Prohibiting Tank and Witch spawns
Left4Dead 2 no longer relies on the mission.txt file to prohibit Tanks and Witches on maps. This is now done with a script file that you should place in your left4dead2/scripts/vscripts folder (you may need to add the vscripts subfolder yourself). Add the following to a text file:
DirectorOptions <- { ProhibitBosses = true }
Save the textfile with the extention .nut in the vscripts folder. In your map, place a logic_auto entity and add an output. The output should target the AI Director entity and make it fire the script by using the BeginScript action. In the parameter field, you set the name of your script (without the .nut extention)
Some Scavenge Gamemode DirectorOptions (for Coop/Versus Finales with Scavenge)
For a finale that is using scavenge as the gameplay (such as in the Mall Atrium finale), you'll need a separate scavenge script. Here's c1m4's (name the script "[the name of the map]_scavenge.nut"):
DirectorOptions <- { PreferredMobDirection = SPAWN_LARGE_VOLUME PreferredSpecialDirection = SPAWN_LARGE_VOLUME ShouldConstrainLargeVolumeSpawn = false MobSpawnMinTime = 45 MobSpawnMaxTime = 90 CommonLimit = 15 ZombieSpawnRange = 3000 } NavMesh.UnblockRescueVehicleNav() Director.ResetMobTimer()
Dark Carnival Onslaught Script
Here's the onslaught script Valve used for the 4th map of Dark Carnival:
Msg("Initiating Onslaught\n"); DirectorOptions <- { // This turns off tanks and witches. ProhibitBosses = false //LockTempo = true MobSpawnMinTime = 3 MobSpawnMaxTime = 7 MobMinSize = 30 MobMaxSize = 30 MobMaxPending = 30 SustainPeakMinTime = 5 SustainPeakMaxTime = 10 IntensityRelaxThreshold = 0.99 RelaxMinInterval = 1 RelaxMaxInterval = 5 RelaxMaxFlowTravel = 50 SpecialRespawnInterval = 1.0 PreferredMobDirection = SPAWN_IN_FRONT_OF_SURVIVORS ZombieSpawnRange = 2000 } Director.ResetMobTimer()
Maximum number of simultaneous specials
The following script not only limits the number of specials (using MaxSpecials
), but also limits the number of specific specials allowed simultaneously and increases the maximum number of common infected:

Msg("Preparing to own the Survivors"); local Dopts = DirectorScript.DirectorOptions; // get a reference to the options Dopts.MaxSpecials <- 20; Dopts.BoomerLimit <- 5; Dopts.SmokerLimit <- 5; Dopts.HunterLimit <- 5; Dopts.ChargerLimit <- 5; Dopts.SpitterLimit <- 5; Dopts.JockeyLimit <- 5; Dopts.CommonLimit <- 120; Dopts.SpecialRespawnInterval <- 1.0;
Dead Center Chapter 3 Special Infected Limit
For multiple SI spawns like in the 3rd chapter of Dead Center:
BoomerLimit = 0 SmokerLimit = 3 HunterLimit = 1 ChargerLimit = 2
You can also limit Spitters and Jockeys with
SpitterLimit = 0 JockeyLimit = 0
c1m4_atrium.nut (or <map_name>.nut)
c1m4_atrium_finale (or <map name>_finale) also relies on this script in certain cases.

Msg(" atrium map script "+"\n") // number of cans needed to escape. if ( Director.IsSinglePlayerGame() ) { NumCansNeeded <- 8 } else { NumCansNeeded <- 13 } DirectorOptions <- { CommonLimit = 15 } NavMesh.UnblockRescueVehicleNav() EntFire( "progress_display", "SetTotalItems", NumCansNeeded ) function GasCanPoured(){}
Other Possible Director Options
There are also other options that might work like the above. Some can be seen in console outputs in dev mode (for example, from <map name>_finale.nuc). They might work like the above scripts, but it appears that some may be affected by a local script that dynamically changes the variables. Here are some examples:
c1m4_atrium_finale (Scavenge Finale)

ShouldAllowMobsWithTank = true ShouldAllowSpecialsWithTank = true MinimumStageTime = 15 MobRechargeRate = 0.5 HordeEscapeCommonLimit = 15 MusicDynamicMobStopSize = 2 MusicDynamicMobSpawnSize = 8 BileMobSize = 15 PreferredSpecialDirection = 9 (In other examples, this was set as a string) MusicDynamicMobScanStopSize = 2 A_CustomFinale[x]('x' seems to be any number, like up to 30) = A_CustomFinale1 = 3 A_CustomFinale2 = 0 A_CustomFinaleValue[x] = c1m4_delay
Tic-Tac-Toe mini-game
This example uses and vscripts to manipulate entities "registered" to a logic_script entity.[1]
- 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"
Overview of Squirrel in L4D2
The following are available in L4D2 by default.[2][3]
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.
Variables
Classes
CDirector
Game Instance: Director
- Class Methods
- GetAveragedSurvivorSpan() - Get the distance between the lead and trailing survivors, smoothed over time
- GetAveragedSurvivorSpeed() - Get the rate at which the lead survivor is moving along the flow, smoothed over time
- GetFurthestSurvivorFlow() - Get the maximum distance along the flow that the survivors have reached
- GetGameMode() - Get the current game mode "versus", "coop", etc.
- HasAnySurvivorLeftSafeArea()
- IsPlayingOnConsole() - Returns true if player is running the client on a console like Xbox 360
- IsSinglePlayerGame() - Return true if game is in single player
- IsValid()
- L4D1SurvivorGiveItem()
- PlayMegaMobWarningSounds() - Plays a horde scream sound and asks survivors to speak 'incoming horde' lines
- ResetMobTimer() - Trigger a mob as soon as possible when in BUILD_UP (refer to
director_debug 1
)
- 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
CEntities
Game Instance: Entities
- Class Methods
- .FindByClassname (null|prev,classname) - Find entities by class name. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search Continue an iteration over the list of entities, providing reference to a previously found entity
- .FindByClassnameNearest (classname, vector) - Find entities by class name nearest to a point
- .FindByClassnameWithin (null|prev,classname) - Find entities by class name within a radius. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search
- .FindByModel (null|prev, modelname) - Find entities by model name. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search
- .FindByName (classname, vector) - Find entities by class name nearest to a point
- .FindByNameNearest (name, vector) ??? - Find entities by name nearest to a point
- .FindByNameWithin (null|prev, name, radius) ??? - Find entities by name within a radius. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search
- .FindByTarget (null|prev, targetname) - Find entities by targetname. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search
- .FindInSphere (null|prev, vector,radius) ??? - Find entities within a radius. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search
- .IsValid() - whether entity is valid
- .First() - Begin an iteration over the list of entities
- .Next() - Returns the next entity in the Entities list
CBaseEntity
Game Instance: N/A
- Class Methods
- .__KeyValueFromInt(key,int) - Sets a keyvalue from an integer, the keys available for all __KeyValue are from the DEFINE_KEYFIELD in baseentity.cpp (classname,rendermode, renderfx, etc.)
- .__KeyValueFromString(key,string) - Sets a keyvalue from a string, the keys available for all __KeyValue are from the DEFINE_KEYFIELD in baseentity.cpp (classname,rendermode, renderfx, etc.)
- .__KeyValueFromVector(key,vector) - Sets a keyvalue from a vector, the keys available for all __KeyValue are from the DEFINE_KEYFIELD in baseentity.cpp (classname,rendermode, renderfx, etc.)
- .ConnectOutput(outputname,functioname) - Adds an I/O connection that will call the named function when the specified output fires
- .DisconnectOutput(event) - Removes a connected script function from an I/O event
- .FirstMoveChild() - returns entity's first move child if exists
- .GetClassname() - returns classname
- .GetForwardVector() - returns Vector instance
- .GetHealth() - returns entity health
- .GetMoveParent() - If in hierarchy, retrieves the entity's parent
- .GetName() - returns targetname of entity (if entity is named)
- .GetOrigin() - returns origin vector
- .GetPreTemplateName - Get the entity name stripped of template unique decoration. ie myitem&0125 returns myitem
- .GetRootMoveParent() - If in hierarchy, walks up the hierarchy to find the root parent
- .GetScriptId() - Retrieve the unique identifier used to refer to the entity within the scripting system
- .GetScriptScope() - Retrieve the script-side data associated with an entity
- .GetVelocity() - returns velocity vector
- .IsValid() - whether entity is valid
- .NextMovePeer - returns next child entity
- .SetForwardVector(vector)
- .SetHealth(int) - sets entity health
- .SetOrigin(vector) - sets entity position in world
- .SetVelocity(vector) - sets entity velocity
- .ValidateScriptScope - Ensure that an entity's script scope has been created
CBaseAnimating
Extends CBaseEntity
- Class Methods
- .__KeyValueFromInt(key,int) - Sets a keyvalue from an integer, the keys available for all __KeyValue are from the DEFINE_KEYFIELD in baseentity.cpp (classname,rendermode, renderfx, etc)
- .__KeyValueFromString(key,string) - Sets a keyvalue from a string, the keys available for all __KeyValue are from the DEFINE_KEYFIELD in baseentity.cpp (classname,rendermode, renderfx, etc)
- .__KeyValueFromVector(key,vector) - Sets a keyvalue from a vector, the keys available for all __KeyValue are from the DEFINE_KEYFIELD in baseentity.cpp (classname,rendermode, renderfx, etc)
- .ConnectOutput(outputname,functioname) - Adds an I/O connection that will call the named function when the specified output fires
- .DisconnectOutput(event) - Removes a connected script function from an I/O event
- .FirstMoveChild() - returns entity's first move child if exists
- .GetClassname() - returns classname
- .GetForwardVector() - returns Vector instance
- .GetHealth() - returns entity health
- .GetMoveParent() - If in hierarchy, retrieves the entity's parent
- .GetName() - returns named (if entity is named)
- .GetOrigin() - returns origin vector
- .GetPreTemplateName - Get the entity name stripped of template unique decoration. ie myitem&0125 returns myitem
- .GetRootMoveParent() - If in hierarchy, walks up the hierarchy to find the root parent
- .GetScriptId() - Retrieve the unique identifier used to refer to the entity within the scripting system
- .GetScriptScope() - Retrieve the script-side data associated with an entity
- .GetVelocity() - returns velocity vector
- .IsValid() - whether entity is valid
- .NextMovePeer - returns next child entity
- .SetForwardVector(vector)
- .SetHealth(int) - sets entity health
- .SetOrigin(vector) - sets entity position in world
- .SetVelocity(vector) - sets entity velocity
- .ValidateScriptScope - Ensure that an entity's script scope has been created
Game Instance: NavMesh
- Class Methods
- .IsValid
- .UnblockRescueVehicleNav() - Removes the block for rescue vehicle. Normally is blocked before the finale is finished, but is necessary for bots to access like in the Atrium finale, c1m4_atrium.bsp.
CCallChainer
Game Instance: N/A
- Class Methods
- .chains
- .constructor
- .PostScriptExecute
- .prefix
- .scope
CSimpleCallChainer
- Class Methods
- .Call
- .chain
- .constructor
- .exactMatch
- .PostScriptExecute
- .prefix
- .scope
regxp
[Todo] Game Instance:
- Class Methods
Vector
Game Instance: None
- Class Methods
- .constructor
- .Cross
- .Dot
- .Length
- .Length2D
- .Length2DSqr
- .LengthSqr
- .Norm
- .ToKVString
Functions
- Assert(value, "optional message") - test value and if not true, throws exception, optionally with messsage
- ConnectOutputs(table) - sets output functions for entity by table TODO
- DebugDrawBox
- DebugDrawLine
- Developer() - returns 1 or TRUE when in developer mode
- DoEntFire( target.tostring(), action, value, delay, activator, caller ) - Fire an event
- EntFire("ent_fire target event") - wrapper for DoEntFire() that sets activator and caller to null
- DoIncludeScript
- IncludeScript - Wrapper for DoIncludeScript
- GetFunctionSignature
- Msg("message here") - prints message to console
- printl("message") - prints message with carriage return
- RandomFloat() - Returns a random float
- RandomInt() - Returns a random int between 0 and 32767
- RetrieveNativeSignature
- SendToConsole("string")- send a string to the console as a command
- ShowMessage("message") - Print a hud message on all clients
- Time() - Get the current server time
- DoUniqueString - Called by UniqueString, prob want to call that instead
- UniqueString() - Generate a string guaranteed to be unique across the life of the script VM, with an optional root string. Useful for adding data to tables when not sure what keys are already in use in that table.
Standard Squirrel library functions (consult official Squirrel language league for more details):
abs,
acos,
array,
asin ,
assert,
atan ,
atan2,
ceil,
collectgarbage,
compilestring,
cos,
exp ,
fabs ,
floor,
format,
getconsttable,
getroottable,
getstackinfos,
log,
log10,
lstrip,
pow,
print,
rand,
rstrip,
setconsttable,
seterrorhandler,
setroottable,
type,
sin,
split,
sqrt,
srand,
strip,
suspend,
tan
Constants
See also
- L4D2 Level Design/Boss Prohibition
- Left 4 Dead 2 Tool Updates
- Mutation Gametype (L4D2)
- trigger_finale
- info_director
- logic_script
External links
- 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)