This article relates to the game "Left 4 Dead 2". Click here for more information.

L4D2 Vscript Examples: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
No edit summary
 
(35 intermediate revisions by 10 users not shown)
Line 1: Line 1:
{{l4d2}} The following are example [[VScript|vscripts]] for [[Left 4 Dead 2]].
{{l4d2}} The following are example [[VScript|vscripts]] for [[Left 4 Dead 2]].
*'''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.
:<strong style="color:#FF0000;">[[File:Warning.png|link=|alt=]] Warning:</strong> 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.
===Iterating through entities===
===Targeting entities with a director script===
With a<code>while</code>loop and a ''[[Left 4 Dead 2/Script Functions#CEntities|Entities]].FindByClassname()'' function, you can iterate through all entities of a matching classname, based on your arguments.
Add something like this in the script for your minifinale when your horde wave gets triggered:
<pre>{
EntFire( "church_bell_relay", "Trigger", 0 )
}</pre>
Then place a logic_relay with that name to send an output to your ambient_generic.


Some official uses can be found in gallery_main, strongman_game_script, whacker_counter, whacker_timer, c1m4_atrium, c1_3_trafficmessage_frequency
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|
===Triggering a panic event through a script===
<source lang=js>
If you want to start a panic event through a script (not an onslaught) then use this:
local ent = null
<pre>
while( ent = Entities.FindByClassname(ent, "prop_physics") )
//-----------------------------------------------------
local PANIC = 0
//-----------------------------------------------------
 
DirectorOptions <-
{
{
A_CustomFinale1 = PANIC
  printl(ent)
A_CustomFinaleValue1 = 2
}</source>}}
}</pre>
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===
<pre>/***************************************
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.</pre>
 
{{note|'''"!survivorname"''' can also be used as the entity name. For example, the health of the survivor can be reduced with
 
<pre>EntFire("!nick", "SetHealth", 1)</pre>
 
'''!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===
[http://forums.steampowered.com/forums/showthread.php?t=1374401] 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...
=== Fetching a specific survivor ===
<pre>/*
With usage of the method<code>.FindByModel()</code>from ''[[Left 4 Dead 2/Script Functions#CEntities|Entities]]'' class, this script uses it to fetch only a specific survivor. Models is one of the unique features all survivors possess, so we can use it to find just a specific survivor.
findsurvivors.nut
author: Lee Pumphret
http://www.leeland.net


The survivors table, once initialized, holds an entity reference to all survivors
All survivors have unique model path names, which is attained with the<code>.GetModelName()</code>method, belonging to the ''[[Left 4 Dead 2/Script Functions#CBaseEntity|CBaseEntity]]'' class. As players are also entities (the name<code>player</code>is their classname), iterate through all of them with a<code>Entities.FindByClassname()</code> function, then<code>printl()</code>their model names in console.
To reference a specific survivor, you can say
{{ExpandBox|
survivors.nick (etc...)
<source lang=js>
/* Should return something like this in console
models/survivors/survivor_teenangst.mdl
models/survivors/survivor_namvet.mdl
models/survivors/survivor_biker.mdl
models/survivors/survivor_manager.mdl
*/
*/
local player = null
while( player = Entities.FindByClassname(player, "player") )
{
  printl( player.GetModelName() )
}</source>}}


survivors <-{
So then, this would be what fetching Zoey looks like:
  coach = "models/survivors/survivor_coach.mdl",
{{ExpandBox|
  ellis = "models/survivors/survivor_mechanic.mdl",
<source lang=js>
  nick = "models/survivors/survivor_gambler.mdl",
// Although things like Sourcemod can allow more than 1 Zoeys, for the sake of the example, let's assume there's only can be one Zoey at all times.
  rochelle = "models/survivors/survivor_producer.mdl"
local zoey = Entities.FindByModel(null, "models/survivors/survivor_teenangst.mdl")
}
printl(zoey)
 
</source>}}
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
    }
  }
}</pre>
 
