L4D2 Vscripts

From Valve Developer Community
Revision as of 14:20, 19 August 2010 by SAS Chester (talk | contribs)
Jump to: navigation, search

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

Note.png Note: Information on vscripts is currently incomplete. Descriptions, accuracy, and information are subject to change.

Contents

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

  1. 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.
  2. Any scripts within that folder are executed with the command sq filename.nut via Windows Command Prompt
    Tip.png Tip: There are sample scripts included with the compiled binary. Try running something simple first, like the "Hello, World!" script. sq hello.nut
  3. 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 To do: 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.
  • 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)

Scripting samples

Warning.png 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:

//-----------------------------------------------------
local PANIC = 0
//-----------------------------------------------------

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. This script needs to be executed via scriptedpanicevent input to the director. Should also be initialized on a battlefield or finale nav mesh.

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.
Note.png Note: "!survivorname" can also be used as the entity name. For example, the health of the survivor can be reduced with
EntFire("!nick", "SetHealth", 1)
!nick, !rochelle, !coach, !ellis point to the corresponding player class entities in game. 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.

"Detecting"/Finding Survivors

[1] Just a sample you can use in your own code to find survivors. You need to call if after a delay of 10 seconds or so with a logic_auto

Something like

logic_auto > onmapspawn "logic_script_name" runscriptcode with a value of "FindSurvivors()"

Then you can iterate (loop) through the survivors like so...

/*
findsurvivors.nut
author: Lee Pumphret
http://www.leeland.net

The survivors table, once initialized, holds an entity reference to all survivors
To reference a specific survivor, you can say
survivors.nick (etc...)
*/

survivors <-{
   coach = "models/survivors/survivor_coach.mdl",
   ellis = "models/survivors/survivor_mechanic.mdl",
   nick = "models/survivors/survivor_gambler.mdl",
   rochelle = "models/survivors/survivor_producer.mdl"
}

survivors_found <- 0 // flag set to true once survivors are found

/*
Find survivors, this needs to be called after a delay. If you call it immediately,
it will fail as they have not been loaded yet, 10 sec after map load should be good.
You can call it with a logic_auto output, runscriptcode FindSurvivors()
*/
function FindSurvivors(){
   foreach(s,m in survivors){
    printl ("looking for "+s+" mdl:"+m);
    survivor <- Entities.FindByModel(null, m)
    if (survivor){
      printl(s+" found: "+survivor);
      survivors[s] = survivor
      survivors_found++
    }else{
      printl(s+" NOT FOUND!: "+survivor);
      survivors[s] = null
    }
   }
}

Find Survivors Closest to Entity

This builds off multiple scripts. Refer to the author's website for a explanation and complete code. [2]

IncludeScript("survivorclosest.nut", this);

ent <- Entities.FindByName(null, "entname");

printl("Found entity "+ent);


FindSurvivors()  // make sure this script isn't called right away or they won't be found yet

who <- FindSurvivorClosestToEnt(ent); // returns entity reference to survivor
if (who){
   printl(who);
   printl(who+" is closest to "+ent.GetName()+" at "+who.GetOrigin());
   // do something here
}else{
   printl("Nothing found");
}


/* or, if you want the name returned (the key into the
 survivors table), add a true value as a second param */

who <- FindSurvivorClosestToEnt(ent, 1); // returns entity reference to survivor
if (who){
   printl(who);
   printl(who+" is closest to "+ent.GetName()+" at "+survivors[who].GetOrigin());
   // do something here
}else{
   printl("Nothing found");
}

"Hovering Chopper Support" Example

[3] If you want to parent a func_tracktrain to something, so it moves relative, you don't parent the path, you parent the func_tracktrain itsef to the item, and the path will move relative to this. So here's what this does.

  • You make your track as normal, your "train" heli, whatever.
  • Place an info_target in the center of your path with a name ex: chopper_follow
  • Then you parent that func_tracktrain to the target (chopper_follow).
  • Pass that target as the first entitygroup to this script.
  • Then what happens is that the script computes the average position of the survivors ever 1/10 of a second, moves the info_target there (chopper_follow), and the path_track will follow.
  • Have your track the height you want, relative to the info_target.
Note.png Note: There are a couple caveats, you'll need to call FindSurvivors() after a delay of map load, or it won't work. They won't be loaded yet, a logic_auto output onmapspawn with a delay of 10 seconds should do it.

Changing The Skybox On Map Load

[4] Save the file to your scripts/vscripts folder as whichskybox.nut

Add a logic_script entity to your map, turn off smartedit and add a "vscripts" key with a value of whichskybox.nut

Obviously you would have to adjust fog and lighting entities as well for it to not look terrible.

