Wolfenstein: Enemy Territory Documentation © 2003 Splash Damage, Ltd. All Rights Reserved. |

Moving Object Scripting
Making a moving object objective requires many interconnected entities and scripting, especially if it needs to be damageable.

Triggers
To allow people to move the object, and see it displayed on the command map, requires a few triggers: a trigger_multiple entity for movement and a trigger_objective_info entity for the command map.

The keys you will need to set on the trigger_multiple are:

targetname: | The name used in the script for referencing this entity.
scriptname: | The routine name in the script file.
target: | This should be the targetname of the target_script_trigger entity used to control the movement, which will be described later.

You may also want to set some of the spawnflags , to allow only one team, or one class, the ability to move the object.

The keys you will need to set on the trigger_objective_info are:

targetname: | The name used in the script for referencing this entity.
scriptname: | The routine name in the script file.
shortname: | The name that will be displayed on the command map.
track: | The name that will display when you are near it.
target: | This should be the targetname of the func_constructible which will be used to handle damaging/repairing the object, and allow it to show on the command map.

You must set the tank spawnflag for this to show up correctly, and if the object is to be destroyed, the team that owns the object must be chosen from the objective spawnflags.

The trigger_multiple should be a fairly large area around the object, whilst the trigger_objective_info should just fit around the edges, thus only allowing people to construct it when very close to it.

Building, Destroying and the Object Itself
The object itself is made from a script_mover entity. This is best made out of clip brushes, with a model2 key supplied to use an external md3 model.

The keys you will likely want to use for this are:

targetname: | The name used in the script for referencing this entity.
scriptname: | The routine name in the script file.
health: | The amount of damage the object can take.
model2: | The path to an md3 that will be used to display the object.
description: | The name that will show up when you hover your cursor over it.
tagent: | (as in tag-ent) If using a gun, the targetname of the entity to which it will be attached. (Note: this can be the same entity)
gun: | The type of gun. Default is mg42, other value is; browning.

There are various spawnflags which should be set, and some which must be set. You must have the resurrectable flag set and in most cases you will want solid to be set as well. If the object is to be damageable, the appropriate team button should be selected for who owns the object. The compass flag should be set if the object is to show on the compass. If the object is only to be destructible using explosive weapons, the explosivedamageonly flag must be set. Last of all, if you want to attach a gun to the object, the mounted_gun flag must be set.

For damage and repair of the object to work, you must have a func_constructible .

The keys you will need for this entity are:

targetname: | The name used in the script for referencing this entity.
scriptname: | The routine name in the script file.

The start_built flag should be set if the object is to be 'alive' at the beginning of the map. The appropriate team should again be chosen for the team that is to own the object.

Miscellaneous Entities
You will also need a handful of other point entities which I will now detail.

2 target_script_trigger entities
1 func_timer entity
1 target_kill entity

The target_script_trigger entities require the following keys :

targetname: | The name used in the script for referencing this entity.
scriptname: | The routine name in the script file.
target: | The name of the trigger event to fire within the routine. (Note: generally this was called 'run')

One of these will be known as the disabler, the other is the enabler (This is the one pointed to by the trigger_multiple entity)

The func_timer entity requires the following keys:

target: | This should point at the other target_script_trigger entity. (Disabler)
wait: | "1"

It should also have the start_on spawnflag set.

The target_kill entity requires the following keys:

targetname: | The name used in the script for referencing this entity.
scriptname: | The routine name in the script file.
target: | This should point at the func_constructible entity.

Scripting

The control of the object via scripting may look complex at first, but it is in fact quite simple. The examples below are taken from the 'Fueldump' script if you wish to take a look over the whole script.

First off, you have the two script trigger routines, which tell the tank whether there is someone in the trigger or not.

tank_disabler { trigger run { trigger tank tank_disable } } tank_enabler { trigger run { trigger tank tank_enable } }

The 'tank_enabler' routine will call the trigger run function while there are players standing within the trigger, and the 'tank_disabler' will call its trigger run function every time the func_timer activates, i.e. once per second. These two functions just keep the tank object informed about players being in the trigger or not, and it will make decisions based on this information.

Now we'll take a look at what the tank does with this information.