*[http://www.leeland.net/gehaonsusu.html Getting a handle on survivors - findsurvivors.nut] - Author's website


===Find survivors closest to entity===
===Find survivors closest to entity===
This builds off multiple scripts. Refer to the [http://www.leeland.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]


<pre>IncludeScript("survivorclosest.nut", this);
<source lang=cpp>IncludeScript("survivorclosest.nut", this);


ent <- Entities.FindByName(null, "entname");
ent <- Entities.FindByName(null, "entname");
Line 128: Line 72:
}else{
}else{
   printl("Nothing found");
   printl("Nothing found");
}</pre>
}</source>
 


==="Hovering Chopper Support" example===
==="Hovering Chopper Support" example===
[http://www.leeland.net/hochsuch.html] 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.
[http://leeland.stores.yahoo.net/hochsuch.html] 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.
*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
*Place an info_target in the center of your path with a name ex: chopper_follow
Line 139: Line 84:
*Have your track the height you want, relative to the info_target.
*Have your track the height you want, relative to the info_target.
{{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.}}
{{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.}}
*[http://www.leeland.net/hochsuch.html Hovering chopper support - choppercover.nut] - Author's Website. Contains full code, video of the example, map source to an example, and description.
*[http://leeland.stores.yahoo.net/hochsuch.html Hovering chopper support - choppercover.nut] - Author's Website. Contains full code, video of the example, map source to an example, and description.
*[http://www.youtube.com/watch?v=orm1SJwaijE l4d2 vscript tracktrain hovertest] - Video of prototype
*[http://www.youtube.com/watch?v=orm1SJwaijE l4d2 vscript tracktrain hovertest] - Video of prototype


===Changing the skybox on map load===
===Changing the skybox on map load===
[http://www.leeland.net/chskonmaplow.html] Save the file to your scripts/vscripts folder as whichskybox.nut
[http://leeland.stores.yahoo.net/chskonmaplow.html] 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
Add a logic_script entity to your map, turn off smartedit and add a "vscripts" key with a value of whichskybox.nut
Line 149: Line 95:
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.


<pre>Skyboxes <- [
<source lang=cpp>Skyboxes <- [
   "sky_l4d_c1_1_hdr",
   "sky_l4d_c1_1_hdr",
   "sky_l4d_c1_2_hdr",
   "sky_l4d_c1_2_hdr",
Line 164: Line 110:
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]) );</pre>
printl( worldspawn.__KeyValueFromString("skyname",Skyboxes[i]) );</source>
 
===Changing witch movement type on map load===
This was based off of the code found here: [http://www.leeland.net/chskonmaplow.html] 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:


<pre>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]) );</pre>
nightwitch.nut:
<pre>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]) );</pre>
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.
<pre>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</pre>
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.
===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:
<pre>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)
}</pre>
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===
===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:


<pre>DirectorOptions <-
<source lang=cpp>DirectorOptions <-
{
{
   ProhibitBosses = true
   ProhibitBosses = true
}</pre>
}</source>


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)


===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"):
<pre>DirectorOptions <-
<source lang=cpp>DirectorOptions <-
{
{
PreferredMobDirection = SPAWN_LARGE_VOLUME
PreferredMobDirection = SPAWN_LARGE_VOLUME
Line 279: Line 136:
}
}
NavMesh.UnblockRescueVehicleNav()
NavMesh.UnblockRescueVehicleNav()
Director.ResetMobTimer()</pre>
Director.ResetMobTimer()</source>
 
===Dark carnival onslaught script===
Here's the onslaught script Valve used for the 4th map of Dark Carnival:


<pre>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()</pre>


===Maximum number of simultaneous specials===
===Maximum number of simultaneous specials===
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.}}
<pre>Msg("Preparing to own the Survivors");
<source 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 323: Line 152:
Dopts.JockeyLimit <- 5;
Dopts.JockeyLimit <- 5;
Dopts.CommonLimit <- 120;
Dopts.CommonLimit <- 120;
Dopts.SpecialRespawnInterval <- 1.0;</pre>
Dopts.SpecialRespawnInterval <- 1.0;</source>
 


===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:
<pre>
<source lang=cpp>
BoomerLimit = 0
BoomerLimit = 0
SmokerLimit = 3
SmokerLimit = 3
HunterLimit = 1
HunterLimit = 1
ChargerLimit = 2
ChargerLimit = 2
</pre>
</source>


