A to Z Scripting: Ways of calling a script

From The DarkMod Wiki
Jump to navigationJump to search

TDM has many methods for calling scripts, some overlap with others but there are always subtle differences. Often you can combine systems such as the objective system with scripting in order to get results that wouldn't easily be possible with either system alone.

Some of the methods allow you to pass on to the script as input variables which entities were involved in calling the script. This allows you to write a single script which chooses to do different things depending on what activated it, i.e. you could have a portal chamber where each portal teleports the player to a different destination even though they all call the same script. You could set custom spawnargs on the entities and use them in the scripts.


From other scripts

To call a script from another script, simply write the name of the script followed by input brackets. The script will wait for the called script to finish.

void script2()
{
	script1();		//call script1, wait for script1 to finish
}

Alternatively, you can write "thread" in front. Both scripts will run simultaneously, because each one runs in its own thread.

void script2()
{
	thread script1();	//call script1, don't wait
}


You can only call scripts that the engine already knows about, so the called script (script1) needs to be higher up than the calling script (script2). If you're using general .script files (A to Z Scripting: Setting up the .script files), the same applies to the order in which you #include your .script files.

The main advantage of calling one script from another is that you can give as much input as you like - if the called script is designed to use input (see A to Z Scripting: Utility scripts for more). Other methods can usually only provide 1 or 2 pieces of input: the entity that called the script (i.e. a trigger brush) and the entity that triggered the calling entity (i.e. a player or AI stepping into the trigger brush).

target_callscriptfunction

You can create an entity in DR called target_callscriptfunction and give it the spawnarg "call" with the name of the script without brackets, i.e. "call" "script1". Whenever this entity is triggered by something, i.e. a button, the script will be called.

