L4D2 Vscript Examples

From Valve Developer Community
Revision as of 17:35, 1 May 2021 by Orinuse (talk | contribs) (info add-on to the cleanup template)
Jump to navigation Jump to search
Broom icon.png
This article or section needs to be cleaned up to conform to a higher standard of quality because:
Either lacking a consistent goal in what to present, or its scripts's syntax habits would be awkward habits for beginners. Some (now hidden) are also better suited for a specific script function's own page.
For help, see the VDC Editing Help and Wikipedia cleanup process. Also, remember to check for any notes left by the tagger at this article's talk page.

Left 4 Dead 2 The following are example vscripts for Left 4 Dead 2. For other resources, see the See Also section.

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


Changing witch movement type on map load

Note.pngNote:A better example of this, using a single script file, can be found here

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]


Pushing a player around

[6][7]


/*--------------------------------------------
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

[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

External links

Alternative Documentation