Entity Scripts
Every game with VScript capability supports attaching scripts to server-side entities. These Entity scripts can be used for adding new functionality or logic to entities, and are a convenient way of scripting in an event driven fashion.
Contents
Script creation
Source
Hammer
In Hammer, Entity scripts are assigned by adding the script file name, or file path when its in another folder within the scripts/vscripts folder, to the Entity Scripts (without SmartEdit, thevscripts
key) KeyValue of an entity. The script is executed when the entity spawns, and loads into a script scope specific to each entity instance. The script and all the variables and functions can be accessed through the entity, and remain available for the duration of the entity's lifetime.
Additional scripts can be loaded into the entity by specifying multiple scripts in the Entity Scripts keyvalue, which differs from theRunScriptFile
input, since it just loads into the root script scope[confirm]. When defining additional scripts, make sure all of the script names are separated by a blank space.
Other VScripts
The option to use when you need to make Entity Scripts while a game is running; CBaseEntity provides the methods.ValidateScriptScope()
and.GetScriptScope()
for editing Entity Scripts on the spot. The former creates a script scope for an entity if it doesn't exist, and returns true if it either one exists or one is created sucessfully; The latter returns a table containing everything in the script scope, if the script scope exists, or it returns nothing.
Since the scripting environment consists of associative arrays, or tables, that are nested inside each other, its why using.GetScriptScope()
returns a table.
Here is an example Entity Script being created, and put into players:
local player = null
while( player = Entities.FindByClassname(player,"player") )
{
if( player.ValidateScriptScope() )
{
player.GetScriptScope()["DescendingCounting"] <- function(num, destnum = 0)
{
if( num > destnum )
{
printl("I'm counting from '"+num.tostring()+"'to '"+destnum+"' !")
for (local i = num; num > 0; i--)
printl(num)
printl("I reached '"+destnum.tostring()+"' !")
}
else
printl("What! That number is lower than the destination number!")
}
}
EntFire("!self", "RunScriptCode", "DescendingCounting(10)", 0.1, player)
}
Scripting Environments
Source
The script scope is placed directly in the root table, using a key made up of an unique identifier followed by the entity name or class name;_<uniqueID>_<entityname>
. All entities only start with a script scope when they have a script attached to it, so during game runtime, always ensure the script scope is created with the CBaseEntity.ValidateScriptScope()
method. The script always has the variable self
defined, pointing to the script handle of the entity owning the script.
An Entity Script has a self
(Source 1) or thisEntity
(Source 2) reference to the script handle of the entity owning it, allowing the script easy access to control the entity through its class methods.
Entity I/O
Predefined Hooks
Entities have the ability to call functions in their script scope from the C++ side. Common entity classes have predefined function calls programmed into them to occur at certain events, allowing scripts to execute code. For example, creating a function called Precache()
in an entity script will call that function right after the entity spawns, allowing the script to precache any custom assets. These functions do not need to be registered, and are always called if if one with the right name exist. Please see the API documentation for your game to find out what hook functions are available for each class.
When multiple scripts are loaded from the vscripts
keyvalue, the Precache()
and OnPostSpawn()
will be chained so that eventual versions present in either or both scripts are called.
Thinker Functions
Each entity supports a single thinker function, by default called every 0.1 seconds. The think function can only be set by the Think Function entity keyvalue (without SmartEdit, thethinkfunction
key), or with the method. In however, TheAddThinkToEnt()
function can be used to assign a function of its own Entity Script as a think function.
The think interval can be adjusted by returning a floating point value from the function, which is intepreted as seconds. You can set any value, but it will be limited by the server tick rate; A tick rate of 30 makes it that the true lowest value possible is 0.0333... seconds.
Example script of a think function:
// This should be an 'Entity Script'; Put it to an entity's 'thinkfunction' keyvalue using Hammer.
// Syntax example: "'thinkfunction' MyThinkFunction"
const RETHINK_TIME = 0.33
function MyThinkFunction()
{
printl("Current Time: "+Time().tostring())
return RETHINK_TIME
}
Source 2
In contrast to Source, the script scopes of entity scripts in Source 2 are not accessible from the root table. Entities in Source 2 has both public and private script scopes, and pre-defined entity scripts get loaded into the private script scope.
The script always has the variable thisEntity
defined, pointing to the script handle of the entity owning the script.
Entity I/O
The CBaseEntity::ConnectOutput()
method seems to be non-functional in Source 2. To call functions on entity output, the output can instead be bound to fire the CallScriptFunction
input on the same entity. The CallScriptFunction
input passes on the activator and caller entities in a table to the first argument of the called function.
Example Lua script referencing the passed handles.
-- To use this, add a output from an entity (like a trigger) to the CallScriptFunction input of the entity with the script, setting the value to SetActivatorColor.
function SetActivatorColor(params)
if params.activator then
-- Tints the activating entity red.
params.activator:SetRenderColor(255, 0, 0)
print(thisEntity:GetDebugName() .. ": Activated by " .. params.activator:GetDebugName())
end
print(thisEntity:GetDebugName() .. ": Called by " .. params.caller:GetDebugName())
end