VScript Fundamentals
This article aims to describe fundamental concepts, and common uses of VScript scripting. It will not go into all concepts related to the language itself, so things such as explaining what are variables or data types will be ignored.
Contents
Running Code
VScripts are loaded in a variety of ways, but there are 3 main notable approaches for different scenarios. Below is a list of approaches for the respective Source engine, with a few examples:
Source
Concept | Description | |
---|---|---|
Console Command | script
|
Runs the inputted text as code. Will all be treated as on one line. |
script_execute
|
The script with the given name (and path) will be executed from <game>/scripts/vscripts/ .
| |
Hammer Inputs | RunScriptCode
|
Runs the given text as code in one line, but double quotation marks cannot be used as it will corrupt your VMF, so you can't pass strings. Otherwise is identical to script command.
|
RunScriptFile
|
Start running a script of the same given name. Identical to script_execute command.
| |
CallScriptFunction
|
Looks up the local function with the name specified by the parameter, then calls it with no arguments. | |
VScripts executed automatically |
<game>/scripts/vscripts/mapspawn.nut
|
Scripts of this name run once when a map is loaded for the first time. Can conflict with other scripts. Note:This script is executed before any entities exist so it needs to be delayed most of the time.
|
left4dead2/scripts/vscripts/mapspawn_addon.nut (only in )
|
Identical to the above, except multiple scripts of the same name do not conflict with each other and all run together. Note:Multiple
mapspawn_addon all write to the same scope, overwriting any identical non-local functions or variable! |
Debugging
It is possible to attach a debugger to running code, which allows to create breakpoints, step line by line, and view the state of all variables during each step. To do that, a community made extension for Visual Studio Code can be used - VScript Debugger It is meant for Source 1 games only. For debugging VScripts in games based on Source 2 there is Eclipse Lua Development Tools
Script Scopes
The scripting environment consists of associative arrays, or tables, nested inside each other. One of these tables is the root table[Correct for Lua?], containing every other table, function, variable and so on. It can be imagined like a tree hierarchy where the root table is the root, other tables represent nodes and everything else (variables, functions, ...) represents a leaf (containing no more than their "value").
Thus, when a script is loaded, it is placed inside one specific table (referred as its script scope), and any code inside the script is executed. All variables, functions and classes in the script persist in the script scope after the code has finished executing.
Squirrel
25715b862_script.2v2 =
{
DispatchOnPostSpawn = (function : 0x09EEC980)
self = ([129] logic_script: script.2v2)
__vname = "25715b862_script.2v2"
PrecacheCallChain = (instance : 0x09CBE850)
OnPostSpawnCallChain = (instance : 0x09CBE800)
EnableWingman = (function : 0x09EEC900)
__vrefs = 1
DispatchPrecache = (function : 0x09EEC940)
}
|
The root table is obtained with getroottable()
.
You can view every data within the scripting environment scopes using the function below. Save it as a .nut
file, use script_execute
on the script, then call the function with the root table as the argument using the script
command.
From the Left 4 Dead 2 script named sm_utilities.nut :// This function is a slightly edited variant of the one in L4D2 (g_ModeScript.DeepPrintTable())
//
// View class instances's functions using instance.getclass() [returns table]
// In Squirrel 3, view extra function information like argument count; use function.getinfos() / nativefunction.getinfos() [returns table]
::DeepPrintTable <- function( debugTable, prefix = "" )
{
if (prefix == "")
{
printl(prefix + debugTable)
printl("{")
prefix = " "
}
foreach (idx, val in debugTable)
{
if (typeof(val) == "table")
{
printl( prefix + idx + " = \n" + prefix + "{")
DeepPrintTable( val, prefix + " " )
printl(prefix + "}")
}
else if (typeof(val) == "string")
printl(prefix + idx + "\t= \"" + val + "\"")
else
printl(prefix + idx + "\t= " + val)
}
if (prefix == " ")
printl("}")
}
// Usage:
// script DeepPrintTable( getroottable() )
Example Output: ] script DeepPrintTable( getroottable().Entities.getclass() ) (class : 0x08800E40) { FindByClassname = (native function : 0x0A759D40) FindByName = (native function : 0x0A759D88) FindByClassnameWithin = (native function : 0x0A75A010) FindByTarget = (native function : 0x0A759E18) FindByModel = (native function : 0x0A759E60) CreateByClassname = (native function : 0x0A759CF8) IsValid = (native function : 0x0A759C20) FindByClassnameNearest = (native function : 0x0A759EA8) FindByNameWithin = (native function : 0x0A759EF0) FindByNameNearest = (native function : 0x0A759F38) Next = (native function : 0x0A759CB0) FindInSphere = (native function : 0x0A759DD0) First = (native function : 0x0A759C68) } |
In and there is already the global VScript function __DumpScope(0, <scope/table>)
that does the job:
Script Handles
Interaction with in-game entities is done through script handles, which are custom objects that keep a reference specific entity, by using their entity index. These objects contain methods for getting / setting an entity's properties. Some entities belong to a certain "class", which comes with its own additional unique methods. All server-side entities currently in play can be fetched through methods available from theEntities
instance; inheriting the CEntities
object. Examples are found below.
See the API reference of the respective game for your project to know all available methods.
Entity Scripts
- This is only a quick introduction; See the main article for full info.
A common VScript feature is to augment the features of entities using Entity Scripts, which are either the specified scripts in an entity'svscripts
keyvalue, or ones that are appended into an entity by simply using VScripts in engine runtime. Entity Scripts also get access to various features, such as "Input Hooks" or "Think Functions".
In Hammer, the I/O system provides inputs to run code for these entity scripts (theRunScriptCode
input). For VScripts itself, CBaseEntity class's methods.GetScriptScope()
and.ValidateScriptScope()
respectively gets or creates an Entity Script at runtime.
I/O system interaction
Execute Code with Inputs
All entities provide three inputs for VScripts:RunScriptFile
, RunScriptCode
and CallScriptFunction
.
RunScriptFile
will automatically start inscripts/vscripts/
, then find the file with the given argument as a name / filepath.RunScriptCode
runs the argument as a line of VScript code in the entity's own script scope, which is also often used to run functions of an entity's script at any given moment.CallScriptFunction
looks up the function in the entity's own script scope with the name given by the argument, then calls it with no parameters. This was added to implementConnectOutput()
, but can be used manually as well. It is equivalent to usingRunScriptCode
to do the same, except that if the function is not present no error is produced.
RunScriptCode
input. In , backticks ( ` ) will be replaced by double-quotation marks at runtime, allowing strings to be passed through RunScriptCode. TeamSpen's Hammer Addons make this workaround available in other games.Firing Inputs
If available in the game API, scripts can use the EntFire()
and EntFireByHandle()
functions to fire outputs to map entities. Both take (target, input, param, delay, activator, caller)
parameters, but EntFireByHandle()
expects a handle to a specific entity to fire at while EntFire
takes a targetname/class, like in Hammer.
EntFire()
allows omitting the last four parameters if you want blank values, but EntFireByHandle
does not.
The parameters activator
and caller
take a script handle or null
, and specify the entities referred to by !activator
and !caller
. This is useful if you want to pass along another specific entity to the target.
Example Squirrel code:
// Sets the health of the entity owning the script scope to 500.
EntFireByHandle( self, "SetHealth", "500", 0, self, self )
// Parents all entities with names starting with "to_move_" to a specific car.
EntFire("to_remove_*", "SetParent", "!activator", 0, the_car, self)
Connecting Outputs
Using the CBaseEntity::ConnectOutput(string output, string function)
method, an entity Output can be connected to a function in the script scope of the entity. This is equivalent to manually adding an output which runs CallScriptFunction
on !self
, but you can add/remove it easily at runtime.
Example Squirrel code:
// Lights a prop on fire when it's used by the player.
function IgniteSelf()
{
DoEntFire( "!self", "Ignite", "", 0, self, self )
}
// Connects the OnPlayerUse output to the above function.
self.ConnectOutput( "OnPlayerUse", "IgniteSelf" )
Input Hooks
When an entity receives an input, the game code will attempt to call a script function of the format Input<Name of Input>()
in the receiving Entity Script. If the function returns false
, the input is prevented from firing. As with connected output functions, the variables activator
and caller
are set to the handles of the activating and calling entities.
Example Squirrel code:
// The script of a door or button. Intercepts the Unlock input,
// and doesn't allow the door/button to unlock until 5 inputs have been received.
UnlockCounter <- 5 // Counter local to the entity script scope.
// Called when the Unlock input is received.
function InputUnlock()
{
UnlockCounter--
if( UnlockCounter <= 0 )
{
return true // Allows the unlock
}
return false // Discards the input
}
Accessing !activator
and !caller
When the inputs RunScriptCode
, RunScriptFile
, CallScriptFunction
and InputXX
hooks are run, the variables activator
and caller
are set to the handles of the activating and calling entities before the function is run. After the function is finished, these are reset. This allows for example for an easy way to find which player triggered something.
Glossary
- Entity handle
- An opaque entity reference passing a C++ EHANDLE. Can only be compared with other handles or passed to API functions expecting them. Only used rarely.
- Script handle
- An entity instance with accessors and mutators to the C++ entity object. Represented as a HSCRIPT typedef in C++ code.
- Script scope
- Execution context of a script. A table where the variables, functions and classes of a VScript are placed.
API References
Squirrel
- List of L4D2 Script Functions
- List of Portal 2 Script Functions
- List of CS:GO Script Functions
- List of Team Fortress 2 Script Functions
- List of Reactive Drop Script Functions
- List of Nuclear Dawn Script Functions
- List of Contagion Script Functions
Lua
- GMOD Scripting API (Facepunch Wiki)
- Dota 2 Scripting API (ModDota Site)
- Half-Life: Alyx Scripting API