A to Z Scripting: Special methods

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

This section is devoted to useful applications of various scripting principles and to special cases.

Going through...

See below for methods to find all entities that meet various criteria and run script events on them.

...all entities of a certain type

The getNextEntity() function allows you to find every entity in the map that has a certain spawnarg. It finds one entity at a time, so you'll need to run it many times. Each time it runs you'll have an opportunity to run script events on the entity that was found.

As shown in "Looping scripts", the combination of do + while() is well suited for this application:

//Example script by Obsttorte
void lamps_toggle()				//finds and triggers all electric lamps in the map. They're identified based on the value of the "lightType" spawnarg
{
	light lamp;
	do					//do repeat this...
	{
		lamp = sys.getNextEntity("lightType","AIUSE_LIGHTTYPE_ELECTRIC",lamp);	//find the next entity with this spawnarg; continue the search from the previously discovered lamp entity
		if (lamp) sys.trigger(lamp);
	}	while (lamp);			//repeat for as long as sys.getNextEntity finds electric lights
}


...all targets of an entity

numTargets() returns the number of targets that an entity has. This can be combined with "for" to run script events once on every target:

void hide_targets()						//finds all targets of an entity and runs a script event on them (in this case: hide)
{
	float i;						//define a float (i for integer) to keep track of how many targets have been identified

	for(i = 0; i < $func_static_1.numTargets(); i++)	//for every target of func_static_1...
	{
		entity m_target = $func_static_1.getTarget(i);	//store target #i as a variable so we can call an event on it. Will get overwritten by the next target
		m_target.hide();				//call hide() on this target
	}
}


...all entities bound to an entity

This is almost identical to going through all targets of an entity. Important is the distinction between bindChildren and bindMasters: bindChildren are lower in the bind hierarchy, bindMasters are higher in the bind hierarchy relative to the entity in question.

void identify_handle()					//goes through all entities bound to a door in order to identify a handle
{
	float i;

	for(i = 0; i < $mover_door_1.numBindChildren(); i++)			
	{
		entity m_bind_child = $mover_door_1.getBindChild(i);		//find the nth bound entity
		if(m_bind_child.getKey("spawnclass") == "CFrobDoorHandle")	//check if this bound entity is a handle. If yes...
		{
			entity m_handle = m_bind_child;				//...store it for later
		}
	}
}


...all spawnargs with the same prefix

This works quite similar to going through all entities of a certain type. The difference is that there's a 2nd step: first find the name of the spawnarg, then find the value of that spawnarg.

Example: finding all frob_peers of a door and making sure they're all frobable

void find_peers()
{
	string key;			//name of the spawnarg
	entity frob_peer;		//value of the spawnarg; in this case, it'll be a frob_peer entity

	do
	{
		key 		= $mover_door_1.getNextKey("frob_peer", key);	//find the exact name of the next spawnarg that begins with "frob_peer", starting from the previous result
		frob_peer	= $mover_door_1.getEntityKey(key);		//find the value of this spawnarg, which should be an entity

		if(frob_peer) frob_peer.setFrobable(1);				//if a valid entity has been found, set it to frobable 
	}	while(key);							//keep repeating until this script stops finding valid spawnarg names
}

Note:

  • Alternatively, you could pass getNextKey() an empty string, "". That should make the script find all spawnargs of this entity.


...all entities in a volume

The easiest method is to combine a regular trigger brush with a trigger_touch brush, but this is limited only to the player, AIs and moveables and requires premade brushes. An alternative method is to go through all entities in the map by passing getNextEntity() 2 empty strings. This can find all types of entities in any volume, but is more work to script, in particular if it might exceed the limit of 10,000 events per frame if used for large volumes with many entities.


With trigger_touch

A trigger_touch brush can detect the player, any AIs and any moveable entities within its volume, running a script on each of them. Unlike other trigger brushes, it can pass self and its activator, if the corresponding spawnargs are set, in addition to the detected entity.

To save performance, the trigger_touch should not be on permanently ("start_on" set to "0"). Instead, a regular trigger brush of the same size should call a script that activates the trigger_touch, waits one frame, then deactivates the trigger_touch.

void activate_trigger_touch()
{
	//called by a regular trigger brush; activates the trigger_touch for a single frame only
	sys.trigger($trigger_touch_1);
	sys.waitFrame();
	sys.trigger($trigger_touch_1);
}
void detect_entities(entity touching_ent)
{
	//called by the trigger_touch brush on every AI, moveable or player it finds within its volume.
	sys.println("Found entity: " + touching_ent.getName());
}


Without trigger_touch

This method is more work-intensive than the trigger_touch method, but allows to find entities of all types in any volume. Note that it'll only look at the origins of the entities, not at their bounding boxes.

You will first need to define the volume to search by specifying 2 corners lying opposite of each other. An easy method is to use getMins() and getMaxs() on an entity, such as a nodraw brush. The script would start like this:

