Entity Scripts

From Valve Developer Community
Jump to navigation Jump to search
English (en)中文 (zh)Translate (Translate)

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

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 keyvalue (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
}

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