Difference between revisions of "L4D2 Vscript Examples"
(→See also: added link to L4D2_Decrypted_mutations) |
(→Removing Weapons/Items: split script into two halfes, hopefully making it easier to understand) |
||
Line 611: | Line 611: | ||
*tonfa | *tonfa | ||
====Removing Weapons/Items==== | ====Removing Weapons/Items==== | ||
− | This script removes all weapon spawns | + | This script removes adrenaline and defibrillator spawns and all melee weapons. It might be better to [[L4D2_Vscript_Examples#Replacing_Weapons.2FItems|replace]] weapons, as opposed to delete them, because a deleted weapon spawn means that no weapon spawns at all. Which wcould end up in a saferoom with no weapons in it at all. |
<source> | <source> | ||
Msg("Removing unwanted weapons. \n"); | Msg("Removing unwanted weapons. \n"); | ||
− | + | DirectorOptions <- | |
− | + | { | |
ActiveChallenge = 1 | ActiveChallenge = 1 | ||
weaponsToRemove = | weaponsToRemove = | ||
{ | { | ||
− | + | // <Weapon script name> = 0 | |
− | weapon_adrenaline = 0 | + | weapon_adrenaline = 0 |
− | weapon_defibrillator = 0 | + | weapon_defibrillator = 0 |
− | weapon_melee = 0 //ALL melee weapons | + | weapon_melee = 0 //ALL melee weapons |
− | //Individual melee weapons need to be deleted via modelname | + | //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. | //If you're building your own map, edit the missionfile to not allow certain melee weapons instead. | ||
} | } | ||
Line 646: | Line 646: | ||
} | } | ||
} | } | ||
− | |||
function OnGameEvent_round_start_post_nav( params ) | function OnGameEvent_round_start_post_nav( params ) | ||
{ | { | ||
− | //Seeks weapon | + | //Seeks weapon _spawn entities, based on all weapon entity names in "weaponsToRemove" |
EntFire( "weapon_spawn", "Kill" ); | EntFire( "weapon_spawn", "Kill" ); | ||
foreach( wep, val in DirectorOptions.weaponsToRemove ) | foreach( wep, val in DirectorOptions.weaponsToRemove ) | ||
EntFire( wep + "_spawn", "Kill" ); | EntFire( wep + "_spawn", "Kill" ); | ||
+ | } | ||
+ | </source> | ||
+ | Removing individual melee weapons, mounted guns and carryable objects has to be handled differently.<br> | ||
+ | The previous script already has the '''OnGameEvent_round_start_post_nav''' function, so you could just copy the individual weapon lines into the existing function. | ||
+ | <source> | ||
+ | function OnGameEvent_round_start_post_nav( params ) | ||
+ | { | ||
//Deletes weapon based on model name, useful for melee and carryables. | //Deletes weapon based on model name, useful for melee and carryables. | ||
//Delete lines of weapons you want to keep. | //Delete lines of weapons you want to keep. | ||
Line 748: | Line 754: | ||
} | } | ||
− | //Mounted guns | + | //Mounted guns ------------------------------------------------------------------------------------------- |
//Gatling | //Gatling | ||
a <- null; | a <- null; | ||
Line 763: | Line 769: | ||
} | } | ||
− | //Carryables | + | //Carryables ------------------------------------------------------------------------------------------- |
//Gascan | //Gascan | ||
a <- null; | a <- null; |
Revision as of 08:21, 2 March 2021
The following are example vscripts for Left 4 Dead 2.
- Please see L4D2 Vscripts for an overview of vscripts for L4D2.
- Scripts used in official campaigns are also available.
Warning: NEVER use the = operator when trying to influence the director. Use <-. The difference is semantics. If you use =, it will throw an error if the table slot isn't defined (like if a previous script didn't define it). <-, on the other hand, will create the variable if it does not exist. Some of these samples are only portions of a full script, so make sure that the variables are created beforehand.
Contents
- 1 Iterating through entities
- 2 Handling a survivor (old method)
- 3 "Detecting"/Finding survivors (old method)
- 4 Find survivors closest to entity
- 5 "Hovering Chopper Support" example
- 6 Changing the skybox on map load
- 7 Changing witch movement type on map load
- 8 Prohibiting tank and witch spawns
- 9 Some scavenge gamemode DirectorOptions (for coop/versus finales with scavenge)
- 10 Maximum number of simultaneous specials
- 11 Dead center chapter 3 special infected limit
- 12 c1m4_atrium.nut
- 13 Tic-Tac-Toe mini-game
- 14 Pushing a player around
- 15 Timers
- 16 Bumping up special infected
- 17 Making an entity orient itself toward a player
- 18 Handling Weapons/Items
- 19 See also
- 20 External links
Iterating through entities
This is how you can iterate over the script handles of a set of entities with certain class names, names or models, and change properties/fire events on them. Note that this can also be used for iterating through the players, using the player class name.
//Disable all shadows from physical props
ent <- null;
while((ent = Entities.FindByClassname(ent,"prop_physics")) != null)
{
EntFire(ent.GetName(),"DisableShadow",0);
}
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 are no more entities found, the find function returns null, and so our loop ends.
But often prop_physics and other classes don't have targetnames. How can you reference them?
A more complete solution for such cases would be to use the full DoEntFire form and using the special "!self" targetname and passing the entity as the activator.
//Disable all shadows from physical props
ent <- null;
while((ent = Entities.FindByClassname(ent,"prop_physics")) != null)
{
DoEntFire("!self","DisableShadow","0",0,null,ent); // Will fire on named and un-named entities alike.
}
Handling a survivor (old method)
/***************************************
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.

EntFire("!nick", "SetHealth", 1)
"Detecting"/Finding survivors (old method)
[1] Just a sample you can use in your own code to find survivors. You need to call if after a delay of 10 seconds or so with a logic_auto
Something like
logic_auto > onmapspawn "logic_script_name" runscriptcode with a value of "FindSurvivors()"
Then you can iterate (loop) through the survivors like so...
/*
findsurvivors.nut
author: Lee Pumphret
http://www.leeland.net
The survivors table, once initialized, holds an entity reference to all survivors
To reference a specific survivor, you can say
survivors.nick (etc...)
*/
survivors <-{
coach = "models/survivors/survivor_coach.mdl",
ellis = "models/survivors/survivor_mechanic.mdl",
nick = "models/survivors/survivor_gambler.mdl",
rochelle = "models/survivors/survivor_producer.mdl"
}
survivors_found <- 0 // flag set to true once survivors are found
/*
Find survivors, this needs to be called after a delay. If you call it immediately,
it will fail as they have not been loaded yet, 10 sec after map load should be good.
You can call it with a logic_auto output, runscriptcode FindSurvivors()
*/
function FindSurvivors(){
foreach(s,m in survivors){
printl ("looking for "+s+" mdl:"+m);
survivor <- Entities.FindByModel(null, m)
if (survivor){
printl(s+" found: "+survivor);
survivors[s] = survivor
survivors_found++
}else{
printl(s+" NOT FOUND!: "+survivor);
survivors[s] = null
}
}
}
- Getting a handle on survivors - findsurvivors.nut - Author's website
Find survivors closest to entity
This builds off multiple scripts. Refer to the author's website for a explanation and complete code. [2]
IncludeScript("survivorclosest.nut", this);
ent <- Entities.FindByName(null, "entname");
printl("Found entity "+ent);
FindSurvivors() // make sure this script isn't called right away or they won't be found yet
who <- FindSurvivorClosestToEnt(ent); // returns entity reference to survivor
if (who){
printl(who);
printl(who+" is closest to "+ent.GetName()+" at "+who.GetOrigin());
// do something here
}else{
printl("Nothing found");
}
/* or, if you want the name returned (the key into the
survivors table), add a true value as a second param */
who <- FindSurvivorClosestToEnt(ent, 1); // returns entity reference to survivor
if (who){
printl(who);
printl(who+" is closest to "+ent.GetName()+" at "+survivors[who].GetOrigin());
// do something here
}else{
printl("Nothing found");
}
"Hovering Chopper Support" example
[3] If you want to parent a func_tracktrain to something, so it moves relative, you don't parent the path, you parent the func_tracktrain itsef to the item, and the path will move relative to this. So here's what this does.
- You make your track as normal, your "train" heli, whatever.
- Place an info_target in the center of your path with a name ex: chopper_follow
- Then you parent that func_tracktrain to the target (chopper_follow).
- Pass that target as the first entitygroup to this script.
- Then what happens is that the script computes the average position of the survivors ever 1/10 of a second, moves the info_target there (chopper_follow), and the path_track will follow.
- Have your track the height you want, relative to the info_target.

