From StrategyWiki, the video game walkthrough and strategy guide wiki
Jump to: navigation, search

ETF is a direct port of a Quake III mod called Q3F (which has ceased in development) to Enemy Territory. This wiki covers the use of entities in ETF map making. It is apparently adapted from the Q3F Map Entity Documentation. Currently, that source seems to contain more information. It may be a good idea to consult it, even though some of the information may not apply to ETF.


What it's all about[edit]

  • The ETF entities are intended to be an extension to existing entities rather than replacing the current entities (at least, as far as possible). Currently, they're both incomplete and potentially bug-ridden. Many features we'd like to add haven't yet been added, and some entity types will not coexist happily with the extensions.
  • Lastly, before we begin, I'd just like to say I'm a great believer in programming first and documenting as late and as little as possible, in the grand tradition. Blame that if this document makes no sense. :)
 - Golliwog
  • Right, since I've taken over since then, some quick remarks on big changes. Initially the entities info_notnull, func_goalitem, func_commandpoint and func_goalinfo defaulted to a 30 second wait when there was NO wait field given. This default wait has been removed since Beta1f to stop some serious issues and make the entities more logical. If your map was based around this default wait, a recompile is needed.
  • Also, some of the worldspawn fields have changed, to make them shorter. Q3 couldn't parse the long keys and gave errors on compile. Finally, the ammo_bullets is now called ammo_nails, which is more logical, too ;)
 - RR2DO2
  • Also, you can now use the mapinfo files to set atmosphere and class names, names, team names, allied teams, etc throughout the whole map or for a specified gameindex.
 - ensiform

Basic Principles[edit]

Essentially, each entity has a 'state', as follows:

Invisible This entity is not triggerable, nor is it visible (although this has some odd side effects, e.g. invisible doors still stop people from walking through them).
Disabled This entity is not triggerable.
Inactive This entity is idle (and can be thought of as 'touchable'). For goalitems, this means 'back at base'.
Active This entity is active, and (depending on its settings), will go back to inactive after a set time. For goalitems, this means 'lying on the ground'.
Carried This state only applies to goalitems.

When successfully touched by a player, an entity will attempt to go to carried/active state. After a set time, active entities will automatically go back to inactive.

Entities can also trigger other entities in a 'cascade' of triggers, by using <state>target keys, e.g. on a flag entity you might have "carriedtarget" "alarms" - that would activate all 'alarms' entities when a flag became carried.

There are a set of criteria checked before an entity can be triggered (although triggers can also be 'forced' to ignore criteria, more on this later).

In addition to this, players can be 'given' bonuses, and teams can be given scores, as well as having messages and sounds played.

What it can do[edit]

  • It can be used to create complex map effects and the like (think of Team Fortress), as well as some degree of 'scripting'.

What it can't do[edit]

  • It can't be used as a language in it's own right - it will maintain no variables, parse no scripts. This may become available in a future release, but not right now (also, there would be performance questions in a scripting language).


Trigger Types[edit]

There are six main sorts of triggers you'll use:

Brush triggers This is something like a trigger_multiple - an area brush that triggers when you walk over it.
Point triggers These are used as func_goalinfo entities, and can have mins and maxs set to define their actual size. They trigger when you walk into the defined area surrounding the entity - they can also do other effects, such as showing a model that becomes invisible while active, etc.
Untouchable triggers These are usually set as info_notnull, and act as relays or simply alternative triggers (i.e. two entities with the same name, one only triggerable by blue and one only triggerable by red).
Flags These are used as func_goalitem entities (or func_flag, same thing), and are 'carried' by the player when successfully touched.
Standard entities These are things like func_door, or info_player_start entities - they act in more or less the usual fashion, but the criteria and cascading triggers still operate.
Special entities These are things like the func_commandpoint - new entities that have special behaviour defined by the entity. (More on these later).

Sample Flag[edit]

