Entity Scripts: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (corrected 'a event' to 'an event')
(Source 1 VScript section remake, pt1.5)
Line 2: Line 2:


== Script creation ==
== Script creation ==
=== Source ===
==== <u>Hammer</u> ====
[[File:EntityScripts-Hammer DefineScripts.PNG|thumb|right|text-top|300x400px|[[info_survivor_position]] entity from {{l4d2}}, getting 2 scripts defined as its '''Entity Scripts'''.
{{bug|The ''Open Source..'' button does not do anything, so you can ignore it.}}]]
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]], the<code>vscripts</code>key) [[Keyvalue|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.


Entity scripts are assigned by adding the script file name to the ''Entity Scripts'' (<code>vscripts</code>) attribute 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 the<code>RunScriptFile</code>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.
{{note|All defined scripts will load into the same script scope, thus overwriting any identical non-local variables and functions!}}


Additional scripts can be loaded by specifying multiple scripts in the ''Entity Scripts'' attribute, or using the <code>RunScriptFile</code> Input. All scripts that are run on the same entity will load into the same script scope, overwriting any identical variables and functions. Variables and functions can also be inserted into the script scope table directly with other scripts.
==== <u>Other VScripts</u> ====
{{todo| How are multiple scripts specified?}}
The option to use when you need to make ''Entity Scripts'' while a game is running; CBaseEntity provides the methods<code>.ValidateScriptScope()</code>and<code>.GetScriptScope()</code>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 [https://en.wikipedia.org/wiki/Associative_array associative arrays], or ''tables'', that are nested inside each other, its why using<code>.GetScriptScope()</code>returns a table.


== Source ==
Here is an example ''Entity Script'' being created, and put into players:
{{ExpandBox|
<source lang=js>
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)


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; <code>_<unique ID>_<entity name></code>. If no script has been run on the entity, a script scope can be manually created by using the <code>CBaseEntity::ValidateScriptScope()</code> method.
printl("I reached '"+destnum.tostring()+"' !")
}
else
printl("What! That number is lower than the destination number!")
}
}
EntFire("!self", "RunScriptCode", "DescendingCounting(10)", 0.1, player)
}
</source>}}
== 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;<code>_<uniqueID>_<entityname></code>. 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<code>.ValidateScriptScope()</code>method. The script always has the variable <code>self</code> defined, pointing to the script handle of the entity owning the script.