You can also limit Spitters and Jockeys with
You can also limit Spitters and Jockeys with
<pre>
<source lang=cpp>
SpitterLimit = 0
SpitterLimit = 0
JockeyLimit = 0
JockeyLimit = 0
</pre>
</source>
 


===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.
<pre>Msg(" atrium map script "+"\n")
<source lang=cpp>Msg(" atrium map script "+"\n")
 
// number of cans needed to escape.
// number of cans needed to escape.
 
if ( Director.IsSinglePlayerGame() )
if ( Director.IsSinglePlayerGame() )
{
{
Line 354: Line 185:
                 NumCansNeeded <- 13
                 NumCansNeeded <- 13
}
}
 
 
DirectorOptions <-
DirectorOptions <-
{
{
             
 
CommonLimit = 15
CommonLimit = 15
 
}
}
 
NavMesh.UnblockRescueVehicleNav()
NavMesh.UnblockRescueVehicleNav()
 
EntFire( "progress_display", "SetTotalItems", NumCansNeeded )
EntFire( "progress_display", "SetTotalItems", NumCansNeeded )
 
 
function GasCanPoured(){}
function GasCanPoured(){}
</pre>
</source>
 


===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]
*[http://www.leeland.net/l4d2-scripts-play-tic-tac-toe.html Writing a Mini Game - Tic Tac Toe - Part One] - Author's Website
*[http://leeland.stores.yahoo.net/l4d2-scripts-play-tic-tac-toe.html Writing a Mini Game - Tic Tac Toe - Part One] - Author's Website
*[http://www.youtube.com/watch?v=B9hASsoHK54 l4d2 - Vscript example - Tic-Tac-Toe] - Video of early Prototype
*[http://www.youtube.com/watch?v=B9hASsoHK54 l4d2 - Vscript example - Tic-Tac-Toe] - Video of early Prototype
*[http://www.youtube.com/watch?v=mvDFEoA0ib0 l4d2 - Vscript example - Tic-Tac-Toe - updated] - Video of current version with "brutally misanthropic AI"
*[http://www.youtube.com/watch?v=mvDFEoA0ib0 l4d2 - Vscript example - Tic-Tac-Toe - updated] - Video of current version with "brutally misanthropic AI"


===Pushing a player around===
===Pushing a player around===
[http://www.leeland.net/puplarpu.html][http://forums.steampowered.com/forums/showthread.php?t=1399520]
[http://leeland.stores.yahoo.net/puplarpu.html]
<pre>/*--------------------------------------------
<source lang=cpp>/*--------------------------------------------
author: http://leeland.net
author: http://leeland.net
file:pushplayer.nut
file:pushplayer.nut
description:  
description:
Allows you to add a velocity vector to any player via trigger.  
Allows you to add a velocity vector to any player via trigger.
Call it from a trigger with something like
Call it from a trigger with something like
OnStartTouch !activator runscriptcode OurPushPlayer(0,0,400)
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.  
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
Z Values around 700 will incap, 1000 or so will prove fatal


Line 402: Line 235:
// 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;</pre>
::OurPushPlayer <- OurPushPlayer;</source>
{{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.}}
===Looping through entities===
This is how you can loop through entities with certain classname or model, and change keyvalues/fire events on them.
<pre>//Disable all shadows from physical props
ent <- null;
while((ent = Entities.FindByClassname(ent,"prop_physics")) != null)
{
  EntFire(ent,"DisableShadow",0);
}</pre>
What this does? Creates an ent variable, sets it to the found entity in the loop, and the finding always starts from the previous entity. Once there is no more entities found, the find function returns null, and so our loop ends.


===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.
<pre>// Create a timer to disable commons after 2 minutes of playing the level
<source 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 430: Line 250:
       timer_done = true;
       timer_done = true;
   }
   }
}</pre>
}</source>


You can also make a repeating timer:
You can also make a repeating timer:
<pre>// Create a timer to increase common limit by 1 every 5 minutes
<source lang=cpp>// Create a timer to increase common limit by 1 every 5 minutes


last_set <- 0;
last_set <- 0;
Line 440: Line 260:
   if(Time() >= last_set + 300)
   if(Time() >= last_set + 300)
   {
   {
      //Here is where you put all the things you do after the timer runs out
       DirectorScript.DirectorOptions.CommonLimit += 1;
       DirectorScript.DirectorOptions.CommonLimit += 1;
       last_set = Time();
       last_set = Time(); //Keep this so the timer works properly
   }
   }
}</pre>
}</source>


Here's an example using both methods:
Here's an example using both methods:
<pre>//After 1 minute, kill Rochelle, and after every 30 seconds, fill up nick's health
<source 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 462: Line 283:
       last_set = Time();
       last_set = Time();
   }
   }
}</pre>
}</source>
 


==Bumping up special infected==
===Bumping up special infected===
[http://www.leeland.net/buupspin.html] This script essentially increases both hunter limit and health for an epic "Boss Battle".
[http://leeland.stores.yahoo.net/buupspin.html] This script essentially increases both hunter limit and health for an epic "Boss Battle".


* Someone on the [http://forums.steampowered.com/forums/showthread.php?t=1757430 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".
* Someone on the [http://forums.steampowered.com/forums/showthread.php?t=1757430 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".
Line 471: Line 293:
* 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.
{{ScrollBox|<pre>/*
{{ScrollBox|<source lang=cpp>/*
hunterhealth.nut
hunterhealth.nut
author: Lee Pumphret
author: Lee Pumphret
Line 561: Line 383:
}
}


}</pre>}}
}</source>}}
 
Full details and code are found at the author's website, leeland.net.
 
 
=== Making an entity orient itself toward a player ===
 
<source lang=cpp>
// 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());
}
}
</source>
 
===Handling Weapons/Items===
These are available weapon entity names, to be used in the following (or any) scripts.
*weapon_adrenaline
*weapon_defibrillator
*weapon_pain_pills
*weapon_first_aid_kit
*weapon_molotov
*weapon_pipe_bomb
*weapon_vomitjar
*weapon_pistol
*weapon_pistol_magnum
*weapon_rifle
*weapon_rifle_ak47
*weapon_rifle_desert
*weapon_rifle_m60
*weapon_rifle_sg552
*weapon_shotgun_chrome
*weapon_pumpshotgun
*weapon_autoshotgun
*weapon_shotgun_spas
*weapon_smg
*weapon_smg_mp5
*weapon_smg_silenced
*weapon_sniper_awp
*weapon_sniper_military
*weapon_sniper_scout
*weapon_hunting_rifle
*weapon_upgradepack_explosive
*weapon_upgradepack_incendiary
*upgrade_item //Lasers
*weapon_grenade_launcher
*weapon_chainsaw
*ammo //ammo piles
Melee weapons seem to only work for "DefaultItems"
*baseball_bat
*cricket_bat
*crowbar
*electric_guitar
*fireaxe
*frying_pan
*golfclub
*katana
*knife
*knife
*machete
*pitchfork
*shovel
*tonfa
====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.
<source lang="js">
Msg("Removing unwanted weapons.\n");
DirectorOptions <-
{
  weaponsToRemove =
{
// <Weapon script name> = 0
weapon_adrenaline = 0
weapon_defibrillator = 0


Full details and code are found at the author's website, leeland.net.  
weapon_melee = 0 //ALL melee weapons
//Individual melee weapons need to be deleted via modelname.
//If you're building your own map, edit the missionfile to not allow certain melee weapons instead.
  }


==See also==
function AllowWeaponSpawn( classname )
*[[L4D2 Vscripts]]
{
if ( classname in weaponsToRemove )
{
return false;
}
return true;
}
// This applies for survivor bots only
function ShouldAvoidItem( classname )
{
if ( ( classname != "weapon_melee" ) && ( classname in weaponsToRemove ) )
{
return true;
}
return false;
}
}
function OnGameEvent_round_start_post_nav( params )
{
//Remove weapon_*_spawn entities, with * being an entity name in "weaponsToRemove"
EntFire( "weapon_spawn", "Kill" );
foreach( wep, val in DirectorOptions.weaponsToRemove )
EntFire( wep + "_spawn", "Kill" );
}
</source>
 
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.
<source lang="js">
// 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
function OnGameEvent_round_start_post_nav( params )
{
// Keep our model path names here; Can be fetched with the .GetModelName() method for CBaseEntity
//// For melees, it doesn't remove ones already in a player's inventory, as those are "predicted_viewmodel" entities using the v_* prefix models.
local ItemstoRemove_ModelPaths =
[
// ** Melees **
"models/weapons/melee/w_bat.mdl", // Baseball Bat
"models/weapons/melee/w_cricket_bat.mdl",
"models/weapons/melee/w_crowbar.mdl",
"models/weapons/melee/w_electric_guitar.mdl",
"models/weapons/melee/w_fireaxe.mdl",
"models/weapons/melee/w_frying_pan.mdl",
"models/weapons/melee/w_golfclub.mdl",
"models/weapons/melee/w_katana.mdl",
"models/weapons/melee/w_machete.mdl",
"models/weapons/melee/w_pitchfork.mdl",
"models/weapons/melee/w_shovel.mdl",
"models/weapons/melee/w_tonfa.mdl",
"models/w_models/weapons/w_knife_t.mdl", // CS:S Knife
 
// ** Mounted Guns **
"models/w_models/weapons/w_minigun.mdl", // 'prop_minigun_l4d1'; Rapid firing gatling minigun found in maps like No Mercy
"models/w_models/weapons/50cal.mdl", // 'prop_mounted_machine_gun' / 'prop_minigun'; The minigun found in Swamp Fever - Plantation
 
// ** Carryable Props **
"models/props_junk/gascan001a.mdl",
"models/props_junk/explosive_box001.mdl", // Fireworks crate
"models/props_equipment/oxygentank01.mdl",
"models/props_junk/propanecanister001a.mdl",
]
// automatically grab each index in the array
foreach( modelpath in ItemstoRemove_ModelPaths )
{
local weapon_ent = null
while( weapon_ent = Entities.FindByModel(weapon_ent, modelpath) )
weapon_ent.Kill()
 
/* Function above is same as doing this:
while( weapon_ent = Entities.FindByModel(weapon_ent, modelpath) )
{
weapon_ent.Kill()
}
*/
}
}
</source>
 
====Replacing Weapons/Items====
<source lang="js">
//-----------------------------------------------------
Msg("Replacing unwanted weapons");
DirectorOptions <-
{
weaponsToConvert =
{
// "To Replace" = "Replace with *_spawn"
weapon_smg = "weapon_smg_mp5_spawn"
weapon_rifle = "weapon_rifle_sg552_spawn"
}
 
function ConvertWeaponSpawn( classname )
{
if ( classname in weaponsToConvert )
{
return weaponsToConvert[classname];
}
return 0;
}
}
</source>
 
====Default Weapons====
This script makes Survivors spawn with predefined items in their inventory.
<source lang="js">
DirectorOptions <-
{
DefaultItems =
[
"weapon_molotov",
"pitchfork",
//Using two "weapon_pistol" makes you spawn holding two pistols.
]
 
function GetDefaultItem( idx )
{
if ( idx < DefaultItems.len() )
{
return DefaultItems[idx];
}
return 0;
}
}
</source>
 
==Map/Gamemode Specific Director Options==
It is possible to make one single script for all maps of a campaign, which will automatically change a few settings if a specific map or gamemode is loaded.<br>
Here's how to change director option based on map names:
<source lang="js">
if ( Director.GetMapName() == "MAP_NAME_1" || Director.GetMapName() == "MAP_NAME_2" || Director.GetMapName() == "MAP_NAME_3" )
{
DirectorOptions.GasCansOnBacks <- true;
}
</source>
Here's how to change director option based on base gamemode:
<source lang="js">
if ( Director.GetGameModeBase() == "survival" || Director.GetGameModeBase() == "scavenge" || Director.GetGameModeBase() == "holdout" )
{
    DirectorOptions.GasCansOnBacks <- false;
}
</source>
The two above lines in the same script will enable the "Hard Rain" cans on the defined maps "MAP_NAME_1, MAP_NAME_2, and MAP_NAME_3. These cans will be disabled on those maps if played in Survival, Scavenge, Holdout and any game modes based on those.
 
== See also ==
=== Intros ===
*[[VScript]]
*[[L4D2_Vscripts|L4D2 VScripts]]
*[[Mutation Gametype (L4D2)]]
*[[L4D2 Gamemodes File]]
=== Documentations ===
*[[VSLib]]
*[[L4D2_Director_Scripts|L4D2 Director Scripts]]
*[[Left 4 Dead 2/Script Functions|L4D2 Script Functions]]
*[[L4D2_Decrypted_mutations|Decrypted Mutation Vscripts]]
*[[L4D2_Level_Design/Boss_Prohibition|L4D2 Level Design/Boss Prohibition]]
*[[L4D2_Level_Design/Boss_Prohibition|L4D2 Level Design/Boss Prohibition]]
*[[Left_4_Dead_2_Tool_Updates|Left 4 Dead 2 Tool Updates]]
*[[L4D2_Level_Design/Custom_Finale|Custom Finales]] ''Contains all original finale scripts as reference.''
*[[Mutation Gametype (L4D2)]]
*[[L4D2 EMS|Extended Mutation System]]
 
=== Miscelleanous ===
*[[Logic_script|logic_script]]
*[[Trigger_finale|trigger_finale]]
*[[Trigger_finale|trigger_finale]]
*[[Info_director|info_director]]
*[[Info_director|info_director]]
*[[Logic_script|logic_script]]
*[[List of L4D2 Cvars]]
*[[VScript|vscript]]


==External links==
==External links==
Line 580: Line 665:
* [http://forums.steampowered.com/forums/showthread.php?t=1238461 It's the vscript'ing documentation FAQ! (Steam forums)]
* [http://forums.steampowered.com/forums/showthread.php?t=1238461 It's the vscript'ing documentation FAQ! (Steam forums)]
* [http://forums.steampowered.com/forums/showthread.php?t=1242468 Tutorial - Writing a Mini Game - Tic Tac Toe - Part One (Steam Forums)]
* [http://forums.steampowered.com/forums/showthread.php?t=1242468 Tutorial - Writing a Mini Game - Tic Tac Toe - Part One (Steam Forums)]
**[http://www.leeland.net/l4d2-scripts-play-tic-tac-toe.html Writing a Mini Game - Tic Tac Toe - Part One] - Author's Website
**[http://leeland.stores.yahoo.net/l4d2-scripts-play-tic-tac-toe.html Writing a Mini Game - Tic Tac Toe - Part One] - Author's Website
**[http://www.youtube.com/watch?v=B9hASsoHK54 l4d2 - Vscript example - Tic-Tac-Toe] - Video of early Prototype
**[http://www.youtube.com/watch?v=B9hASsoHK54 l4d2 - Vscript example - Tic-Tac-Toe] - Video of early Prototype
**[http://www.youtube.com/watch?v=mvDFEoA0ib0 l4d2 - Vscript example - Tic-Tac-Toe - updated] - Video of current version with "brutally misanthropic AI"
**[http://www.youtube.com/watch?v=mvDFEoA0ib0 l4d2 - Vscript example - Tic-Tac-Toe - updated] - Video of current version with "brutally misanthropic AI"
Line 589: Line 674:
* [http://www.valvesoftware.com/publications/2009/ai_systems_of_l4d_mike_booth.pdf The AI Systems of Left 4 Dead by Michael Booth (PDF)]
* [http://www.valvesoftware.com/publications/2009/ai_systems_of_l4d_mike_booth.pdf The AI Systems of Left 4 Dead by Michael Booth (PDF)]
* {{as}}[http://swarmarmory.com/forums/viewtopic.php?f=19&t=541 "Creating a "Money"/Point System"] - Swarm Armory
* {{as}}[http://swarmarmory.com/forums/viewtopic.php?f=19&t=541 "Creating a "Money"/Point System"] - Swarm Armory
[[Category:Left_4_Dead_2]]
{{l4d2 topicon}}
[[Category:VScript examples]]
[[Category:Scripting]]
[[Category:Scripting]]

Latest revision as of 23:20, 28 February 2025

Left 4 Dead 2 The following are example vscripts for Left 4 Dead 2.

Iterating through entities

With awhileloop and a Entities.FindByClassname() function, you can iterate through all entities of a matching classname, based on your arguments.

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.


local ent = null
while( ent = Entities.FindByClassname(ent, "prop_physics") )
{
   printl(ent)
}

Fetching a specific survivor

With usage of the method.FindByModel()from Entities class, this script uses it to fetch only a specific survivor. Models is one of the unique features all survivors possess, so we can use it to find just a specific survivor.

All survivors have unique model path names, which is attained with the.GetModelName()method, belonging to the CBaseEntity class. As players are also entities (the nameplayeris their classname), iterate through all of them with aEntities.FindByClassname() function, thenprintl()their model names in console.


/* Should return something like this in console
models/survivors/survivor_teenangst.mdl
models/survivors/survivor_namvet.mdl
models/survivors/survivor_biker.mdl
models/survivors/survivor_manager.mdl
*/
local player = null
while( player = Entities.FindByClassname(player, "player") )
{
   printl( player.GetModelName() )
}

So then, this would be what fetching Zoey looks like:


// Although things like Sourcemod can allow more than 1 Zoeys, for the sake of the example, let's assume there's only can be one Zoey at all times.
local zoey = Entities.FindByModel(null, "models/survivors/survivor_teenangst.mdl")
printl(zoey)

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.
Note.pngNote: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

[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]) );


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()


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.pngNote: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

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 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 scavenge finale is implemented.

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.[4]


Pushing a player around

[5]

/*--------------------------------------------
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.pngNote:Negative Z values do not work. A player cannot be pounded into the ground.

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

[6] 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());
	}
}

Handling Weapons/Items

These are available weapon entity names, to be used in the following (or any) scripts.

  • weapon_adrenaline
  • weapon_defibrillator
  • weapon_pain_pills
  • weapon_first_aid_kit
  • weapon_molotov
  • weapon_pipe_bomb
  • weapon_vomitjar
  • weapon_pistol
  • weapon_pistol_magnum
  • weapon_rifle
  • weapon_rifle_ak47
  • weapon_rifle_desert
  • weapon_rifle_m60
  • weapon_rifle_sg552
  • weapon_shotgun_chrome
  • weapon_pumpshotgun
  • weapon_autoshotgun
  • weapon_shotgun_spas
  • weapon_smg
  • weapon_smg_mp5
  • weapon_smg_silenced
  • weapon_sniper_awp
  • weapon_sniper_military
  • weapon_sniper_scout
  • weapon_hunting_rifle
  • weapon_upgradepack_explosive
  • weapon_upgradepack_incendiary
  • upgrade_item //Lasers
  • weapon_grenade_launcher
  • weapon_chainsaw
  • ammo //ammo piles

Melee weapons seem to only work for "DefaultItems"

  • baseball_bat
  • cricket_bat
  • crowbar
  • electric_guitar
  • fireaxe
  • frying_pan
  • golfclub
  • katana
  • knife
  • knife
  • machete
  • pitchfork
  • shovel
  • tonfa

Removing Weapons/Items

This script removes adrenaline and defibrillator spawns, along with all melee weapons. Generally you want to replace weapons; Deleted weapon spawn means nothing would spawn in its place.

Msg("Removing unwanted weapons.\n");
DirectorOptions <-
{
  	weaponsToRemove =
 	{
	//	<Weapon script name>	= 0
		weapon_adrenaline 		= 0
		weapon_defibrillator 	= 0

		weapon_melee 			= 0 //ALL melee weapons
		//Individual melee weapons need to be deleted via modelname.
		//If you're building your own map, edit the missionfile to not allow certain melee weapons instead.
  	}

 	function AllowWeaponSpawn( classname )
 	{
 		if ( classname in weaponsToRemove )
 		{
 			return false;
 		}
 		return true;
 	}
 	// This applies for survivor bots only
 	function ShouldAvoidItem( classname )
 	{
 		if ( ( classname != "weapon_melee" ) && ( classname in weaponsToRemove ) )
 		{
 			return true;
 		}
 		return false;
 	}
}
function OnGameEvent_round_start_post_nav( params )
{
	//Remove weapon_*_spawn entities, with * being an entity name in "weaponsToRemove"
	EntFire( "weapon_spawn", "Kill" );
 	foreach( wep, val in DirectorOptions.weaponsToRemove )
 		EntFire( wep + "_spawn", "Kill" );
}

The hook functionAllowWeaponSpawncan'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.

// 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
function OnGameEvent_round_start_post_nav( params )
{
	// Keep our model path names here; Can be fetched with the .GetModelName() method for CBaseEntity
	//// For melees, it doesn't remove ones already in a player's inventory, as those are "predicted_viewmodel" entities using the v_* prefix models.
	local ItemstoRemove_ModelPaths =
	[
		// ** Melees **
		"models/weapons/melee/w_bat.mdl", // Baseball Bat
		"models/weapons/melee/w_cricket_bat.mdl",
		"models/weapons/melee/w_crowbar.mdl",
		"models/weapons/melee/w_electric_guitar.mdl",
		"models/weapons/melee/w_fireaxe.mdl",
		"models/weapons/melee/w_frying_pan.mdl",
		"models/weapons/melee/w_golfclub.mdl",
		"models/weapons/melee/w_katana.mdl",
		"models/weapons/melee/w_machete.mdl",
		"models/weapons/melee/w_pitchfork.mdl",
		"models/weapons/melee/w_shovel.mdl",
		"models/weapons/melee/w_tonfa.mdl",
		"models/w_models/weapons/w_knife_t.mdl", // CS:S Knife

		// ** Mounted Guns **
		"models/w_models/weapons/w_minigun.mdl", // 'prop_minigun_l4d1'; Rapid firing gatling minigun found in maps like No Mercy
		"models/w_models/weapons/50cal.mdl", // 'prop_mounted_machine_gun' / 'prop_minigun'; The minigun found in Swamp Fever - Plantation

		// ** Carryable Props **
		"models/props_junk/gascan001a.mdl",
		"models/props_junk/explosive_box001.mdl", // Fireworks crate
		"models/props_equipment/oxygentank01.mdl",
		"models/props_junk/propanecanister001a.mdl",
	]
	// automatically grab each index in the array
	foreach( modelpath in ItemstoRemove_ModelPaths )
	{
		local weapon_ent = null
		while( weapon_ent = Entities.FindByModel(weapon_ent, modelpath) )
			weapon_ent.Kill()

		/* Function above is same as doing this:
		while( weapon_ent = Entities.FindByModel(weapon_ent, modelpath) )
		{
			weapon_ent.Kill()
		}
		*/
	}
}

Replacing Weapons/Items

//-----------------------------------------------------
Msg("Replacing unwanted weapons");
DirectorOptions <-
{
 	weaponsToConvert =
 	{
	// "To Replace"		=	"Replace with *_spawn"
		weapon_smg		=	"weapon_smg_mp5_spawn"
		weapon_rifle	=	"weapon_rifle_sg552_spawn"
 	}

 	function ConvertWeaponSpawn( classname )
 	{
 		if ( classname in weaponsToConvert )
 		{
 			return weaponsToConvert[classname];
		}
		return 0;
	}
}

Default Weapons

This script makes Survivors spawn with predefined items in their inventory.

DirectorOptions <-
{
 	DefaultItems =
 	[
 		"weapon_molotov",
 		"pitchfork",
		//Using two "weapon_pistol" makes you spawn holding two pistols.
 	]

 	function GetDefaultItem( idx )
 	{
 		if ( idx < DefaultItems.len() )
 		{
 			return DefaultItems[idx];
 		}
 		return 0;
 	}
}

Map/Gamemode Specific Director Options

It is possible to make one single script for all maps of a campaign, which will automatically change a few settings if a specific map or gamemode is loaded.
Here's how to change director option based on map names:

if ( Director.GetMapName() == "MAP_NAME_1" || Director.GetMapName() == "MAP_NAME_2" || Director.GetMapName() == "MAP_NAME_3" )
{
	DirectorOptions.GasCansOnBacks <- true;
}

Here's how to change director option based on base gamemode:

if ( Director.GetGameModeBase() == "survival" || Director.GetGameModeBase() == "scavenge" || Director.GetGameModeBase() == "holdout" )
{
    DirectorOptions.GasCansOnBacks <- false;
}

The two above lines in the same script will enable the "Hard Rain" cans on the defined maps "MAP_NAME_1, MAP_NAME_2, and MAP_NAME_3. These cans will be disabled on those maps if played in Survival, Scavenge, Holdout and any game modes based on those.

See also

Intros

Documentations

Miscelleanous

External links

Alternative Documentation