DECORATE state syntax

From Eternity Wiki
Jump to navigationJump to search

EDF allows using a concise ZDoom DECORATE-style state syntax for thing types, by using the states field, followed by a heredoc (multiline string). The syntax is based on the one from ZDoom, described here, in the ZDoom wiki.

Note that DECORATE errors in Eternity will trigger warnings at startup which can only be observed with -edfout or -edf-show-warnings during the text-mode part of the program initialization. You can press the Pause key during that sequence to read the warning messages. Usually in these cases, the DECORATE state definition is fully disabled.

Back to EDF state reference


The DECORATE grammar is defined as such (source: Eternity source code comment):

 <labeledunit> := <labelblock><frameblock><labeledunit> | nil
   <labelblock> := <label><eol><labelblock>
     <label> := [A-Za-z0-9_]+('.'[A-Za-z0-9_]+)?':'
     <eol>   := '\n'
   <frameblock> := <frame><frameblock> | <frame>
     <frame> := <keyword><eol> | <frame_token_list><eol>
       <keyword> := "stop" | "wait" | "loop" | "goto" <jumplabel>
         <jumplabel> := <jlabel> | <jlabel> '+' number
           <jlabel> := [A-Za-z0-9_:]+('.'[A-Za-z0-9_]+)?
       <frame_token_list> := <sprite><frameletters><tics><flagslist><action>
         <sprite> := [A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]
         <frameletters> := [A-Z\[\\\]]+
         <tics> := [0-9]+
         <flagslist> := <flag><flagslist>
           <flag> := "bright" | "fast" | "offset" '(' <x> ',' <y> [',' "interpolate"] ')' | nil
         <action> := <name>
                   | <name> '(' <arglist> ')'
                   | nil
           <name> := [A-Za-z0-9_]+
           <arglist> := <arg> ',' <arglist> | <arg> | nil
             <arg> := "string" | number

Here's an example, the Sergeant's DECORATE state (from base/doom/things.edf):

   SPOS AB 10 A_Look
   SPOS E 10        A_FaceTarget
   SPOS F 10 bright A_SPosAttack
   SPOS E 10
   goto See
   SPOS G 3
   SPOS G 3 A_Pain
   goto See
   SPOS H 5
   SPOS I 5 A_Scream
   SPOS J 5 A_Fall
   SPOS K 5
   SPOS L -1
   SPOS M      5
   SPOS N      5 A_XScream
   SPOS O      5 A_Fall
   SPOS U     -1
   goto See

Here we can see three kinds of units: labels, frames and keywords.


Labels are the Spawn:, See:, Missile: and other lines ended with colons. They designate starting points for special thing states, equivalent to non-DECORATE EDF thingtype spawnstate, seestate, missilestate and so on. You can also create your own labels, which will be reachable using keywords like goto, as well as state-jumping codepointers. Labels are case-insensitive. The following labels are used natively by Eternity:

  • Spawn: equivalent of spawnstate: state the thing is spawned at;
  • See: equivalent of seestate: state the monster goes into after seeing a target;
  • Melee: equivalent of meleestate: state the monster goes into after getting close to the target;
  • Missile: equivalent of missilestate: state the monster goes into when deciding to shoot the target;
  • Pain: equivalent of painstate: state the thing goes into when hit;
  • Death: equivalent of deathstate: state the thing goes into when killed or otherwise destroyed;
  • XDeath: equivalent of xdeathstate: optional state the thing goes into when killed with very high damage;
  • Raise: equivalent of raisestate: optional state the corpse goes into when raised by an archvile;
  • Heal (since Feb 21 2016): equivalent of healstate: state to jump to if during VileChase it encounters a corpse. If not set, it will just go into S_VILE_HEAL1.
  • Crash: equivalent of crashstate: optional state for a corpse to go into when falling and hitting the floor;
  • Active: equivalent of activestate: optional state to go into when triggered by Thing_Activate;
  • Inactive: equivalent of inactivestate: optional state to go into when triggered by Thing_Deactivate;
  • Pickup.Respawn: state to jump to when the gettable item thing has the RAVENRESPAWN flag and is picked up, after which it waits to respawn;
  • Pickup.Remove: state to jump to when the gettable item thing has the RAVENRESPAWN flag and is picked up, after which it is removed permanently without respawning. This applies for items which for any reasons must not respawn, such as individual dropped items. It's a property of the particular item, not of the entire thingtype class, so be sure to also include a Pickup.Respawn state.