The script always has the variable <code>self</code> defined, pointing to the script handle of the entity owning the script.
An Entity Script has a <code>self</code> (Source 1) or <code>thisEntity</code> (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 ===
==== <u>Entity I/O</u> ====


''See [[Vscript_Fundamentals#I.2FO_system_interaction]]''
''See [[Vscript_Fundamentals#I.2FO_system_interaction]]''




=== Predefined Hooks ===
==== <u>Predefined Hooks</u> ====


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 <code>Precache()</code> 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.
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 <code>Precache()</code> 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 <code>vscripts</code> keyvalue, the <code>Precache()</code> and <code>OnPostSpawn()</code> will be chained so that eventual versions present in either or both scripts are called.{{confirm|Doesn't loading multiple entity scripts override script scopes?}}


=== Thinker Functions ===


Each entity supports a single thinker function, by default called every 0.1 seconds. The think function can be set either with the ''Think Function'' (<code>thinkfunction</code>) entity attribute, or with the <code>CBaseEntity::AddThinkToEnt()</code> method.
==== <u>Thinker Functions</u> ====


The think interval can be adjusted by returning a floating point value from the function. This value is give in seconds. It is limited by the server tick rate, so for example for a tick rate of 30, the lowest value possible is 0.333 seconds.
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 keyvlaue (without SmartEdit, the<code>thinkfunction</code>key), or with the method. In {{l4d2}} however, The<code>[[List_of_L4D2_Script_Functions#Entity_Manipulation|AddThinkToEnt()]]</code>function can be used to assign a function of its own ''Entity Script'' as a think function.


== Source 2 ==
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.
{{note|In {{l4d2}}, entities that inherit the ''CTerrorPlayer'' class are capped to 0.0666... seconds, half as slow of the game's default tickrate.}}
Example script of a think function:
{{ExpandBox|
<source lang=js>
// 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>}}
For {{l4d2}}; Example script of<code>AddThinkToEnt</code>usage:
{{ExpandBox|
<source lang=js>
// Slowly convert player's health into temporary health!
//// L4D2 ONLY
const HEALTH_DECAY_TIME = 2
const HEALTH_DECAY_BUFFERCOUNT = 3
local player = null
while( player = Entities.FindByClassname(player,"player") )
{
if( player.IsSurvivor() )
{
if( player.ValidateScriptScope() )
{
local player_entityscript = player.GetScriptScope()
player_entityscript["HealthDecayBufferCount"] <- HEALTH_DECAY_BUFFERCOUNT
player_entityscript["HealthDecay"] <- function()
{
if( player_entityscript["HealthDecayBufferCount"] < 0 )
{
if( player.GetHealth() > 1 )
{
player.SetHealth( player.GetHealth()-1 )
player.SetHealthBuffer( player.GetHealthBuffer()+1 )
}
}
return HEALTH_DECAY_TIME
}
}
}
AddThinkToEnt(player, "HealthDecay")
}
</source>
}}
{{style|border-bottom:1px solid #3A3937;display:block|}}
=== 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. Entity scripts get loaded into the private script scope.
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.
{{todo| How are public script scopes used?}}
{{todo| How are public script scopes used?}}
{{confirm|Does Source 2 implement script hooks?}}


The script always has the variable <code>thisEntity</code> defined, pointing to the script handle of the entity owning the script.
The script always has the variable <code>thisEntity</code> defined, pointing to the script handle of the entity owning the script.


 
==== <u>Entity I/O</u> ====
=== Entity I/O ===


The <code>CBaseEntity::ConnectOutput()</code> method seems to be non-functional in Source 2. To call functions on entity output, the output can instead be bound to fire the <code>CallScriptFunction</code> input on the same entity. The <code>CallScriptFunction</code> input passes on the activator and caller entities in a table to the first argument of the called function.
The <code>CBaseEntity::ConnectOutput()</code> method seems to be non-functional in Source 2. To call functions on entity output, the output can instead be bound to fire the <code>CallScriptFunction</code> input on the same entity. The <code>CallScriptFunction</code> 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.
Example Lua script referencing the passed handles.
{{ExpandBox|
<source lang="lua">
<source lang="lua">
-- 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.
-- 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.
Line 49: Line 131:
function SetActivatorColor(params)
function SetActivatorColor(params)


if params.activator
if params.activator then
then
-- Tints the activating entity red.
-- Tints the activating entity red.
params.activator:SetRenderColor(255, 0, 0)
params.activator:SetRenderColor(255, 0, 0)
 
print(thisEntity:GetDebugName() .. ": Activated by " .. params.activator:GetDebugName())
print(thisEntity:GetDebugName() .. ": Activated by " .. params.activator:GetDebugName())
end
end
Line 59: Line 140:
end
end
</source>
</source>
}}


 
==== <u>Thinker Functions</u> ====
=== Thinker Functions ===


''See [[Dota_2_Workshop_Tools/Scripting/ThinkerFunctions]]''
''See [[Dota_2_Workshop_Tools/Scripting/ThinkerFunctions]]''

Revision as of 03:45, 23 April 2021

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.

Script creation

Source

Hammer

info_survivor_position entity from Left 4 Dead 2, getting 2 scripts defined as its Entity Scripts.
Icon-Bug.pngBug:The Open Source.. button does not do anything, so you can ignore it.  [todo tested in ?]

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, thevscriptskey) 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 theRunScriptFileinput, 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.

Note.pngNote:All defined scripts will load into the same script scope, thus overwriting any identical non-local variables and functions!

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

See Vscript_Fundamentals#I.2FO_system_interaction


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.

Confirm:Doesn't loading multiple entity scripts override script scopes?


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 keyvlaue (without SmartEdit, thethinkfunctionkey), or with the method. In Left 4 Dead 2 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.

Note.pngNote:In Left 4 Dead 2, entities that inherit the CTerrorPlayer class are capped to 0.0666... seconds, half as slow of the game's default tickrate.

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
}

For Left 4 Dead 2; Example script ofAddThinkToEntusage:


// Slowly convert player's health into temporary health!
//// L4D2 ONLY
const HEALTH_DECAY_TIME = 2
const HEALTH_DECAY_BUFFERCOUNT = 3
local player = null
while( player = Entities.FindByClassname(player,"player") )
{
	if( player.IsSurvivor() )
	{
		if( player.ValidateScriptScope() )
		{
			local player_entityscript = player.GetScriptScope()
			player_entityscript["HealthDecayBufferCount"] <- HEALTH_DECAY_BUFFERCOUNT
			player_entityscript["HealthDecay"] <- function()
			{
				if( player_entityscript["HealthDecayBufferCount"] < 0 )
				{
					if( player.GetHealth() > 1 )
					{
						player.SetHealth( player.GetHealth()-1 )
						player.SetHealthBuffer( player.GetHealthBuffer()+1 )
					}
				}
				return HEALTH_DECAY_TIME
			}
		}
	}
	AddThinkToEnt(player, "HealthDecay")
}

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.

Todo:  How are public script scopes used?
Confirm:Does Source 2 implement script hooks?

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

Thinker Functions

See Dota_2_Workshop_Tools/Scripting/ThinkerFunctions