A to Z Scripting: Scriptobjects

From The DarkMod Wiki
Revision as of 14:53, 27 December 2020 by Dragofer (talk | contribs)
Jump to navigationJump to search

Scriptobjects

Scriptobjects are sets of scripts that are assigned to individual entities. They're used for making entities with scripted behaviours (i.e. a healing fountain) and for entities that carry out scripted effects (i.e. atdm:target_setteam, which changes the team of targeted AIs when triggered).

They allow many entities to carry the same, complex scripts without conflicting, since each entity has its own values for the variables. They are also well suited for taking spawnargs into account, allowing mappers to change how the scripts play out for each entity. An entity can only have one scriptobject.

A scriptobject typically has these 3 sections:

  • The object definition (mandatory), where all scripts must be defined and variables can be defined
  • The init() script, which is called when the entity spawns. Used for initial setup, such as determining what script to call when triggered
  • The scripts that perform the main function of the scriptobject.

Optional, but recommended, is to create an entity definition. This allows you and others to easily create entities in DR that have your scriptobject and all relevant spawnargs, including tooltips.

A further option is to create a RestoreScriptObject() script, which gets called whenever a savegame is loaded.

.script files with scriptobjects should be stored in the /script folder and #included in tdm_custom_scripts.script. This is because saving/loading will no longer work if the map .script contains scriptobjects.


Part 1: object definition

  • All scripts have to be defined in advance here. You may also create variables here if they should be accessible to all the scriptobject's scripts. Note that it's not possible to assign values to variables or run script events yet.

Example:

object healing_fountain			//scriptobject for a fountain that heals the player when frobbrd
{
	//define the scripts
	void init();				//for performing initial setup
	void heal_player();			//the script that does the healing

 	//define any variables here if they should be common to all the object's scripts, or if their value should persist
	//my preference is to also list spawnargs that get used in the scripts

	//SPAWNARGS
	//heal_amount				//the fountain will heal the player by this amount
	//cooldown				//fountain will need this amount of time to recharge
	//snd_heal				//healing sound; no need to define as a variable, because sound events are started directly from the spawnarg
	//snd_inactive				//sound played when player frobs the fountain during the cooldown period

	//NON-SPAWNARGS
	float active;				//whether the fountain will heal the player when frobbed
	float last_used;			//time on the game clock when the fountain was last used; for cooldown period
	float health_target;			//how much health the player will heal to
};


Part 2: init()

  • This script is called when the entity spawns and is used to perform initial setup. That includes retrieving values of spawnargs or setting up the entity to respond to trigger events.
  • Alternatively you can retrieve spawnarg values later in the actual scripts each time they run, if you'd like to allow mappers to change spawnargs after the map has started.
  • Since scriptobjects are entity-specific, all events are called on self by default, so you can just write i.e. getFloatKey("cooldown");
void healing_fountain::init()			//init() function of the healing_fountain scriptobject
{
	ResponseAdd(STIM_FROB);					//setup a response to frobbing that calls "heal_player" and enable it
	ResponseSetAction(STIM_FROB,"heal_player");
	ResponseEnable(STIM_FROB,1);

	active		= 1;					//assign a starting value to this variable
}


Part 3: the actual scripts

  • From now on this is almost like regular scripting and can be as simple or complex as you like. There is some added convenience:
    • all events are called on self by default, and you can use self as an input parameter.
    • Any script can call any other script, regardless of which one is written first. This is because you've already named them all in the object definition.
void healing_fountain::heal_player()
{
	if ($player1.getHealth == 100) return;		//do nothing if the player is at full health
	
	elseif (!active)				//if the fountain is inactive, check if enough time has passed to reactivate it...
	{
		if ( (last_used + getFloatKey("cooldown")) > sys.getTime() ) active = 1;
	}


	if (active)					//if the fountain is (now) ready, heal the player
	{
		health_target	= $player1.getHealth() + getFloatKey("heal_amount");	//calculate how much health the player will have
			if(health_target > 100) health_target = 100;		//limit the new health to max 100 (not strictly necessary for setHealth())

		$player1.setHealth(health_target);				//heal the player
		startSound("snd_heal", SND_CHANNEL_ANY, false);			//play a heal sound on the fountain, using the "snd_heal" spawnarg on the fountain

		active		= 0;						//fountain is now inactive until it has recharged
		last_used	= sys.getTime();				//keep track of when this fountain was last used
	}

	else 						//fountain is not ready; play inactive sound and abort
	{
		startSound("snd_inactive", SND_CHANNEL_ANY, false);	//play inactive sound
		sys.clearSignalThread(SIG_TRIGGER, $func_static_1);
		ResponseEnable(STIM_FROB,0);				//disable the frob response for 3s to prevent sound spamming
		sys.wait(3);
		ResponseEnable(STIM_FROB,1);
		return;							//abort
	}
}


Recommended: the entity definition

Scriptobjects are made to be used by entities, so it definitely makes sense to create a premade entity with all the correct spawnargs already set by making an entity definition.