- 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
[4] Save the file to your scripts/vscripts folder as whichskybox.nut
Add a logic_script entity to your map, turn off smartedit and add a "vscripts" key with a value of whichskybox.nut
Obviously you would have to adjust fog and lighting entities as well for it to not look terrible.
Skyboxes <- [
"sky_l4d_c1_1_hdr",
"sky_l4d_c1_2_hdr",
"sky_l4d_c2m1_hdr",
"sky_l4d_night02_hdr",
"sky_l4d_predawn02_hdr",
"sky_l4d_c4m1_hdr",
"sky_l4d_c4m4_hdr",
"sky_l4d_c5_1_hdr",
"sky_l4d_c6m1_hdr"
]
worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("skyname",Skyboxes[i]) );
Changing witch movement type on map load

This was based off of the code found here: [5] Save the two files to your scripts/vscripts folder as daywitch.nut and nightwitch.nut
Add two logic_script entities to your map. Give "Name" a value of daywitchscript, turn off smartedit and add a "vscripts" key with a value of daywitch.nut.
Repeat for the other with a "Name" value of nightwitchscript, turn off smartedit again and add a "vscripts" key with a value of nightwitch.nut.
There are two scripts involved here, one for day witches and one for night. These scripts run on map load and select the numerical equivialant of Midnight (0) or Morning (2) found in worldspawn. I seperated the scripts so entity logic can tie into them. More on that later.
daywitch.nut:
Skyboxes <- [
"2"
]
worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );
nightwitch.nut:
Skyboxes <- [
"0"
]
worldspawn <- Entities.FindByClassname (null, "worldspawn");
local i = RandomInt(0,Skyboxes.len()-1);
printl("Skybox is "+Skyboxes[i]);
printl( worldspawn.__KeyValueFromString("timeofday",Skyboxes[i]) );
This is the line of entity logic I used in my test map to achieve random day or night witches. Note I start the logic_auto with a delay of 10 seconds so the AI can get ready. It doesn't seem to work if I don't, as noted in the "Hovering Chopper Support Example" above.
Class: logic_auto My output named: OnMapSpawn Targets entities named: daynightwitchcase Via this input: PickRandomShuffle Delay: 10.00 Class: logic_case My output named: OnCase01 Targets entities named: 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.
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:

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.[6]
- 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
[9] 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 and all melee weapons. It might be better to replace weapons, as opposed to delete them, because a deleted weapon spawn means that no weapon spawns at all. Which wcould end up in a saferoom with no weapons in it at all.
Msg("Removing unwanted weapons. \n");
DirectorOptions <-
{
ActiveChallenge = 1
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;
}
function ShouldAvoidItem( classname )
{
if ( ( classname != "weapon_melee" ) && ( classname in weaponsToRemove ) )
{
return true;
}
return false;
}
}
function OnGameEvent_round_start_post_nav( params )
{
//Seeks weapon _spawn entities, based on all weapon entity names in "weaponsToRemove"
EntFire( "weapon_spawn", "Kill" );
foreach( wep, val in DirectorOptions.weaponsToRemove )
EntFire( wep + "_spawn", "Kill" );
}
Removing individual melee weapons, mounted guns and carryable objects has to be handled differently.
The previous script already has the OnGameEvent_round_start_post_nav function, so you could just copy the individual weapon lines into the existing function.
function OnGameEvent_round_start_post_nav( params )
{
//Deletes weapon based on model name, useful for melee and carryables.
//Delete lines of weapons you want to keep.
//Baseball Bat
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_bat.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//Cricket Bat
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_cricket_bat.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//crowbar
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_crowbar.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//guitar
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_electric_guitar.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//fireaxe
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_fireaxe.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//frying pan
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_frying_pan.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//golf club
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_golfclub.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//katana
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_katana.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//css knife
a <- null;
while( ( a = Entities.FindByModel( a, "models/W_models/weapons/W_knife_t.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//machete
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_machete.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//pitchfork
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_pitchfork.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//shovel
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_shovel.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//tonfa
a <- null;
while( ( a = Entities.FindByModel( a, "models/weapons/melee/W_tonfa.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//Mounted guns -------------------------------------------------------------------------------------------
//Gatling
a <- null;
while( ( a = Entities.FindByModel( a, "models/w_models/Weapons/w_minigun.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//Browning
a <- null;
while( ( a = Entities.FindByModel( a, "models/w_models/Weapons/50cal.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//Carryables -------------------------------------------------------------------------------------------
//Gascan
a <- null;
while( ( a = Entities.FindByModel( a, "models/props_junk/gascan001a.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//Fireworks
a <- null;
while( ( a = Entities.FindByModel( a, "models/props_junk/explosive_box001.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//Oxygen
a <- null;
while( ( a = Entities.FindByModel( a, "models/props_equipment/oxygentank01.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
//propane
a <- null;
while( ( a = Entities.FindByModel( a, "models/props_junk/propanecanister001a.mdl" ) ) != null )
{
DoEntFire( "!self", "kill", "", 0, null, a );
}
}
Replacing Weapons/Items
//-----------------------------------------------------
Msg("Replacing unwanted weapons");
DirectorOptions <-
{
ActiveChallenge = 1
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 <-
{
ActiveChallenge = 1
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;
}
}
See also
- L4D2 Vscripts
- List of L4D2 Script Functions
- L4D2 Level Design/Boss Prohibition
- Left 4 Dead 2 Tool Updates
- Mutation Gametype (L4D2)
- trigger_finale
- info_director
- logic_script
- vscript
- Decrypted Mutation Vscripts
List of Portal 2 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