void find_entities_in_volume(entity volume_entity)
{
    vector corner1 = volume_entity.getMins();    //use an entity, such as a big clip brush, to define the volume
    vector corner2 = volume_entity.getMaxs();


Alternatively, you can input any 2 vectors. This is more flexible, but your script will need to make sure that you have a minimal and a maximal corner. This means the lower values on each axis are in the minimal corner & vice versa. This is necessary for an if( corner1 > origin > corner2 ) conditional to work.

void find_entities_in_volume(vector corner1, vector corner2)
{
//In this method, you will need to ensure that all components on corner1 are smaller than corner2, if necessary swapping them around
//This step is not necessary if your vectors came from getMins() and getMaxs()

float temp;        //used to temporarily store the value of a vector component if swapping components around

if(corner1_x > corner2_x){temp = corner1_x; corner1_x = corner2_x; corner2_x = temp;}
if(corner1_y > corner2_y){temp = corner1_y; corner1_y = corner2_y; corner2_y = temp;}
if(corner1_z > corner2_z){temp = corner1_z; corner1_z = corner2_z; corner2_z = temp;}


Now that you have the minimal and maximal corners of the volume, you can cycle through all entities in the map and check whether their origins lie within. Note that maps can have many thousands of entities, so you'll probably want to limit the number of entities that can be looked at per frame. Even at 1000 entities per frame, it'll be done in a fraction of a second.

entity ent;
vector ent_origin;
float i;
float max_entities_per_frame = 1000;

    do
    {
        ent = getNextEntity(ent);        //go to the next entity in the map
        ent_origin = ent.getOrigin();

        if( (corner1_x <= ent_origin_x <= corner2_x ) && (corner1_y <= ent_origin_y <= corner2_y ) && (corner1_z <= ent_origin_z <= corner2_z ) )
        {
            sys.println(ent.getName() + " is within this volume.")        //an entity has been found within this volume, run any events here
        }

	i++;					//add 1 to the number of entities checked in this frame
	if ( i == max_entities_per_frame )	//if the limit for this frame has been hit...
	{
		sys.waitFrame();		//wait a frame before continuing
		i = 0;				//reset entity counter to 0
	}

    } while(ent);                                    //repeat for as long as valid entities are found
}


Spawning entities

You can spawn an entity of a specified classname while the map is running. In the same frame, you should also set any custom values that you need for spawnargs. Example:

void spawn_stagecoach()
{
	entity spawned_entity 	= sys.spawn("func_static");		//spawn a "func_static" entity
	spawned_entity.setName("stagecoach");
	spawned_entity.setModel("models/darkmod/misc/carriages/stagecoach.lwo");
	spawned_entity.setOrigin('483 723 41');
}

If you plan to use this event often you may want to create a new entity def that contains most of the desired spawnargs so you no longer need to set them in the script.


Doing more with vector variables

Accessing individual components as floats

It's possible to access individual components of a vector by appending _x, _z or _y to the end of the vector variable's name. These will get treated as floats.

Example: teleporting the player up by 16 units from wherever he is now

void teleport_player_up()
{
	vector player_position	= $player1.getOrigin();		//where is the player now? Store as a variable named "player_position"
	player_position_z	= player_position_z + 16;	//increase the z-component (height) of "player_position" by 16

	$player1.setOrigin(player_position);			//set the new origin of the player to the modified "player_position"
}


Getting the magnitude of a vector

Another option is to get the magnitude, or length, of a vector with sys.vecLength(), which turns the vector into a float. This is useful for example in calculating distances or expressing the progress of a mover as a percentage.

Example: checking the progress of a func_mover

void check_progress()
{
	vector start_position 	= '0 0 0';
	vector target_position	= '200 0 0';
	vector current_position	= $func_mover_1.getOrigin();

	float distance_moved 	= sys.vecLength(current_position - start_position);			//how far the mover has moved so far in units
	float percent_progress	= distance_moved / sys.vecLength(target_position - start_position);	//how far the mover has moved so far in percent 
}


Spline movers

A spline is a curved line which is stored on a spline mover entity (misleading name). A regular mover (func_mover) can be scripted to move along this spline. Usually the spline mover is invisible, while the func_mover is visible or has visible entities bound to it.

To create a spline in DarkRadiant, click on the button "Create a NURBS curve" along the left edge of the window. Next to this button there are several other buttons which allow you to append, insert or remove control points from this curve, or convert it into a curve which uses the CatmullRom algorithm instead of NURBS. The control points can be moved around to change the shape of the curve, quite similar to how the control vertices of patches work.

When your spline is finished, create a small ca. 8x8x8 brush textured with textures/common/nodraw around the origin of the curve (make sure you look at it in all 3 axis). Select the brush and right-click > create entity > func_splinemover.

Copy the "curve_Nurbs" or "curve_CatmullRomSpline" spawnarg from the curve, paste it onto the nodraw brush and delete the original curve. You now have a spline. Next you need to create a func_mover with the same origin as the spline. This func_mover can be a visible model, or it can be another nodraw brush to which you can bind a func_smoke particle, which would leave a trail like the wisps in Thief.


You'll now need a script to make the func_mover move along the spline:

void start_spline()
{
	entity spline		= $func_splinemover_2;		//the name of the nodraw splinemover brush carrying the curve data in your map
	entity func_mover	= $func_mover_1;		//the name of the func_mover in your map

	func_mover.time(10);					//let the mover take 10s per lap. Alternatively set speed(), in units per second
	func_mover.disableSplineAngles();			//optional: use this to stop the mover from rotating wildly depending on how the curve is angled
 	sys.threadname("spline_1");				//optional: give this thread a name so the spline mover can be stopped by another script with sys.killthread("spline_1");

	while(1)						//loop the following indefinitely
	{
		func_mover.startSpline(spline);			//start the mover along the spline
	 	sys.waitFor(func_mover);			//wait for the mover to finish its movement
	}
}


Setting up Stim/Response via script

It's possible to apply Stim/Response settings to an entity at run time, rather than doing so manually in DR. This is especially useful for setting up a spawned entity to respond to triggers or frobs. The below example is for setting up an entity to respond to triggering by calling script1:

$func_static_1.ResponseAdd(STIM_TRIGGER);
$func_static_1.ResponseSetAction(STIM_TRIGGER,"script1");
$func_static_1.ResponseEnable(STIM_TRIGGER,1);


Next / previous article