Skyboxes <- [
   "sky_l4d_c1_1_hdr",
   "sky_l4d_c1_2_hdr",
   "sky_l4d_c2m1_hdr",
   "sky_l4d_night02_hdr",
   "sky_l4d_predawn02_hdr",
   "sky_l4d_c4m1_hdr",
   "sky_l4d_c4m4_hdr",
   "sky_l4d_c5_1_hdr",
   "sky_l4d_c6m1_hdr"
]

worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("skyname",Skyboxes[i]) );

Changing Witch Movement Type On Map Load

This was based off of the code found here: [5] Save the two files to your scripts/vscripts folder as daywitch.nut and nightwitch.nut

Add two logic_script entities to your map. Give "Name" a value of daywitchscript, turn off smartedit and add a "vscripts" key with a value of daywitch.nut.

Repeat for the other with a "Name" value of nightwitchscript, turn off smartedit again and add a "vscripts" key with a value of nightwitch.nut.

There are two scripts involved here, one for day witches and one for night. These scripts run on map load and select the numerical equivialant of Midnight (0) or Morning (2) found in worldspawn. I seperated the scripts so entity logic can tie into them. More on that later.

daywitch.nut:

Skyboxes <- [
   "2"
]

worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );

nightwitch.nut:

Skyboxes <- [
   "0"
]

worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );

This is the line of entity logic I used in my test map to achieve random day or night witches. Note I start the logic_auto with a delay of 10 seconds so the AI can get ready. It doesn't seem to work if I don't, as noted in the "Hovering Chopper Support Example" above.

Class: logic_auto
My output named: OnMapSpawn
Targets entities named: daynightwitchcase
Via this input: PickRandomShuffle
Delay: 10.00

Class: logic_case
My output named: OnCase01
Targets entities named: daywitchtemplate
Via this input: ForceSpawn
Delay: 0.00

My output named: OnCase02
Targets entities named: nightwitchtemplate
Via this input: ForceSpawn
Delay: 0.00

Class: point_template
Name: daywitchscripttemplate
Template 1: daywitchscript

Class: point_template
Name: nightwitchscripttemplate
Template 1: nightwitchscript

Class: logic_script
Name: daywitchscript
vscripts: daywitch.nut

Class: logic_script
Name: nightwitchscript
vscripts: nightwitch.nut

Compile your map with this logic in place, and both named scripts in the scripts/vscripts folder in your main game directory and you should see witches respond and behave differently, depending on which script the game chose randomly.


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

Note.png Note: Information on vscripts is currently incomplete. Descriptions, accuracy, and information are subject to change.

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

  1. 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.
  2. Any scripts within that folder are executed with the command sq filename.nut via Windows Command Prompt
    Tip.png Tip: There are sample scripts included with the compiled binary. Try running something simple first, like the "Hello, World!" script. sq hello.nut
  3. 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 To do: 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.
  • 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)

Scripting samples

Warning.png 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:

//-----------------------------------------------------
local PANIC = 0
//-----------------------------------------------------

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. This script needs to be executed via scriptedpanicevent input to the director. Should also be initialized on a battlefield or finale nav mesh.

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.
Note.png Note: "!survivorname" can also be used as the entity name. For example, the health of the survivor can be reduced with
EntFire("!nick", "SetHealth", 1)
!nick, !rochelle, !coach, !ellis point to the corresponding player class entities in game. 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.

"Detecting"/Finding Survivors

[6] Just a sample you can use in your own code to find survivors. You need to call if after a delay of 10 seconds or so with a logic_auto

Something like

logic_auto > onmapspawn "logic_script_name" runscriptcode with a value of "FindSurvivors()"

Then you can iterate (loop) through the survivors like so...

/*
findsurvivors.nut
author: Lee Pumphret
http://www.leeland.net

The survivors table, once initialized, holds an entity reference to all survivors
To reference a specific survivor, you can say
survivors.nick (etc...)
*/

survivors <-{
   coach = "models/survivors/survivor_coach.mdl",
   ellis = "models/survivors/survivor_mechanic.mdl",
   nick = "models/survivors/survivor_gambler.mdl",
   rochelle = "models/survivors/survivor_producer.mdl"
}

survivors_found <- 0 // flag set to true once survivors are found

/*
Find survivors, this needs to be called after a delay. If you call it immediately,
it will fail as they have not been loaded yet, 10 sec after map load should be good.
You can call it with a logic_auto output, runscriptcode FindSurvivors()
*/
function FindSurvivors(){
   foreach(s,m in survivors){
    printl ("looking for "+s+" mdl:"+m);
    survivor <- Entities.FindByModel(null, m)
    if (survivor){
      printl(s+" found: "+survivor);
      survivors[s] = survivor
      survivors_found++
    }else{
      printl(s+" NOT FOUND!: "+survivor);
      survivors[s] = null
    }
   }
}

Find Survivors Closest to Entity