trigger tank_enable { trigger self stuck_check accum 1 abort_if_bitset 3 // stuck check accum 4 set 0 // reset stop counter accum 1 bitreset 8 // reset stop check accum 1 abort_if_bitset 2 // already following spline accum 5 abort_if_not_equal 0// are we not in a script lockout? accum 1 abort_if_bitset 7 // death check // Any just started moving stuff goes here trigger self script_lockout trigger tank_sound start startanimation 55 10 15 nolerp norandom wait 666 startanimation 5 40 15 nolerp norandom wait 500 trigger self tracks_forward trigger self script_lockout_stop trigger self move }

First off, it calls the trigger 'stuck_check' function (which will be explained later), which will set bit 3 of accum 1 if the object is stuck. If it is stuck, it aborts, as movement is not possible.

Next, it resets the counter (accum 4) which would stop the vehicle from moving if there was no-one there, and resets the counter that says we shouldn't be moving any more (accum 1 bit 8 ).

Next it aborts if we were already following a path, as you don't want the startup stuff to happen if already moving.

Next, it will abort if a script_lockout (accum 5, more information later) is in place, or if the tank has been damaged (accum 1 bit 7 ).

If it passes all of these checks, then the tank has just started moving, and so it calls all the sound startup and animation startup functions. As it has some waits to have the animation work properly, a script_lockout is put in place so that nothing can interrupt this sequence. After the startup is complete, it then calls the 'trigger move' function, to commence movement along a path.

The disabler routine below is fairly simple.

trigger tank_disable { accum 4 inc 1 // up the stop counter accum 4 abort_if_less_than 4 accum 1 bitset 8 // set stop check trigger self deathcheck }

Every time this is called (once per second), accum 4 is incremented. If the value of accum 4 is below 4, then the function does nothing more. Accum 4 will be reset by the enabler function as long as the tank is not stuck, and someone is in the trigger. If the tank is stuck, or no-one is in the trigger, then the accum will be allowed to continue incrementing, up to the value of 4 and beyond. Once it reaches this stage, the rest of the function will be allowed to execute, and the stopped moving flag (accum 1 bit 8 ) will be set. This also then calls the 'trigger deathcheck' function, to be sure that something is checking whether the tank has been damaged enough to go into its broken state.

Outlined below are some more of the tank's main functions.

Movement Loop

trigger move_check { trigger self stuck_check accum 1 abort_if_bitset 3 trigger self dispatch } trigger move { trigger self move_check wait 500 trigger self move } trigger dispatch { accum 3 trigger_if_equal 0 tank run_0 ... accum 3 trigger_if_equal 133 tank run_133 }

The 'trigger move' function will be called from the 'tank_enabler' event upon startup, and never again, unless it shuts down, and then starts up again, or is continuing movement from a previous call. The 'trigger move' function calls the 'trigger move_check' function which will see if the tank is stuck, and if not, then call the 'trigger dispatch' function which will in turn select the appropriate 'run_XXX' function to move from its current position. If the 'trigger move_check' function fails because the tank is stuck, then execution will return to the 'trigger move' function. The function will wait 500ms and then call itself again.

Run Events

trigger run_0 { accum 1 bitset 2 trigger tank_turret2 run_0 followspline 0 spln0 80 wait length -64 accum 1 bitreset 2 trigger self run_continue }

Above is one of the tank's 'trigger run' functions. This first sets the moving status to in (accum 1 bit 2 ), then issues the command for the turret to move (as it isn't attached using a tag in this case), and then issue its own move command. After this has completed, it sets the move status to off again, then calls the 'trigger run_continue' function.

trigger run_incpos { accum 3 inc 1 } trigger run_continue { trigger self run_incpos trigger self deathcheck trigger self stopcheck trigger self move }

The 'trigger run_continue' function increases the position counter for the tank, checks it hasn't been damaged, if it hasn't, then checks it hasn't stopped because no one is escorting it, and then if there are escorts, trigger the move function, to continue movement.

Stuck Checking

trigger stuck_check { accum 1 bitreset 3 trigger self stuck_check_bridge_built trigger self stuck_check_bridge_dyna trigger self stuck_check_scriptlockout trigger self stuck_check_finished } trigger stuck_check_finished { accum 3 abort_if_less_than 134 accum 1 bitset 3 } trigger stuck_check_scriptlockout { accum 5 abort_if_equal 0 accum 1 bitset 3 }

These functions, plus several more not outlined here, form a set of functions that check whether the tank can move or not.

The 'trigger stuck_check' function first resets accum 1 bit 3 , which it uses as an indicator to say whether or not the tank is in fact stuck. Since we are resetting it every time, we are assuming that the tank is not stuck unless we are told otherwise, which is the easiest way to handle things. Next, it calls the functions for each individual reason for being stuck, such as the bridge not being built, or it having finished its entire run across the map.

These extra functions simply check to make sure they are valid, usually by checking the position indicator, and any other data they may need to know about. If those checks don't fail, then they set accum 1 bit 3, as an indicator that the tank is in fact stuck. Now, no matter what any of the other events do (assuming they don't reset accum 1 bit 3, which they shouldn't ever do), the flag will remain set when the 'trigger stuck_check' function exits. Other functions can use this to check whether the tank is stuck by simply calling the 'trigger stuck_check' function, then checking the value of accum 1 bit 3 .

Stop Checking

trigger stopcheck_setup { accum 1 bitset 6 accum 1 abort_if_bitset 8 trigger self stuck_check accum 1 abort_if_bitset 3 accum 1 bitreset 6 } trigger stopcheck { trigger self stopcheck_setup accum 1 abort_if_not_bitset 6 trigger self script_lockout // Any just stopped moving stuff goes here trigger tank_sound stop trigger self tracks_stop startanimation 45 10 15 nolerp norandom wait 666 startanimation 0 1 15 nolerp norandom wait 900 trigger self script_lockout_stop resetscript }

These two functions, in combination, check whether the tank should stop its movement, as either it's stuck, or no-one is escorting it.

The 'trigger stopcheck_stuck' function does the initial checks to see whether either of those conditions are met, and stores the result as accum 1 bit 6 .

The 'trigger stopcheck' function will then check that value, and act accordingly, either shutting the tank down, or aborting. As before, since the shutdown animation requires some waits, the shutdown enforces a script lockout during that period to prevent anything from interfering.

Script Lockouts

trigger script_lockout { accum 5 inc 1 } trigger script_lockout_stop { accum 5 inc -1 }

The script lockout system acts as a reference counter, that is, it keeps track of how many times it has been told to lock itself using accum 5. This allows various parts of the script to check whether it is locked, by checking that accum 5 is not 0. As this isn't just a flag, it can be used by multiple parts of the script at once, from multiple procedures, as long as they make sure to unlock the tank when they are finished. This was used for the firing sequences and some other things.

Death and Resurrection

death { accum 1 bitset 7 } trigger deathcheck { accum 1 abort_if_not_bitset 7 accum 1 abort_if_bitset 9 accum 1 abort_if_bitset 2 accum 5 abort_if_not_equal 0 accum 1 bitset 9 changemodel models/mapobjects/tanks_sd/churchhill_broken.md3 setstate tank_smoke default alertentity kill_tank trigger self sound_death trigger self tracks_stop trigger self script_lockout trigger self tracks_stop startanimation 45 10 15 nolerp norandom wait 666 startanimation 0 1 15 nolerp norandom trigger self script_lockout_stop resetscript }

The 'trigger death' function simply sets accum 1 bit 7 to on, as a flag for the 'trigger deathcheck' function, so that it knows that the tank's health has reached 0.

The 'trigger deathcheck' function does a few checks at the beginning before changing the model, and playing the shutdown sequence. These checks are: making sure the health has hit 0, the tank hasn't already been made to look broken, the tank isn't moving, and the tank isn't in a script lockout. As before, it initiates a script lockout during the shutdown process.

This also fires the target_kill entity, which is used to damage the func_constructible entity, making the trigger_objective_info entity become active, and so, allowing people to rebuild the constructible, which in turn, will activate the script_mover entity which will call the 'rebirth' function below.

rebirth { accum 1 bitreset 9 accum 1 bitreset 7 trigger self script_lockout changemodel models/mapobjects/tanks_sd/churchhill.md3 setstate tank_smoke invisible trigger tank_sound rebirth wait 500 trigger self script_lockout_stop }

This function simply makes the tank look repaired, setting the appropriate flags, and starts the low engine hum up again.

The Other Entities

tank_trigger { spawn { wait 100 attachtotag tank tag_turret } }

This is the trigger_multiple entity routine which is used to move the tank. When it spawns, it attaches its origin to a tag positioned at the turret origin on the tank. This allows the trigger to follow the tank around, without having to script its movement independently.

tank_build { spawn { wait 100 attachtotag tank tag_turret } }

This is the trigger_objective_info entity routine, which is used for the repair of the tank. As with the trigger_multiple entity it attaches itself to the tank, for easy movement.

tank_construct { spawn { wait 400 constructible_class 2 } built final { alertentity tank } trigger final_pos { constructible_constructxpbonus 3 constructible_destructxpbonus 3 } }

This is the func_constructible entity associated with the tank. First, it sets its construction class, which is required for the command map marker to show correctly. When the construction finishes, it activates the tank, which will call its 'rebirth' function, and fill its health up again.

When the tank reaches its final position, the 'trigger final_pos' function is called, which changes the amount of XP you get for repairing the tank.