A to Z Scripting: Special methods: Difference between revisions
mNo edit summary |
|||
(4 intermediate revisions by the same user not shown) | |||
Line 10: | Line 10: | ||
As shown in "Looping scripts", the combination of do + while() is well suited for this application: | As shown in "Looping scripts", the combination of do + while() is well suited for this application: | ||
//finds and triggers all electric lamps in the map. They're identified based on the value of the "lightType" spawnarg | |||
void lamps_toggle() | |||
{ | { | ||
entity lamp; | |||
do | |||
do | |||
{ | { | ||
//find the next entity with this spawnarg; continue the search from the previously discovered lamp entity | |||
if (lamp) | lamp = sys.getNextEntity("lightType","AIUSE_LIGHTTYPE_ELECTRIC",lamp); | ||
} while (lamp); //repeat for as long as sys.getNextEntity finds electric lights | |||
if ( lamp != $null_entity ) | |||
{ | |||
lamp.On(); | |||
} | |||
} while (lamp); //repeat for as long as sys.getNextEntity finds electric lights | |||
} | } | ||
=== ...all targets of an entity === | === ...all targets of an entity === | ||
Line 76: | Line 82: | ||
* Alternatively, you could pass getNextKey() an empty string, "". That should make the script find all spawnargs of this entity. | * 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 == | == Spawning entities == | ||
You can spawn an entity of a specified classname while the map is running. | You can spawn an entity of a specified classname while the map is running. Optionally, you can set values for spawnargs in advance of the spawning. | ||
void spawn_stagecoach() | void spawn_stagecoach() | ||
{ | { | ||
sys.setSpawnArg("name", "stagecoach"); | |||
sys.setSpawnArg("model", "models/darkmod/misc/carriages/stagecoach.lwo") | |||
sys.setSpawnArg("origin", "483 723 41"); | |||
spawned_entity. | entity spawned_entity = sys.spawn("func_static"); //spawn a "func_static" entity with the spawnargs above | ||
} | } | ||
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. | 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. | ||
Line 163: | Line 242: | ||
== Next / previous article == | == Next / previous article == | ||
*Next article: [[A to Z Scripting: | *Next article: [[A to Z Scripting: Utility scripts]] | ||
*Previous article: [[A to Z Scripting: Scripting with...]] | *Previous article: [[A to Z Scripting: Scripting with...]] | ||
*Table of contents: [[A to Z Scripting]] | *Table of contents: [[A to Z Scripting]] | ||
[[Category:Scripting]] | [[Category:Scripting]] |
Latest revision as of 14:29, 27 January 2024
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:
//finds and triggers all electric lamps in the map. They're identified based on the value of the "lightType" spawnarg void lamps_toggle() { entity lamp; do { //find the next entity with this spawnarg; continue the search from the previously discovered lamp entity lamp = sys.getNextEntity("lightType","AIUSE_LIGHTTYPE_ELECTRIC",lamp); if ( lamp != $null_entity ) { lamp.On(); } } 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. Optionally, you can set values for spawnargs in advance of the spawning.
void spawn_stagecoach() { sys.setSpawnArg("name", "stagecoach"); sys.setSpawnArg("model", "models/darkmod/misc/carriages/stagecoach.lwo") sys.setSpawnArg("origin", "483 723 41"); entity spawned_entity = sys.spawn("func_static"); //spawn a "func_static" entity with the spawnargs above }
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
- Next article: A to Z Scripting: Utility scripts
- Previous article: A to Z Scripting: Scripting with...
- Table of contents: A to Z Scripting