A 'flag' is a carryable entity that can only be carried by one team, and does various effects when carried.

    // The red flag

    "classname" "func_goalitem"
    // this one is set by the editor when you position the entity:
    "origin" "1248 1984 -328"

    // What it looks like
    "model" "models/flags/r_flag.md3"    // Model to use
    "light" "200"                        // Dynamic light
    "color" "1 0 0"                      // Pure red
    "sparkle" "1 0 0"                    // Generate smoke puffs around carrier

    // What it's called
    "groupname" "redflag"

    // Criteria
    "allowteams" "blue"    // Only blue players can trigger (i.e. carry)

    // Messages on trigger
    // Centerprint message to activator:
    "carried_message" "~You have taken the ^1RED^* flag!"
    // Tell everyone activator has done it:
    "carried_all_message" "~%N has TAKEN the ^1RED^* flag!"
    // Tell everyone activator dropped the flag:
    "active_all_message" "~%N has DROPPED the ^1RED^* flag!"
    "inactive_all_message" "The ^1RED^* flag has returned."
    // Play Sound to team:
    "carried_team_sound" "~sound/teamplay/voc_team_flag.wav"
    // Play Sound to non-team players:
    "carried_nonteam_sound "~sound/teamplay/voc_enemy_flag.wav"
    // Play Sound to activator:
    "carried_sound" "~sound/teamplay/voc_you_flag.wav"

    // Flaginfo (shown on \flaginfo command)
    "carried_flaginfo" "%N has the ^1RED^* flag."
    "active_flaginfo" "The ^1RED^* flag has been dropped at $l."                        
    // Flags must use $l for active_flaginfo and active_all_message
    "inactive_flaginfo" "The ^1RED^* flag is at the red base."

    // Misc
    // Reveal spies on trigger, show flag above player when carried:
    "flags" "revealagent,showcarry"
    // Wait 45 seconds before going inactive (i.e. back to base):
    "wait" "45"

Sample Capture Point[edit]

Now we have a flag, we need somewhere to capture it.

    // The capture point in the blue base

    "classname" "trigger_multiple"
    // Origin/size defined by brush (never set)
    "model" "*10"

    // Criteria
    // Only trigger if activator holds redflag:
    "holding" "redflag"

    // Messages
    // Centerprint message to everyone:
    "active_all_message" "~%N has CAPTURED the ^1RED^* flag!"
    "active_message" "You have scored 2 frags for capturing the flag."
    // Play sound to activator:
    "active_sound" "~sound/teamplay/voc_blue_scores.wav"
    // Play sound to activator's team (apart from activator):
    "active_team_sound" "sound/teamplay/flagcap_blu.wav"

    // 'Give' bonuses
    "give" "score=+2,health=+100,armor=+200,ammo_shells=+200,ammo_nails=+200,ammo_cells=+200,ammo_rockets=+200,ammo_medikit=+50,gren1=+4,gren2=+2"
    // Give activator's team 10 points (the teamscore, not the players on the team):
    "teamscore" "10"

    // When activated, force redflag to inactive (back to base),
    // trigger scorer (optional extra 10 points entity):
    "activetarget" "redflag=~inactive,scorer"

Sample Command Point[edit]

To make things a little more interesting, why don't we have a 'scorer' command point that gives players an extra 10 points when they capture the flag?

    // A button to press to claim the command point

    "classname" "func_button"
    "model" "*1"
    "angle" "-2"
    "wait" "5"
    // Trigger the scorer command point when activated:
    "activetarget" "scorer_cp"

    // The scorer command point (no model, so must be triggered by another entity)

    "classname" "func_commandpoint"
    "origin" "448 832 -128"

    // Misc
    "groupname" "scorer_cp"

    // Messages
    "active_all_message" "~%N has claimed the\ncapture bonus for the %T!"

    // On activation (i.e. touch if we have a model, or trigger by a button
    // or something otherwise)
    // All entities named 'scorer' are locked to activators team:
    "teamset" "scorer"
    // Set scorer to inactive (i.e. ready to be activated):
    "activetarget" "scorer=inactive"

    // An untouchable 'scorer' entity
    "classname" "info_notnull"
    "origin" "444 828 -84"         // Hardly 'essential' in this case...

    // Misc
    "groupname" "scorer"           // So other entities can reference
    // Prevent scorer from being triggered initially
    // (i.e. until command point is claimed):
    "initialstate" "disabled"

    // On activation
    "teamscore" "10"               // Give the activator's team 10 points
    // Go inactive after 1 second (the chances of someone capturing twice
    // in under a second are pretty slim...):
    "wait" "1"

Sample Hud[edit]

We've forgotten something - we need to show a player if he's carrying the flag or not, or he's liable to get confused (and upset if he finds he's had it for the last 5 minutes without capturing). We need a HUD entity.

    // A HUD entity shown to the red flag carrier
    "classname" "func_hud"
    "origin" "1252 1980 -292"

    // Entity never actually activates, just show this model:
    "inactive_model" "models/flags/r_flag.md3"
    "slot" "1"            // Display it in slot 1 (top left)
    "holding" "redflag"   // Only player holding redflag can see this entity


Entity States[edit]

Essentially, each entity has a 'state', as follows:

Invisible This entity is not triggerable, nor is it visible (although this has some odd side effects, e.g. invisible doors still stop people walking through them).
Disabled This entity is not triggerable.
Inactive This entity is idle (and can be thought of as 'touchable'). For goalitems, this means 'back at base'.
Active This entity is active, and (depending on its settings), will go back to inactive after a set time. For goalitems, this means 'lying on the ground'.
Carried This state only applies to goalitems.

When a player touches an entity and the criteria are passed, it will be triggered to carried (if it's a carryable entity), or active.

Unless forced (see targets), only certain state changes are permitted (note that you cannot change state to the current state (nothing at all will happen):

  invisible disabled inactive active carried
invisible   * *    
disabled *   *    
inactive * *   * *
active     *   *
carried     * *  

When an entity changes state, it will trigger any messages or sounds for that state, as well as executing give (only when in carried/active state, depending on whether it can be carried), teamscore (again, only in carried/active) and <state>target (see targets).

An entity can have its state changed a maximum of 5 times (currently) per-frame single frame - this is to prevent infinite loops from hanging the server - but you would be wiser to avoid any situations where this kicks in (since you cannot always be sure what state the entity will end up in, particularly if the limit changes).


When an entity changes state, messages and sounds can be sent out to various players, optionally with the 'force' flag (prefixing the string with a ~ symbol). The force flag causes messages to be centerprinted and sounds to be global (i.e. no attenuation). Messages can be sent to four different groups: The activator, the activator's team (excluding activator), all players not on the activator's team, or all players.

<state>_message Send a message to the activator.
<state>_team_message Send a message to the activator's teammates.
<state>_nonteam_message Send a message to all players not on the activator's team.
<state>_all_message Send a message to all players.
<state>_sound Play a sound to the activator (currently, plays globally as though ~ was specified).
<state>_team_sound Play a sound to the activator's teammates (currently, plays globally as though ~ was specified).
<state>_nonteam_sound Play a sound to all players not on the activator's team (currently, plays globally as though ~ was specified).
<state>_all_sound Play a sound to all players.

<state> in the above strings means the state to play on, e.g. active_message. For sounds, currently only sounds everyone can hear (<state>_all_sound) can be played 'unforced' (i.e. fades with distance from the entity).

Additionally, there is another prefix that is not a state. This is the kill prefix. It is used just as the other messages, but useful for o.a. trigger_hurt entities. Example: "kill_all_message" "%n shouldn't have been here." as a keypair on a trigger_hurt will display "Dummy shouldn't have been here." as a deathmessage when the player called Dummy gets killed by the trigger_hurt.

Messages can also contain tokens that are expanded as required. Tokens in UPPERCASE represent the activator (if present). Tokens in lowercase represent the entity itself:

%H Health
%A Armor
%L Location (returns 'unknown location' if unable to work it out)
%D Location of last death.
%R Last reported location (i.e. identical to last %L)
%T Team name (e.g. "Red Team", "Blue Team" etc.)
%C Team colour (e.g. red, blue etc.)
%G Disguise (returns 'not disguised' if not wearing a disguise)
%N Name (player name / entity groupname (or one of them)
%E State (e.g. "active", "carried" etc.)
%S Class (e.g. "soldier", "medic" etc.)

For example, you could say:

"active_flaginfo" "The ^1RED^* flag has been dropped at $l."
// $l is used for locations of the goalitem entity dunno why but it is.

Incidentally, you can use these tokens in communications as well (case does not matter), save that you require $ instead of %. If you want to annoy everyone on the server, just say "Hello $r, I'm $n of the $t." (I wouldn't really recommend it, though). $r is readers name now instead of $n.


Flaginfo is sent to players who execute a \flaginfo command. All entities that have a <state>_flaginfo will have that info sent to the client. Normally, it's flags and the like that keep flaginfo, hence the command name. For flags, you'd expect a carried, active, and inactive flaginfo string, e.g.

carried_flaginfo "%N has the red flag at %L (and only %H health left)"
active_flaginfo "The red flag is lying in the field at $l" (note the lowercase and $, there's no carrier!)
inactive_flaginfo "The red flag is safe at base"


Entities can 'give' bonuses to affected players when set to carried (if a carryable entity) or active (if not). The give string is of the form "give" "stat=value,stat=value,stat=value". The following stats are available:

health Health
armor Armor
damage Deal damage to the target
ammo_shells Shells
ammo_nails Nails
ammo_rockets Rockets
ammo_cells Cells
ammo_medikit Medikit ammo
ammo_charge HE Charge
score Player score (not teamscore)
gren1 Grenade type 1
gren2 Grenade type 2
quad Seconds of quad damage
regen Seconds of regen
flight Seconds of flight
battlesuit Seconds of battlesuit
invis Seconds of invisibility (shimmer invis, not agent invis)
haste Seconds of haste
invuln Seconds of invulnerability
aqualung Seconds of aqualung
ceasefire Seconds a player is unable to shoot
gas Seconds of gas
stun Seconds of stun
flash Seconds of flash
tranq Seconds of tranq
fire Amount of fire counters, every counter does a certain amount of burn damage every few seconds
armortype Armour type (as a number from 0-100). This is the amount of damage the armour saves you from*
aclass_shell If true, give 15% extra protection against shells (otherwise remove)
aclass_bullet If true, give 15% extra protection against nailgun / sniper rifle (otherwise remove)
aclass_explosive If true, give 15% extra protection against explosions (otherwise remove)
aclass_shock If true, give 15% extra protection against emp/electrical attacks (otherwise remove)
aclass_fire If true, give 15% extra protection against fire and prevent burning (otherwise remove)
aclass_all If true, set all armour classes (otherwise, remove all armour classes)

If not forced (with a ~ prefix), these values are limited to player maximums. If + or - is prefixed, they're added or removed from existing values rather than set, e.g.

"give" "health=~500,armor=+50,score=+3,quad=10,haste=10,aclass_fire=1"

Would give a player 500 health (no matter their class max), 50 more armour (up to class max), 3 points, ten seconds of quad and haste (no matter what they had before), and fire-resistant armour.

If armortype is not specified and armour is given (i.e. the player gets more armour than they had before the command), it is automatically assumed that their armour type should be set to the maximum. If you want their armour type to remain unchanged, put in a key like "armortype=+0".

There are also a set of entity keys that affect the command:

affectteams Affect the named teams, (e.g. "red,blue") regardless of which team activated this entity
effectradius Affect only those players within the specified radius
holding Affect only those players holding these goalitems
notholding Affect only those players not holding these goalitems
clientstats Affect only those matching these clientstats

Note that the way the criteria work, it first checks affectteams and the affectteam/affectnonteam flags for players to affect, and them limits this based on the lineofsight/environment flags.


There are a set of boolean flags that can be used to affect the entity's behavior in multiple areas, as follows. These can be categorised as criteria/effect/command flags:

hideactive (cmd) Cause the entity to be invisible while active (e.g. for refill goalinfos).
affectteam (eff) Cause the 'give' command to affect everyone on the activator's team.
affectnonteam (eff) Cause the 'give' command to affect everyone not on the activator's team.
dropoff (eff) 'give' values 'drop off' with distance (requires a 'range' key in the entity).
lineofsight (eff) Only entities in line of sight will be affected.
environment (eff) Only entities in the same environment will be affected.
shootable (cmd) Allow shooting the entity to be equivalent to touching it (only for func_doors).
reversecriteria (cri) Causes the entity to only be triggered if the criteria are NOT met.
orclientstats (cri) Causes the clientstats criteria to be OR instead of AND.
revealagent (cmd) If triggered, the activator will lose his disguise.
showcarry (cmd) On carryable entities, cause it to be shown above the player, as with CTF flags.
chargeable (cmd) Allow HE charging the entity to be equivalent to touching it.
rotating (cmd) Cause the model to rotate and bob like an item (only for goalinfo/goalitems).
noshrink (cmd) Goal does not grow or shrink when appearing/disappearing.
nodrop (cmd) On carryable entities, stop the 'dropflag' command affecting it.
keepondeath (cmd) On carryable entities, don't drop on death.
usegauntlet (cmd) Entity must be axed instead of touched.
allowdead (cmd) Allow dead players to be selected in target_cycle use.
allowsame (cmd) Allow same player to be selected in target_cycle use.
faildirection (cmd) Forcefield direction field applies to those failing the criteria.
allowsentrylock (cmd) Sentry is allowed to lock on through forcefields (bad idea :).
disguisecriteria (cri) Criteria work on agent's apparent team/class rather than real.

More to explore[edit]

This section discusses additional noteworthy features of ETF mapping (not just related to entities) and provides links to further online resources.

Particle system[edit]

ETF includes a particle engine for making light effects, sparks, etc. To add a source of particles to your map, create an entity like the one in the example below:

    "classname" "misc_particlesystem"
    "origin" "8 0 80"
    "script" "spirit/maps/muon_blue_sparks.spirit"
    "dir" "90"

The "script" attribute refers to a script file in one of the *.pk3 archives available to the game engine. To see examples, extract the *.spirit files contained in the pk3s shipped with ETF. There is also some reference documentation available here: Spirit File Docs