Damage type pain and death states[edit]

You can also add labels for means-of-death (damage type) specific pain and death states. They're equivalent to the dmg_painstates and dmg_deathstates thingype fields. You formulate them as such:

  • Pain.damage_type: for non-death pain states to go into when hit by specific kinds of damage;
  • Death.damage_type: for death states when hit by specific kinds of damage.

The built-in damage types are defined in base/things.edf, and you can add your own. Example: define Pain.Pistol, Death.Pistol states when the thing is hit by player pistol bullets and has to react specifically to this kind of damage.


A frame has the following syntax:

sprite frame_letters tics [flags_list] [action]
  • sprite: this is a four letter code corresponding to the sprite lump stem. In Doom, for example, imps have TROO, zombiemen have POSS and so on. For the invisible sprite, use TNT1.
  • frame_letters: this is one or more letters corresponding to the sprite frame index. Include more than one letter if you want a sequence of frames with the same properties (duration, flags and action).
  • tics: the duration, in tics (1/35 seconds), of each frame. Do not use value 0 in loops. Setting value -1 will cause the thing to stop at that frame, without going forward.
  • flags_list: zero or more flags, separated by spaces. The flags can be:
    • bright: causes the sprite to be full bright in any place;
    • fast: makes the frame take half as long on fast skill levels (used in Doom by the demons);
    • offset(x, y[, interpolate]): useful for weapon HUD sprites, causes the image to be offset by the exact amount of x and y from the center-bottom, overriding the current bob amount. Under Doom, x must be nonzero for both offsets to have effect; under other games, each of them needs to be nonzero for each of them to have effect — otherwise the offset will be preserved from the previous frame. You can override the 0 condition choice (Doom/non-Doom) in gameproperties. Beware that y has the origin at 32. An offset of (0, 32) keeps the weapon at the origin, but because of Doom's rules, you may need to have (1, 32) or (-1, 32) instead. Do note that this offset vector has been available in vanilla Doom as well, modifiable under DeHackEd, and it is the same thing as the misc1 and misc2 frame fields, which means these work as parameters for several MBF codepointers. However, currently you cannot use strings or other expressions in x and y, unlike when using the full frame section or the cmp specifier. In the classic games original designs, only Hexen has used offset, in order to animate its melee weapons.
If interpolate is added, it causes the movement between offsets to be smooth. By default interpolation is disabled when offset is specified.
  • action: this can be a codepointer, optionally followed by a list of arguments enclosed by parentheses. Make sure to include strings in double quotes (""). Conventionally, from ZDoom, and based on the Doom source code in general, codepointers are prefixed with A_, but it is not required here.

Using state-switching codepointers with DECORATE[edit]

Some codepointers, such as CounterJump, Jump and so on let the calling thing jump to specific frames. For the frame name parameter, just use the label name.

The MBF-based RandomJump uses the misc1 and misc2 frame fields as parameters. Since the DECORATE style syntax currently only supports misc1 and misc2 through the offset specifier, you are better off using Jump, which uses args, is based on ZDoom's implementation and only works for DECORATE state syntax. Other codepointers, such as CounterJump, work well both for non-DECORATE and DECORATE syntax. Certain codepointers such as CounterSwitch aren't designed for DECORATE state syntax however.


These are placed at the end of a state, before other labels. They are:

  • stop: this is equivalent to setting next frame to 0. If the last frame in the state has a finite (different from -1) duration, it will disappear. Otherwise it's commonly used after -1 duration frames when nothing is expected to follow.
  • wait: this causes the last frame to loop back into itself. If the duration is 0, it will be adjusted to 1.
  • loop: this causes the last frame to loop back to where its label points.
  • goto jump_label: sets the next frame of the last frame to the one designated by the label.

Differences from ZDoom's DECORATE[edit]

  • The frame after a goto line must be labelled in Eternity. Of course, labels can be native (spawn, see, pain etc.) or user-defined.