This builds off multiple scripts. Refer to the author's website for a explanation and complete code. [7]

IncludeScript("survivorclosest.nut", this);

ent <- Entities.FindByName(null, "entname");

printl("Found entity "+ent);


FindSurvivors()  // make sure this script isn't called right away or they won't be found yet

who <- FindSurvivorClosestToEnt(ent); // returns entity reference to survivor
if (who){
   printl(who);
   printl(who+" is closest to "+ent.GetName()+" at "+who.GetOrigin());
   // do something here
}else{
   printl("Nothing found");
}


/* or, if you want the name returned (the key into the
 survivors table), add a true value as a second param */

who <- FindSurvivorClosestToEnt(ent, 1); // returns entity reference to survivor
if (who){
   printl(who);
   printl(who+" is closest to "+ent.GetName()+" at "+survivors[who].GetOrigin());
   // do something here
}else{
   printl("Nothing found");
}

"Hovering Chopper Support" Example

[8] If you want to parent a func_tracktrain to something, so it moves relative, you don't parent the path, you parent the func_tracktrain itsef to the item, and the path will move relative to this. So here's what this does.

  • You make your track as normal, your "train" heli, whatever.
  • Place an info_target in the center of your path with a name ex: chopper_follow
  • Then you parent that func_tracktrain to the target (chopper_follow).
  • Pass that target as the first entitygroup to this script.
  • Then what happens is that the script computes the average position of the survivors ever 1/10 of a second, moves the info_target there (chopper_follow), and the path_track will follow.
  • Have your track the height you want, relative to the info_target.
Note.png Note: There are a couple caveats, you'll need to call FindSurvivors() after a delay of map load, or it won't work. They won't be loaded yet, a logic_auto output onmapspawn with a delay of 10 seconds should do it.

Changing The Skybox On Map Load

[9] Save the file to your scripts/vscripts folder as whichskybox.nut

Add a logic_script entity to your map, turn off smartedit and add a "vscripts" key with a value of whichskybox.nut

Obviously you would have to adjust fog and lighting entities as well for it to not look terrible.

Skyboxes <- [
   "sky_l4d_c1_1_hdr",
   "sky_l4d_c1_2_hdr",
   "sky_l4d_c2m1_hdr",
   "sky_l4d_night02_hdr",
   "sky_l4d_predawn02_hdr",
   "sky_l4d_c4m1_hdr",
   "sky_l4d_c4m4_hdr",
   "sky_l4d_c5_1_hdr",
   "sky_l4d_c6m1_hdr"
]

worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("skyname",Skyboxes[i]) );

Changing Witch Movement Type On Map Load

This was based off of the code found here: [10] Save the two files to your scripts/vscripts folder as daywitch.nut and nightwitch.nut

Add a logic_script entity to your map, turn off smartedit and add a "vscripts" key with a value of daywitch.nut, then repeat for the other with a value of nightwitch.nut

There are two scripts involved here, one for day witches and one for night. These scripts run on map load and select the numerical equivialant of Midnight (0) or Morning (2) found in worldspawn. I seperated the scripts so entity logic can tie into them. More on that below.

daywitch.nut:

Skyboxes <- [
   "2"
]

worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );;

nightwitch.nut:

Skyboxes <- [
   "0"
]

worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );

This is the line of entity logic I used in my test map to achieve random day or night witches. Note I start the logic_auto with a delay of 10 seconds so the AI can get ready. It didn't seem to work if I didn't, as noted in the "Hovering Chopper Support Example" above.

Class: logic_auto
My output named: OnMapSpawn
Targets entities named: daynightwitchcase
Via this input: PickRandomShuffle
Delay: 10.00

Class: logic_case
My output named: OnCase01
Targets entities named: daywitchtemplate
Via this input: ForceSpawn
Delay: 0.00

My output named: OnCase02
Targets entities named: nightwitchtemplate
Via this input: ForceSpawn
Delay: 0.00

Class: point_template
Name: daywitchscripttemplate
Template 1: daywitchscript

Class: point_template
Name: nightwitchscripttemplate
Template 1: nightwitchscript

Class: logic_script
Name: daywitchscript
vscripts: daywitch.nut

Class: logic_script
Name: nightwitchscript
vscripts: nightwitch.nut

Compile your map with this logic in place, and both named scripts in the scripts/vscripts folder in your main game directory and you should see witches respond and behave differently, randomly.

The advantages of tying it into map logic and thus using two seperate script files are many. By randomly changing the skybox in the same way, substituting script files, you could theoretically have a map that for all intents and purposes could switch from day to night or vice versa on map load. Of course, you'd need to adjust fog and lighting entities as well for it to look good, but this is only one step of many in converting a 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:

Note.png Note: Developers should keep in mind that in-game and network performance may suffer as more infected are introduced to the player at the same time.
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.

