L4D2 Vscript Examples: Difference between revisions
m (changed visual representation of last two weapon scripts to make them fit in with the rest.) |
m (can't do much about this page yet, so a cleanup template and hiding some sections (that should be migrated elsewhere will do for now). This page is probably going to be deep in my backlog instead of at the front) |
||
Line 1: | Line 1: | ||
{{l4d2}} The following are example [[VScript|vscripts]] for [[Left 4 Dead 2]]. | {{cleanup|Either lacking a consistent goal in what to present, or its scripts's syntax habits would be awkward habits for beginners. Some are also better suited for a specific script function's own page.}} | ||
__NOTOC__ | |||
{{l4d2}} The following are example [[VScript|vscripts]] for [[Left 4 Dead 2]]. For other resources, see the [[#See Also|See Also]] section. | |||
*'''Please see [[L4D2 Vscripts]]''' for an overview of vscripts for L4D2. <br> | *'''Please see [[L4D2 Vscripts]]''' for an overview of vscripts for L4D2. <br> | ||
*'''[[L4D2_Vscripts#Decrypting NUC files|Scripts used in official campaigns]]''' are also available. | *'''[[L4D2_Vscripts#Decrypting NUC files|Scripts used in official campaigns]]''' are also available. | ||
<!-- | |||
===Iterating through entities=== | ===Iterating through entities=== | ||
With a<code>while</code>loop and a ''[[List of L4D2 Script Functions#CEntities|Entities]].FindByClassname()'' function, you can iterate through all entities of a matching classname, based on your arguments. | With a<code>while</code>loop and a ''[[List of L4D2 Script Functions#CEntities|Entities]].FindByClassname()'' function, you can iterate through all entities of a matching classname, based on your arguments. | ||
Line 7: | Line 10: | ||
The first parameter of ''Entities.FindByClassname()'' is named 'previous' which accepts a script handle (of an entity inherits 'CBaseEntity' specifically), which verifies if the matching entity it finds has an entity index that's higher than the current one in the 'previous' argument. If it turns out to be not, then its ignored. | The first parameter of ''Entities.FindByClassname()'' is named 'previous' which accepts a script handle (of an entity inherits 'CBaseEntity' specifically), which verifies if the matching entity it finds has an entity index that's higher than the current one in the 'previous' argument. If it turns out to be not, then its ignored. | ||
{{ExpandBox| | {{ExpandBox| | ||
< | <syntaxhighlight lang=js> | ||
local ent = null | local ent = null | ||
while( ent = Entities.FindByClassname(ent, "prop_physics") ) | while( ent = Entities.FindByClassname(ent, "prop_physics") ) | ||
{ | { | ||
printl(ent) | printl(ent) | ||
}</ | }</syntaxhighlight>}} | ||
--> | |||
=== Fetching a specific survivor === | === Fetching a specific survivor === | ||
With usage of the method<code>.FindByModel()</code>from ''[[List of L4D2 Script Functions#CEntities|Entities]]'' | With usage of the CEntities method<code>.FindByModel()</code>accessible from the ''[[List of L4D2 Script Functions#CEntities|Entities]]'' instance, this script uses it to fetch only a specific survivor, as models for all survivors are unique. | ||
All survivors | Here's Zoey being fetched: | ||
{{ExpandBox|nostartinglinebreak=1|<syntaxhighlight> | |||
local zoey = null | |||
while( zoey = Entities.FindByModel(zoey, "models/survivors/survivor_teenangst.mdl") ) | |||
{ | |||
printl( zoey.GetModelName() ) | |||
} | |||
</syntaxhighlight>}} | |||
<!-- | |||
All survivors's model path names are fetched with the<code>.GetModelName()</code>method, belonging to the ''[[List of L4D2 Script Functions#CBaseEntity|CBaseEntity]]'' class. | |||
{{ExpandBox| | {{ExpandBox| | ||
< | <syntaxhighlight lang=js> | ||
/* Should return something like this in console | /* Should return something like this in console | ||
models/survivors/survivor_teenangst.mdl | models/survivors/survivor_teenangst.mdl | ||
Line 30: | Line 42: | ||
{ | { | ||
printl( player.GetModelName() ) | printl( player.GetModelName() ) | ||
}</ | }</syntaxhighlight>}} | ||
--> | |||
===Find survivors closest to entity=== | ===Find survivors closest to entity=== | ||
This builds off multiple scripts. Refer to the [http://leeland.stores.yahoo.net/survivorclosest-nut.html author's website] for a explanation and complete code. [http://forums.steampowered.com/forums/showthread.php?t=1401390] | This builds off multiple scripts. Refer to the [http://leeland.stores.yahoo.net/survivorclosest-nut.html author's website] for a explanation and complete code. [http://forums.steampowered.com/forums/showthread.php?t=1401390] | ||
{{ExpandBox|nostartinglinebreak=1| | |||
< | <syntaxhighlight lang=js>IncludeScript("survivorclosest.nut", this); | ||
ent <- Entities.FindByName(null, "entname"); | ent <- Entities.FindByName(null, "entname"); | ||
Line 72: | Line 77: | ||
}else{ | }else{ | ||
printl("Nothing found"); | printl("Nothing found"); | ||
}</ | }</syntaxhighlight>}} | ||
Line 94: | Line 99: | ||
Obviously you would have to adjust fog and lighting entities as well for it to not look terrible. | Obviously you would have to adjust fog and lighting entities as well for it to not look terrible. | ||
{{ExpandBox|nostartinglinebreak=1| | |||
< | <syntaxhighlight lang=cpp>Skyboxes <- [ | ||
"sky_l4d_c1_1_hdr", | "sky_l4d_c1_1_hdr", | ||
"sky_l4d_c1_2_hdr", | "sky_l4d_c1_2_hdr", | ||
Line 110: | Line 115: | ||
local i = RandomInt(0,Skyboxes.len()-1); | local i = RandomInt(0,Skyboxes.len()-1); | ||
printl("Skybox is "+Skyboxes[i]); | printl("Skybox is "+Skyboxes[i]); | ||
printl( worldspawn.__KeyValueFromString("skyname",Skyboxes[i]) );</ | printl( worldspawn.__KeyValueFromString("skyname",Skyboxes[i]) );</syntaxhighlight>}} | ||
Line 124: | Line 129: | ||
daywitch.nut: | daywitch.nut: | ||
{{ExpandBox|nostartinglinebreak=1| | |||
< | <syntaxhighlight lang=cpp>Skyboxes <- [ | ||
"2" | "2" | ||
] | ] | ||
Line 132: | Line 137: | ||
local i = RandomInt(0,Skyboxes.len()-1); | local i = RandomInt(0,Skyboxes.len()-1); | ||
printl("Skybox is "+Skyboxes[i]); | printl("Skybox is "+Skyboxes[i]); | ||
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );</ | printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );</syntaxhighlight>}} | ||
nightwitch.nut: | nightwitch.nut: | ||
{{ExpandBox|nostartinglinebreak=1| | |||
< | <syntaxhighlight lang=cpp>Skyboxes <- [ | ||
"0" | "0" | ||
] | ] | ||
Line 143: | Line 148: | ||
local i = RandomInt(0,Skyboxes.len()-1); | local i = RandomInt(0,Skyboxes.len()-1); | ||
printl("Skybox is "+Skyboxes[i]); | printl("Skybox is "+Skyboxes[i]); | ||
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );</ | printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );</syntaxhighlight>}} | ||
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. | 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. | ||
< | {{ExpandBox|nostartinglinebreak=1|<syntaxhighlight lang="cpp"> | ||
Class: logic_auto | |||
My output named: OnMapSpawn | My output named: OnMapSpawn | ||
Targets entities named: daynightwitchcase | Targets entities named: daynightwitchcase | ||
Line 177: | Line 183: | ||
Class: logic_script | Class: logic_script | ||
Name: nightwitchscript | Name: nightwitchscript | ||
vscripts: nightwitch.nut</ | vscripts: nightwitch.nut</syntaxhighlight>}} | ||
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. | 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. | ||
<!-- | |||
===Prohibiting tank and witch spawns=== | ===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: | 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: | ||
< | <syntaxhighlight lang=cpp>DirectorOptions <- | ||
{ | { | ||
ProhibitBosses = true | ProhibitBosses = true | ||
}</ | }</syntaxhighlight> | ||
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) | 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) | ||
Line 193: | Line 199: | ||
===Some scavenge gamemode DirectorOptions (for coop/versus finales with scavenge)=== | ===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"): | 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"): | ||
< | <syntaxhighlight lang=cpp>DirectorOptions <- | ||
{ | { | ||
PreferredMobDirection = SPAWN_LARGE_VOLUME | PreferredMobDirection = SPAWN_LARGE_VOLUME | ||
Line 204: | Line 210: | ||
} | } | ||
NavMesh.UnblockRescueVehicleNav() | NavMesh.UnblockRescueVehicleNav() | ||
Director.ResetMobTimer()</ | Director.ResetMobTimer()</syntaxhighlight> | ||
Line 210: | Line 216: | ||
The following script not only limits the number of specials (using <code>MaxSpecials</code>), but also limits the number of specific specials allowed simultaneously and increases the maximum number of common infected: | The following script not only limits the number of specials (using <code>MaxSpecials</code>), but also limits the number of specific specials allowed simultaneously and increases the maximum number of common infected: | ||
{{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.}} | {{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.}} | ||
< | <syntaxhighlight lang=cpp>Msg("Preparing to own the Survivors"); | ||
local Dopts = DirectorScript.DirectorOptions; // get a reference to the options | local Dopts = DirectorScript.DirectorOptions; // get a reference to the options | ||
Dopts.MaxSpecials <- 20; | Dopts.MaxSpecials <- 20; | ||
Line 220: | Line 226: | ||
Dopts.JockeyLimit <- 5; | Dopts.JockeyLimit <- 5; | ||
Dopts.CommonLimit <- 120; | Dopts.CommonLimit <- 120; | ||
Dopts.SpecialRespawnInterval <- 1.0;</ | Dopts.SpecialRespawnInterval <- 1.0;</syntaxhighlight> | ||
===Dead center chapter 3 special infected limit=== | ===Dead center chapter 3 special infected limit=== | ||
For [http://forums.steampowered.com/forums/showthread.php?t=1155050 multiple SI spawns] like in the 3rd chapter of Dead Center: | For [http://forums.steampowered.com/forums/showthread.php?t=1155050 multiple SI spawns] like in the 3rd chapter of Dead Center: | ||
< | <syntaxhighlight lang=cpp> | ||
BoomerLimit = 0 | BoomerLimit = 0 | ||
SmokerLimit = 3 | SmokerLimit = 3 | ||
HunterLimit = 1 | HunterLimit = 1 | ||
ChargerLimit = 2 | ChargerLimit = 2 | ||
</ | </syntaxhighlight> | ||
You can also limit Spitters and Jockeys with | You can also limit Spitters and Jockeys with | ||
< | <syntaxhighlight lang=cpp> | ||
SpitterLimit = 0 | SpitterLimit = 0 | ||
JockeyLimit = 0 | JockeyLimit = 0 | ||
</ | </syntaxhighlight> | ||
===c1m4_atrium.nut=== | ===c1m4_atrium.nut=== | ||
In many cases in official maps, a vscript is loaded on map load to initialize some variables or the actual state of the director. This script is loaded via entity [[logic_auto]] where an output is fired to [[info_director|the director]]. There appears to be no specific reason to name the script after the name of the map other than keeping a logical progression in naming convention. Refer to c1m4_atrium.vmf, c1m4_atrium_finale.nut, and c1m4_delay.nut for more details on how the [[L4D2_Level_Design/Scavenge_Finale|scavenge finale]] is implemented. | In many cases in official maps, a vscript is loaded on map load to initialize some variables or the actual state of the director. This script is loaded via entity [[logic_auto]] where an output is fired to [[info_director|the director]]. There appears to be no specific reason to name the script after the name of the map other than keeping a logical progression in naming convention. Refer to c1m4_atrium.vmf, c1m4_atrium_finale.nut, and c1m4_delay.nut for more details on how the [[L4D2_Level_Design/Scavenge_Finale|scavenge finale]] is implemented. | ||
< | <syntaxhighlight lang=cpp>Msg(" atrium map script "+"\n") | ||
// number of cans needed to escape. | // number of cans needed to escape. | ||
Line 268: | Line 274: | ||
function GasCanPoured(){} | function GasCanPoured(){} | ||
</ | </syntaxhighlight> | ||
--> | |||
===Tic-Tac-Toe mini-game=== | ===Tic-Tac-Toe mini-game=== | ||
This example uses and vscripts to manipulate entities "registered" to a [[logic_script]] entity.[http://forums.steampowered.com/forums/showthread.php?t=1242468] | This example uses and vscripts to manipulate entities "registered" to a [[logic_script]] entity.[http://forums.steampowered.com/forums/showthread.php?t=1242468] | ||
Line 280: | Line 286: | ||
===Pushing a player around=== | ===Pushing a player around=== | ||
[http://leeland.stores.yahoo.net/puplarpu.html][http://forums.steampowered.com/forums/showthread.php?t=1399520] | [http://leeland.stores.yahoo.net/puplarpu.html][http://forums.steampowered.com/forums/showthread.php?t=1399520] | ||
< | {{ExpandBox|nostartinglinebreak=1| | ||
<syntaxhighlight lang=cpp>/*-------------------------------------------- | |||
author: http://leeland.net | author: http://leeland.net | ||
file:pushplayer.nut | file:pushplayer.nut | ||
Line 303: | Line 310: | ||
// slip in a reference to our function in the global table so we can access from all objects, | // 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* | // *technique to use sparingly! Your function could overwrite another. Name uniquely to try and avoid conflicts* | ||
::OurPushPlayer <- OurPushPlayer;</ | ::OurPushPlayer <- OurPushPlayer;</syntaxhighlight> | ||
{{note|Negative Z values do not work. A player cannot be pounded into the ground.}} | {{note|Negative Z values do not work. A player cannot be pounded into the ground.}} | ||
}} | |||
===Timers=== | ===Timers=== | ||
Timers can use the Time() function to get the server uptime, and use it to count how many seconds has elapsed from certain time. | Timers can use the Time() function to get the server uptime, and use it to count how many seconds has elapsed from certain time. | ||
< | {{ExpandBox|nostartinglinebreak=1| | ||
<syntaxhighlight lang=cpp>// Create a timer to disable commons after 2 minutes of playing the level | |||
timer_done <- false; //A boolean to ensure we only run the timer once | timer_done <- false; //A boolean to ensure we only run the timer once | ||
Line 319: | Line 327: | ||
timer_done = true; | timer_done = true; | ||
} | } | ||
}</ | }</syntaxhighlight>}} | ||
You can also make a repeating timer: | You can also make a repeating timer: | ||
< | {{ExpandBox|nostartinglinebreak=1| | ||
<syntaxhighlight lang=cpp>// Create a timer to increase common limit by 1 every 5 minutes | |||
last_set <- 0; | last_set <- 0; | ||
Line 333: | Line 342: | ||
last_set = Time(); //Keep this so the timer works properly | last_set = Time(); //Keep this so the timer works properly | ||
} | } | ||
}</ | }</syntaxhighlight>}} | ||
Here's an example using both methods: | Here's an example using both methods: | ||
< | {{ExpandBox|nostartinglinebreak=1| | ||
<syntaxhighlight lang=cpp>//After 1 minute, kill Rochelle, and after every 30 seconds, fill up nick's health | |||
timer_done <- false; //Note that this is only for the timer running once | timer_done <- false; //Note that this is only for the timer running once | ||
Line 352: | Line 362: | ||
last_set = Time(); | last_set = Time(); | ||
} | } | ||
}</ | }</syntaxhighlight> | ||
}} | |||
===Bumping up special infected=== | ===Bumping up special infected=== | ||
Line 362: | Line 372: | ||
* This example will issue a beginscript "hunterb.nut" on the director when the "boss" hunters are dispatched. This example will spawn a tank. Though you could call any script, including the built-in ones. | * This example will issue a beginscript "hunterb.nut" on the director when the "boss" hunters are dispatched. This example will spawn a tank. Though you could call any script, including the built-in ones. | ||
* Press the button to start the "boss" hunter attack. In a real map, you'd want to disable the button so it can only be triggered once. | * Press the button to start the "boss" hunter attack. In a real map, you'd want to disable the button so it can only be triggered once. | ||
{{ | {{ExpandBox|nostartinglinebreak=1|<syntaxhighlight lang=js>/* | ||
hunterhealth.nut | hunterhealth.nut | ||
author: Lee Pumphret | author: Lee Pumphret | ||
Line 452: | Line 462: | ||
} | } | ||
}</ | }</syntaxhighlight>}} | ||
Full details and code are found at the author's website, leeland.net. | Full details and code are found at the author's website, leeland.net. | ||
Line 459: | Line 469: | ||
=== Making an entity orient itself toward a player === | === Making an entity orient itself toward a player === | ||
< | {{ExpandBox|nostartinglinebreak=1|<syntaxhighlight lang=cpp> | ||
// Locks on to the nearest survivor and points the entity running this at them. | // Locks on to the nearest survivor and points the entity running this at them. | ||
// By: Rectus | // By: Rectus | ||
Line 497: | Line 507: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight>}} | ||
<!-- | |||
===Handling Weapons/Items=== | ===Handling Weapons/Items=== | ||
These are available weapon entity names, to be used in the following (or any) scripts. | These are available weapon entity names, to be used in the following (or any) scripts. | ||
Line 549: | Line 559: | ||
====Removing Weapons/Items==== | ====Removing Weapons/Items==== | ||
This script removes adrenaline and defibrillator spawns, along with all melee weapons. Generally you want to [[L4D2_Vscript_Examples#Replacing_Weapons.2FItems|replace]] weapons; Deleted weapon spawn means nothing would spawn in its place. | This script removes adrenaline and defibrillator spawns, along with all melee weapons. Generally you want to [[L4D2_Vscript_Examples#Replacing_Weapons.2FItems|replace]] weapons; Deleted weapon spawn means nothing would spawn in its place. | ||
< | <syntaxhighlight lang="js"> | ||
Msg("Removing unwanted weapons.\n"); | Msg("Removing unwanted weapons.\n"); | ||
DirectorOptions <- | DirectorOptions <- | ||
Line 589: | Line 599: | ||
EntFire( wep + "_spawn", "Kill" ); | EntFire( wep + "_spawn", "Kill" ); | ||
} | } | ||
</ | </syntaxhighlight> | ||
The hook function<code>AllowWeaponSpawn</code>can't remove specific melee weapons, mounted / machine guns or carryable objects. Fortunately, it isn't too hard to make our own method. Using the existing '''OnGameEvent_round_start_post_nav''' function hook, we can use it to run a function that deletes weapons by its model name. | The hook function<code>AllowWeaponSpawn</code>can't remove specific melee weapons, mounted / machine guns or carryable objects. Fortunately, it isn't too hard to make our own method. Using the existing '''OnGameEvent_round_start_post_nav''' function hook, we can use it to run a function that deletes weapons by its model name. | ||
< | <syntaxhighlight lang="js"> | ||
// Deletes weapons based on model name, meant for melee and carryables as 'AllowWeaponSpawn' DirectorOptions hook doesn't support doing so. | // Deletes weapons based on model name, meant for melee and carryables as 'AllowWeaponSpawn' DirectorOptions hook doesn't support doing so. | ||
// You can use 'script __RunGameEventCallbacks("round_start_post_nav", {})' to force the game event callback to run after its registered | // You can use 'script __RunGameEventCallbacks("round_start_post_nav", {})' to force the game event callback to run after its registered | ||
Line 641: | Line 651: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
====Replacing Weapons/Items==== | ====Replacing Weapons/Items==== | ||
< | <syntaxhighlight lang="js"> | ||
//----------------------------------------------------- | //----------------------------------------------------- | ||
Msg("Replacing unwanted weapons"); | Msg("Replacing unwanted weapons"); | ||
Line 665: | Line 675: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
====Default Weapons==== | ====Default Weapons==== | ||
This script makes Survivors spawn with predefined items in their inventory. | This script makes Survivors spawn with predefined items in their inventory. | ||
< | <syntaxhighlight lang="js"> | ||
DirectorOptions <- | DirectorOptions <- | ||
{ | { | ||
Line 688: | Line 698: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
--> | |||
==See also== | ==See also== | ||
=== Intros === | === Intros === |
Revision as of 17:33, 1 May 2021

The following are example vscripts for Left 4 Dead 2. For other resources, see the See Also section.
- Please see L4D2 Vscripts for an overview of vscripts for L4D2.
- Scripts used in official campaigns are also available.
Fetching a specific survivor
With usage of the CEntities method.FindByModel()
accessible from the Entities instance, this script uses it to fetch only a specific survivor, as models for all survivors are unique.
Here's Zoey being fetched:
local zoey = null
while( zoey = Entities.FindByModel(zoey, "models/survivors/survivor_teenangst.mdl") )
{
printl( zoey.GetModelName() )
}
Find survivors closest to entity
This builds off multiple scripts. Refer to the author's website for a explanation and complete code. [1]
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
[2] 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.

- Hovering chopper support - choppercover.nut - Author's Website. Contains full code, video of the example, map source to an example, and description.
- l4d2 vscript tracktrain hovertest - Video of prototype
Changing the skybox on map load
[3] 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: [4] 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: daywitchscripttemplate
Via this input: ForceSpawn
Delay: 0.00
My output named: OnCase02
Targets entities named: nightwitchscripttemplate
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.
Tic-Tac-Toe mini-game
This example uses and vscripts to manipulate entities "registered" to a logic_script entity.[5]
- 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"
Pushing a player around
/*--------------------------------------------
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;

Timers
Timers can use the Time() function to get the server uptime, and use it to count how many seconds has elapsed from certain time.
// Create a timer to disable commons after 2 minutes of playing the level
timer_done <- false; //A boolean to ensure we only run the timer once
function Update()
{
if(!timer_done && Time() >= 120)
{
DirectorScript.DirectorOptions.CommonLimit <- 0;
timer_done = true;
}
}
You can also make a repeating timer:
// Create a timer to increase common limit by 1 every 5 minutes
last_set <- 0;
function Update()
{
if(Time() >= last_set + 300)
{
//Here is where you put all the things you do after the timer runs out
DirectorScript.DirectorOptions.CommonLimit += 1;
last_set = Time(); //Keep this so the timer works properly
}
}
Here's an example using both methods:
//After 1 minute, kill Rochelle, and after every 30 seconds, fill up nick's health
timer_done <- false; //Note that this is only for the timer running once
last_set <- 0;
function Update()
{
if(!timer_done && Time() >= 60)
{
EntFire("!rochelle","sethealth",0);
timer_done = true;
}
if(Time() >= last_set + 30)
{
EntFire("!nick","sethealth",100);
last_set = Time();
}
}
Bumping up special infected
[8] This script essentially increases both hunter limit and health for an epic "Boss Battle".
- Someone on the forums wanted to up the limits on Hunters and increase their health. Unfortunately SendToConsole() respects cheats (which is kind of silly as scripts are server side), so here's a script that does that. Just call it from logic_script, setting it's Think function to "Think".
- The script code be easily modified to for any SI (or commons). Also you could extend it to give SI random amounts of health (some stronger, some weaker).
- This example will issue a beginscript "hunterb.nut" on the director when the "boss" hunters are dispatched. This example will spawn a tank. Though you could call any script, including the built-in ones.
- Press the button to start the "boss" hunter attack. In a real map, you'd want to disable the button so it can only be triggered once.
/*
hunterhealth.nut
author: Lee Pumphret
http://www.leeland.net
*/
Msg("HUNTERS v3");
BossHunterCount <- 6; // how many you want
FoundBossHunters <- 0
UseBossHunters <- 0;
HunterHealth <- 1250
OurHunters <- [] // keep track of our bumped up hunters
OurLastSeen <- null // track the last we've seen so we don't have to traverse the entire entity list
function Think(){
if (UseBossHunters){
local z
while (z = Entities.FindByModel(OurLastSeen,"models/infected/hunter.mdl")){
if (FoundBossHunters++ < BossHunterCount){
z.SetHealth(HunterHealth);
OurHunters.push(z); // save a reference to our guys
printl("Hunter #"+FoundBossHunters+" "+z.GetClassname() + " health:"+z.GetHealth());
}else {
//printl("Hunter cap hit, disabling");
UseBossHunters = 0 // turns ourselves off
DirectorScript.DirectorOptions.HunterLimit = 0
}
OurLastSeen = z
}
}
if (OurHunters){
DeadHunters <- 0;
foreach (hunter in OurHunters){
//printl("looking at hunter " + hunter + " health is "+hunter.GetHealth());
if (!hunter.IsValid() || (hunter.GetHealth() <= 1)){ /* dead hunter has 1 health, why? */
DeadHunters++;
}
}
if (DeadHunters == BossHunterCount){
Msg("Boss Hunters dead...");
OurHunters = [];
StopBossHunters();
// EntFire your sound here...
EntFire("director","beginscript", "hunters_b.nut") // or scriptname.nuc if it's a nuc
}
}
}
function StartBossHunters(){
Msg("Activating Boss Hunters")
UseBossHunters = 1
FoundBossHunters = 0
local Dopts = DirectorScript.DirectorOptions // get a reference to the options
Dopts.BoomerLimit <- 0
Dopts.SmokerLimit <- 0
Dopts.HunterLimit <- BossHunterCount
Dopts.ChargerLimit <- 0
Dopts.SpitterLimit <- 0
Dopts.JockeyLimit <- 0
Dopts.DominatorLimit <- BossHunterCount
Dopts.MaxSpecials <- BossHunterCount
EntFire("spawn_hunter","spawnzombie", "hunter") // or scriptname.nuc if it's a nuc
}
function StopBossHunters(){
Msg("Deactivating Boss Hunters")
UseBossHunters = 0
local Dopts = DirectorScript.DirectorOptions // get a reference to the options
Dopts.BoomerLimit <- 1
Dopts.SmokerLimit <- 1
Dopts.HunterLimit <- 1
Dopts.ChargerLimit <- 1
Dopts.SpitterLimit <- 1
Dopts.JockeyLimit <- 1
Dopts.DominatorLimit <- 3
Dopts.MaxSpecials <- 4
}
}
Full details and code are found at the author's website, leeland.net.
Making an entity orient itself toward a player
// Locks on to the nearest survivor and points the entity running this at them.
// By: Rectus
target <- null;
// Set the think function of the entity to 'PointEntity' for it to keep doing it.
function PointEntity()
{
// Finds the closest survivor if we don't have a target yet.
if(!target || !target.IsValid())
{
local bestTarget = null;
local player = null;
while(player = Entities.FindByClassname(player, "player"))
{
if(player.IsSurvivor())
{
if(!bestTarget || (player.GetOrigin() - self.GetOrigin()).Length() <
(bestTarget.GetOrigin() - self.GetOrigin()).Length())
{
bestTarget = player;
}
}
}
if(bestTarget)
{
target = bestTarget;
}
}
if(target)
{
self.SetForwardVector(target.GetOrigin() - self.GetOrigin());
}
}
See also
Intros
Documentations
Miscelleanous
- logic_script
List of Portal 2 Script Functions
List of Counter-Strike: Global Offensive Script Functions
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