The entity definition allows you to:

  • Define tooltips and types for any spawnarg. If you define a spawnarg as a "model" spawnarg, it'll have a "Choose model..." button.
  • Set default values for spawnargs, making them show up in "inherited properties".
  • Set values for spawnargs in the same way as a mapper would. This makes them show up even if "inherited properties" are hidden.


Calling individual scripts of a scriptobject

You can directly call a script that's part of a scriptobject in one of the following ways:

  • calling callFunction() on the entity carrying the scriptobject, i.e. $healing_fountain_1.callFunction("heal_player")
  • Atdm:target callobjectfunction entity, targeting the entity carrying the scriptobject and with a "call" spawnarg referencing the script, i.e. in the case of the healing fountain "call" "heal_player". The spawnargs "pass_self" and "pass_activator" allow passing entities.

You may even want to create special scripts within the scriptobject explicitly for being called externally in this way, for example if you want to force the healing fountain in the above example to become "active" immediately.


Accessing variables of a scriptobject in a global script

If you want to access the variables of this scriptobject from a global script, i.e. if you want to check whether a specific fountain is "active" or not, you will need to make the variables visible in some way:

  • The easiest way is to let the scriptobject set custom spawnargs on itself, i.e. setKey("active", "1"). Any other script can access that.
  • Alternatively, you can use the approach seen earlier for accessing "AI flags". You can create a variable where the the fountain is defined as a subcategory of entity named after the scriptobject. Example:
healing_fountain fountain = $fountain_1;		//define $fountain_1 as a variable of type "healing_fountain" (identical to the scriptobject's name

Then:

if ( fountain.active ) sys.println(fountain + " is ready to heal the player.");


Derived scriptobjects

Just like entities, scriptobjects can inherit from other scriptobjects. This is useful for building on an existing scriptobject without duplicating much of the code. An example is the "player" scriptobject inheriting from the "ai" scriptobject (which is the reason why AI_ flags can be used on the player - see A to Z Scripting: Scripting with..., "AI flags").

You have the option of defining a DerivedInit() script.


Example: atdm:target_unbind, for unbinding targeted entities when triggered

This will create a small box-like entity for use in DR. When it's triggered by something, it'll run a script on all its targets that unbinds them from whatever they're bound to. It's similar to i.e. atdm:target_setteam, which changes the team of all targeted AIs when it's triggered.


The script

As an example, this script could be stored in tdm_target_unbind.script in the script folder, and #included in tdm_custom_scripts.script.

object target_unbind				//create a new scriptobject
{
	//Name the scripts belonging to this scriptobject
	void init();				//mandatory; performs initial setup, mainly to make the entity respond to trigger signals by calling unbind_targets()
	void unbind_targets();			//performs the unbinding

	//Define all variables
	entity target;				//a target of this entity	
	float i;				//used for cycling through all targets of this entity
	float delay;				//wait this amount before performing the unbind; value comes from a spawnarg
};						//semicolon needed after the closing curly bracket of a scriptobject definition

void target_unbind::init()			//initial setup; gets called automatically when this entity spawns
{
	sys.onSignal(SIG_TRIGGER, self, "unbind_targets");	//when receiving a trigger signal on this entity, call the script unbind_targets() (contained in this scriptobject)
	delay	= getFloatKey("delay");				//retrieve a spawnarg value on self. "self" is implied, since it's a scriptobject
}

void target_unbind::unbind_targets()		//performs the unbinding
{
	sys.wait(delay);			//wait for "delay" seconds before running the script
	
	for (i = 0; i < numTargets() ; i++)	//repeat for every target of this entity
	{
		target = getTarget(i);		//get the next target of this entity
		target.unbind();		//run the unbind() script event on the target
	}
}

The entity definition

An entity definition allows you to get an entity carrying this scriptobject listed in DR's "Create entity" menu, allowing you and others to easily use it in your map. Create a .txt file in the def folder and change the extension to .def, the name doesn't matter.

entityDef atdm:target_unbind
{ 
	"inherit" 			"atdm:entity_base"		//inherit from a basic entity
	"spawnclass"			"idTarget"			//use a generic spawnclass without too much inbuilt coding
	"scriptobject"			"target_unbind"			//assign this scriptobject		
	"editor_displayFolder"		"Targets"			//entity will be listed in the Targets folder in DR

	"editor_color"			"0.3 0.1 0.6"			//borrow the appearance from the trigger_relay entity (purple box)
	"editor_mins"			"-8 -8 -8"			//dimensions (bottom left corner)
	"editor_maxs"			"8 8 8"				//dimensions (top right corner)
	"editor_material"		"textures/common/trigrelay"	

	"editor_usage"			"Unbinds all targeted entities when triggered." 	//description, shown in the Create entity menu
	"editor_usage1"			"Multi-line descriptions..."
	"editor_usage2"			"... are done like this."

	"editor_float delay"		"Optional. Delay action by this amount in seconds."	//tell DR that "delay" is a float and the tooltip
	"delay"				"0"			//delay will inherit a value of 0 (shown in inherited properties)
	//"editor_setKeyValue delay"	"0"			//alternatively, delay will automatically be assigned a value of 0 as if a mapper had done it
}


Your entity is now ready to be used in DR, performing unbind() on all its targets when triggered.


See also

Writing Script Objects

Next / previous article