Note.png Note: The description of the finale script was derived from the given c1m4_atrium_finale script comment: "// this is too late. Moved to c1m4_atrium.nut"
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(){}

Tic-Tac-Toe mini-game

This example uses and vscripts to manipulate entities "registered" to a logic_script entity.[11]

Pushing a Player Around

[12][13]

/*--------------------------------------------
author: http://leeland.net
file:pushplayer.nut
description: 
Allows you to add a velocity vector to any player via trigger. 
Call it from a trigger with something like
OnStartTouch !activator runscriptcode OurPushPlayer(0,0,400)

A note on values, 4096 is the maximum velocity, values below 250 don't move the player at all. 
Z Values around 700 will incap, 1000 or so will prove fatal


--------------------------------------------*/

function OurPushPlayer(x,y,z) {
   local addv = Vector(x,y,z); // 4096 max velocity, anything higher is clamped
   local v = self.GetVelocity()
   self.SetVelocity(v+addv);
}


// slip in a reference to our function in the global table so we can access from all objects,
// *technique to use sparingly! Your function could overwrite another. Name uniquely to try and avoid conflicts*
::OurPushPlayer <- OurPushPlayer;
Note.png Note: Negative Z values do not work. A player cannot be pounded into the ground.

Director options

This is by no means an exaustive list of all the director options. Additions and updates can be found in the Steam Forums

  • function OnBeginCustomFinaleStage()
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)

Code:

function OnBeginCustomFinaleStage( num, type )
{
      printl( "Beginning custom finale stage " + num + " of type " + type );
      // set options as wanted for the stage
}
  • function Update()
If you define an Update() function in your vscript, it will run repeatedly much like a Think() function, but ONLY after a trigger_finale is triggered, if you have multiple scripts that are running that define it, they all will be called.
  • Director.L4D1SurvivorGiveItem()
This makes l4d1 survivors give an item
  • AlwaysAllowWanderers = true|false
  • BileMobSize = NUM ? Doesn't seem to do anything
  • BoomerLimit = maximum number of boomers allowed
  • ChargerLimit =maximum number of chargers allowed
  • CommonLimit = maximum number of commons allowed
  • DisallowThreatType = ZOMBIE_WITCH, ZOMBIE_TANK (other values???)
  • HordeEscapeCommonLimit = number of commons allowed when the escape vehicle has arrived
  • HunterLimit = maximum number of hunters allowed
  • IntensityRelaxThreshold = All survivors must be below this intensity before a Peak is allowed to switch to Relax (in addition to the normal peak timer)
  • JockeyLimit = maximum number of jockeys allowed
  • LockTempo = 0 ???
  • MaxSpecials = number of specials allowed at once
  • MegaMobMaxSize = maximum megamob size
  • MegaMobMinSize = minimum megamob size
  • MobMaxPending = not sure
  • 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
  • MusicDynamicMobScanStopSize = When see less 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
  • 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
Note.png Note: SPAWN_NEAR_IT_VICTIM does not exist before a finale and will cause an error, so I'm assuming the director picks someone as IT when the finale starts. SPAWN_LARGE_VOLUME is what makes you be a mile away on DC finale.
  • PreferredSpecialDirection
Note.png Note: The same values for PreferredMobDirection appear to work, BUT I've also seen the following, I don't know if it's just redundancy or what.

SPAWN_SPECIALS_ANYWHERE

SPAWN_SPECIALS_IN_FRONT_OF_SURVIVORS
  • 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
  • SpecialRespawnInterval = time in seconds for special respawns
  • SpitterLimit = maximum number of spitters allowed
  • SustainPeakMaxTime = in minutes
  • SustainPeakMinTime = in minutes
  • TempHealthDecayRate = 0.27 // pain_pills_decay_rate default, higher values equals quicker decay
  • ZombieSpawnRange = How far away can zombies spawn?
  • ZombieSpawnInFog = true|false

Finale scripts

Some variables in DirectorOptions can only be used during finales.

A normal finale consists of X number of stages

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 - 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 to the director (a goal of your choice, like a certain trigger volume, timer, random value, etc.). Otherwise, the onslaught will not end.

You can also specify custom game sounds for a stage by adding a key, for instance A_CustomFinaleMusic1 = "C2M5.BadManTank2" An example custom finale script:

PANIC <- 0
TANK <- 1
DELAY <- 2
ONSLAUGHT <- 3

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
}

Overview of Squirrel in L4D2

The following are available in L4D2 by default.[14][15]

Warning.png 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

To do: Add 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.png 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

CNavMesh

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

To do 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.

<Squirrel Language>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

To do: Add Constants

See also

External links

<Squirrel Language>+<Left 4 Dead 2> = Pnkhrt-16px.png