These entities are among the most versatile ways to call a script because, with the spawnarg "foreach" "1" and at least one target, they can pass on 3 entities to the script:

  • one target of the callscriptfunction entity. The script will get repeated on each target, one by one (that's where "foreach" comes from)
  • the entity that triggered the callscriptfunction entity (aka the activator)(i.e. a button)
  • the name of the callscriptfunction entity itself


Basic example: simply calling a script with no input

void script1()
{
 	sys.println("script1 has been called.");
}
//spawnarg on the callscriptfunction entity:
"call" "script1"


Advanced example: teleportation script to teleport targeted entities in 2 directions

Here's a more complex example of a teleportation script that uses all 3 entities that a callscriptfunction can pass to a script:

void teleport_targets_away(entity ent_target, entity ent_button, entity ent_callscriptfunction)
{
	sys.println("the first entity (the target) is " + ent_target.getName());
	sys.println("the second entity (the button) is " + ent_button.getName());
	sys.println("the third entity (the callscriptfunction) is " + ent_callscriptfunction.getName());
 
	float teleportation_direction 	= ent_button.getFloatKey("direction");			//find the teleportation direction, stored on the button
	vector teleportation_vector	= ent_callscriptfunction.getVectorKey("vector");	//find the teleportation vector, stored on the callscriptfunction entity
	if (teleportation_direction == -1) teleportation_vector = -teleportation_vector;	//invert the teleportation vector if the direction is backwards

	ent_target.setOrigin(ent_target.getOrigin() + teleportation_vector);			//teleport the target
}

The setup in the map would be 2 buttons (1 per direction) targeting a callscriptfunction entity which targets the entities you want to get teleported. The spawnargs are as follows:

  • callscriptfunction entity
    • spawnarg: "foreach" "1" (so it repeats for every target and passes on its name and the button that triggered it)
    • spawnarg: "call" "teleport_targets_away"
    • custom spawnarg: "vector", with whatever teleportation vector you want i.e. "450 0 0"
  • 2 buttons targeting the callscriptfunction entity
    • one button with custom spawnarg: "direction" "1"
    • the other with custom spawnarg: "direction" "-1"

The setup can be cloned as often as you want, just change the "vector" spawnarg and optionally the targets of the callscriptfunction entity each time.

Trigger brushes

Trigger brushes are a simple way to call scripts, activated when the player or an AI enters the volume of the brush. For AIs to work you need to set "anyTouch" "1" on the brush. The brush needs a spawnarg "call" with the name of the script without brackets, i.e. "call" "script1". They also trigger all their targets. See Triggers for details on the creation of trigger brushes and the various types.

Most trigger brushes are quite basic, so they can't pass the name of the entity that stepped into them or their own name to the script. The best you can do is target a callscriptfunction entity (see above) instead of calling the script directly on the brush: this way, the callscriptfunction will tell the script which brush was activated.

A more advanced type of trigger brush is trigger_touch, which detects all AIs/players/moveables in its volume and calls its script on each of them. More on their use in A to Z Scripting: Special methods.


Potential pitfalls with trigger brushes

  • AIs will stop activating trigger_multiple brushes if they become stationary.


Path nodes

Many Path Nodes trigger all of their targets whenever an AI reaches them. They can target a target_callscriptfunction entity in order to call a script.


Stim/Response

The Stim/Response system is very versatile, in particular when augmented with scripting.

Responses, i.e. to frobbing

In most cases, you'll likely be interested in only the "Response" tab of the S/R editor interface. This allows you to make an entity respond to certain stimuli, such as a frob, a trigger or water by executing various effects, such as running a script (no input possible). Many other effects are available from the dropdown list.

One common use is to make an entity frobable and give it a "Response" to frobbing with an effect to run a script. To do so, set the spawnarg "frobable" "1" on the entity to make it frobable. Then open the S/R editor, go to the Response tab, in the left half of the window add a "Frob" entry, in the right half of the window right-click and add an effect to "Run script", naming the script you want to run.

To make this single-use you could add another effect to either:

  • disable frobability of this entity, specifying _SELF as the entity
  • or deactivate the response to frobbing on _SELF. The ID of the frob stim is 0.


Stim and Response

Working with both the "stim" and "response" tabs of the editor lets you make scripts and other effects happen when two entities come near each other. A common example is a water arrow carrying a water stim being shot at a flame carrying a response to water ("extinguish").

Enabling a "stim" on an entity causes that entity to emit the stim to its surroundings. The settings allow you to finetune this, i.e. how often the stim gets emitted, the radius etc.

Meanwhile, using the "response" tab on another entity allows you to define what effects happen when this entity is reached by a stim of that type.

Creating a custom stim type is recommended, unless you explicitly want to do something with an existing stim like water or fire.


Setting up Stim/Response via script

Stim/Response can be setup either via DarkRadiant or via script. The latter has the advantage that you can setup S/R on the fly after the map has started, which could for example be useful if you're making a custom entity and don't want to clutter its spawnarg list. Its disadvantage is that it offers less flexibility than DarkRadiant's editor and some script functions may not work properly such as sys.wait.

See below for an example of adding a trigger response to an entity via script:

ResponseAdd(STIM_TRIGGER);
ResponseSetAction(STIM_TRIGGER,"responseTrigger");  //when this entity is triggered, call the script "responseTrigger".
ResponseEnable(STIM_TRIGGER,1); 

In this case, the script to be called will need the following input variables: entity me, float threadnum

void responseTrigger( entity me, float threadnum )
{
...
}


Action scripts

Entities can be given spawnargs that make them run scripts when certain actions are performed on them, such as frobbing or using. The value of the spawnarg is the name of the script. Example: "frob_action_script" "script1".

Like with trigger brushes (see earlier), these entities pass their own name to the script function. This allows you to reuse the same spawnarg & script on many entities.

frob_action_script Calls the specified script when the entity is frobbed by the player.
use_action_script Calls the specified script when the player uses an inventory item on the entity (i.e. uses a key on a door). The inventory item must be named in a "used_by" spawnarg on the entity. More info here: Tool, Key, custom used by inventory actions
equip_action_script Calls the specified script when the player is holding the entity and uses it (i.e. eating a held apple by pressing enter).


Action scripts are able to tell which entity was frobbed/used/equipped.

Example: remove the entity when the player frobs it

void destroy_frobbed(entity ent_frobbed)
{
	ent_frobbed.remove();
}
Spawnarg on each entity that should be destroyable:
"frob_action_script" "destroy_frobbed"


Signals

Signals are events which can be setup to call a script. Examples of events are the entity being triggered, touched, removed or damaged (but not: frobbing). Some signals only work for movers i.e. when a mover is blocked or a door is closed/opened. See the Signals wiki article for a full list of available signals.

Entities using the signal system can pass on their own name to the script.

To use the signal system, you need to use a script event to instruct the entity to respond to a certain signal by running a certain script. Example:

sys.onSignal(SIG_TRIGGER, $func_static_1, "script1");

You may later want to disable this again. In that case:

sys.clearSignalThread(SIG_TRIGGER, $func_static_1);


Note that the signal system is old, so it doesn't support "frobbing", which is a TDM invention. Its SIG_USE doesn't appear to be useable in TDM either. Furthermore, it doesn't seem to pass which entity

SIG_TOUCH call its script whenever the player (or something else?) stands very close to the entity, and SIG_BLOCKED for as long as a func_mover is blocked. Note that the scripts get called several times per second, so you might want to clear the signal the first time the script runs and maybe reapply the signal after a certain delay.


Objective system

The Objectives Editor can be seen as a visual scripting editor. Not unsurprisingly it synergises well with scripting.

The main way to use the objective system for scripting is by specifying completion scripts and failure scripts for when an objective is completed or failed. Note that if the objective is reversible, it might call the scripts multiple times.

The objectives system allows you to do some things much more easily than with regular scripting, such as calling a script when reaching a certain page in a book (i.e. playing an ominous sound). Many maps use a mix of hidden objectives and scripts in order to achieve interesting scripted effects.


Location system

The location system (wiki page: Location Settings allows you to call scripts whenever the player enters or leaves specific locations.

It works by setting spawnargs on the info_location entity, with the value being the script's name:

"call_on_entry"
"call_on_exit"
"call_once_on_entry"
"call_once_on_exit"

On the scripting side of things, your script must always contain one entity variable in the input brackets, which will receive the name of the info_location entity involved.

void enter_location_streets(entity ent_location)


Basic example: start a conversation when the player enters a specific location

void start_conversation(entity ent_location)			//ent_location must be defined even if it's not used in the script
{
	sys.trigger($start_conversation_1);			//trigger an entity that starts the conversation
}
//conversation on the info_location entity
"call_once_on_entry" "start_conversation"


Intermediate example: play a sound once when the player finds one of several ways into the mansion

Say you wanted a special sound to play when the player finds his way into the mansion. You'd probably have several different entrances, each with their own location. You'd use "call_once_on_entry" on each location to call a script triggering the sound.

Since there are multiple locations and you only want the sound to play once, you also need to create a variable to store whether the sound has already played to stop further triggers of the sound.


boolean sound_has_played = false;			//stores whether the sound has played; create here as a global variable so it doesn't get recreated every time the script runs

void found_way_in(entity ent_location)
{
	if(!sound_has_played)
	{
		sys.trigger($speaker_found_way_in);		//trigger a speaker carrying the sound and "s_waitfortrigger" "1"
		sound_has_played = true;			//if other locations call this script, tell them the sound has already played
	}
}
//Spawnarg on all info_location entities that cover the entryways to the mansion
"call_once_on_entry" "found_way_in"


Advanced example: toggling fog particles depending on whether the player is inside or outside

Particles have a tendency of leaking through walls. If you have a map with lots of particle fog, you might want to manually switch them off whenever the player is inside. Preferrably the switch should happen somewhere where the player can't see the fog being toggled off.

Unlike lights with On() and Off(), there's no script event that specifically switches the func_emitters on or off (to my knowledge), you can only toggle them with triggering. So the script should create a new variable to store what state the emitters are in.

This script won't trigger all func_emitters individually, but rather a trigger_relay entity that in turn targets all the func_emitters. This saves a lot of lines.


This example relies heavily on conditionals to check which location the player is in and whether the fog needs to be toggled, see A to Z Scripting: Conditionals for more information.

boolean fog_is_on = true;		//fog starts on; create here as a global variable so it doesn't get recreated every time toggle_fog() is called

void toggle_fog(entity ent_location)
{
	//if fog is on and player goes inside, toggle off the fog and update fog_is_on to false
	if ( (fog_is_on) && (ent_location == ( $location_manor_entry1 || $location_manor_entry2 || $location_manor_entry3 ) ) )
	{
		sys.trigger($relay_fogs);	//trigger_relay targeting all fog func_emitters
		fog_is_on = false;
	}

	//if fog is off and player goes outside, toggle on the fog and update fog_is_on to true
	else if ( (!fog_is_on) && (ent_location == ( $location_manor_garden1 || $location_manor_garden2 || $location_manor_garden3 ) ) )
	{
		sys.trigger($relay_fogs);
		fog_is_on = true;
	}
}
//Spawnargs on the info_location entities (i.e. location_manor_entry1, location_manor_garden2)
"call_on_entry" "toggle_fog"

I've chosen not to use call_on_exit because that would call the script also if the player leaves the entrance locations to go deeper into the mansion. There's no need for that.

Conversation system

There's no direct way to call a script from a conversation, but you can tell one of the actors to activate a callscriptfunction entity.


Killing or KOing an AI

You can add a spawnarg to AIs to call a script when they're killed or knocked out:

"death_script"
"ko_script"

You may pass one entity to the script, the victim, allowing you for example to play a flames effect on the body or remove it after some time:

void auto_remove(entity ent_self)
{
	sys.println(ent1_self.getName() + " has been killed.");
	sys.wait(30);
	ent_self.remove();			//remove the body after 30s
}
//spawnarg on the AI:
"death_script" "auto_remove"

Note that you can't tell the script who killed or knocked out the AI (i.e. whether it was the player's fault). If you need this info, you may use the objectives system and use the "Player responsible" checkbox.

From an animation

You can call a script or a script from the entity's scriptobject by using one of the following, respectively, as a frame keyword with the name of the script. I believe the entity would pass its name to the script.

call
object_call

From the console

This is only for testing purposes because you will no longer be able to save the game after calling a script from the console. The console also lets you try to call script events, but this seems to have no effect ingame.

script name_of_script()

The trailing "()" is required.

Next / previous article