Converting DECORATE code to ZScript

From ZDoom Wiki
Jump to navigation Jump to search

DECORATE code is mostly compatible with ZScript code, able to recognize any action function and definition of actors. However, the parser is stricter on detecting code conversions and there is some syntax changes.

DECORATE and ZScript can be used simultaneously. There is therefore no need to convert a DECORATE actor to ZScript if you do not intend to use this opportunity to enhance its code with ZScript-specific features. Converting the code from a large project need therefore only be done when it is important, while the more trivial code can remain untouched. Note that while DECORATE actors can inherit from ZScript actors, the reverse is not true; therefore if an actor is converted, any of its parent classes must also be converted; however its children classes can be left alone.

Required changes

These changes must be performed on all DECORATE code in order to work with ZScript.

  • Actors are now known as classes which must inherit from Actor (or anything inheriting from Actor itself).
  • Actor names must be valid identifiers (i.e. be composed of only letters, numbers, and underscores; and must start with a letter or underscore). Most notably, this means that actor names may no longer begin with a number.
  • 'return state("name") casts must be converted by the following rules:
    • state() is now known as ResolveState()
    • No state (previously 0 or "") must now use null instead.
NOTE: null and "Null" are two different meanings. Every actor has a Null state, and the double quotes means to jump there. This will destroy the actor. Without the quotes, it means jump to no state.
return ResolveState(null); // No state jump.
return ResolveState("Null"); // Destroys the actor.
  • float types must be named as double.
    • In DECORATE, floats were actually doubles the whole time. The name float was used as a misnomer, simply translated into a double internally.
  • Property separations require a comma.
  • Damage must be renamed to DamageFunction if direct control is desired.
  • Properties must now be placed inside a Default { } block. Constants are an exception. See the examples below.
  • Damagetype definitions belong in MAPINFO.
  • PowerupType no longer automatically adds Power to the beginning of items.
  • Enums now require a name. enum namehere
  • Some warnings such as missing actors are treated as errors. All actors must be defined.
  • All class names, colors, and strings now require enclosure by quotes (" ").
    • Species must be set to "None".
    • Empty Sounds and colors must use "".
    • Empty class actors, names and strings must use null. This applies to the following examples:
return ResolveState(null);
A_CustomBulletAttack(...,null); // null can be used to not spawn something like the puff or projectile tracer, as "" will not work.
  • Backslashes in strings must be escaped (i.e. use a double backslash); this is technically not a new requirement but is encountered more often due to the quote enclosure requirement. For example:
Inventory.Icon "RPOW\\0"; // will set the icon to the '\' frame of the RPOW sprite. Using "RPOW\0" will not work.
  • Every property, state definition and action function, whether inside or outside of anonymous functions now require a semicolon at the end. Even if the state definition doesn't have an action occurring, it requires a semicolon.
    Exceptions: actor flags, closing brackets ( }) (enum closing brackets still require it).
  • Game, DoomEdNum, SpawnID and ConversationID cannot be used in ZScript. Use MAPINFO for these instead.
  • Subclass flags are no longer optional and will require the appropriate prefix:
+ALWAYSPICKUP // This will make ZDoom error out.


While the following behavior does not prevent the game from loading, at any time this could be changed. As such, it is strongly recommended to avoid the following:

  • Changing deprecated flags, A_ChangeFlag, and A_CheckFlag. One can assign the flag directly now. See below for more information.
  • Position and velocity are accessed through vectors.
pos.* //where * is x, y or z
vel.* //same as position, replaces velx//y/z (just adds a period between 'vel' and 'x')


ZScript brings some immediate benefits.

  • String constants can be defined and used in any property, using double quotes.
  • Name constants can be defined with single quotes.
  • String comparisons
  • Class<Actor> casting for names, specifically actors.
    • Strings are simply compared with <, >, <=, >=, ==, ~== operators.
    • ~== is case insensitive. This also only works on strings. Names do not have ~== available and aren't case sensitive.
  • Structs
    • Struct member functions
  • SizeOf/AlignOf operators
  • Custom pointer creation
  • Custom functions
  • Direct access to actor flags
  • 2D and 3D Vectors
    • Functions: Unit() and Length()
  • Switch/Case
  • Virtual Functions
  • States and Functions cast types
  • Pass-by-reference
  • PSprite manipulation (aka actor overlays)
  • Custom player animation control
  • Multiple return value assignments
    • NOTE: The syntax is slightly different from LUA. Square brackets around them. I.e.
[a,b] = Function();
  • Named Arguments
A_SpawnItemEx("DoomImp", flags: SXF_TRANSFERPOINTERS);
  • Constant and local arrays


Some specific code syntax has changed which requires special noting.


Virtual functions allow overriding by inherited classes. Some of the internal functions have also been exposed.

  • BeginPlay: Called just after an actor class has been created. This is used for initializing the defaults of an actor.
  • PostBeginPlay: Called just before the first tic is processed, after BeginPlay.
  • Tick: Called every tic that passes. This makes an actor actually perform over time in game.
  • Activate: Called whenever Thing_Activate or similar is used.
  • Deactivate: Called whenever Thing_Deactivate or similar us used. This is performed if the actor is spawned with the DORMANT flag.

Virtual functions can be defined in two ways, both mutually exclusive to one another:

virtual void MyFunction();
override void MyFunction(); //Override is already considered virtual on the engine side.

To call the super function:

override void PostBeginPlay()
    Super.PostBeginPlay(); // In ZDoom, the super call is done by just super. instead of Super::

This is especially important with the internal functions, otherwise the actor will not perform properly unless an entirely advanced new version is implemented.

Cast types

States and functions can optionally have one of four cast types. Generally, these will not be needed, but can be used identify specifics on what type of state is allowed to execute what kind of functions. The four types are:

  • Actor
  • Overlay
  • Weapon
  • Item
    // Only actor-qualified functions can be used in here.
Action(Weapon) void MyFuncName()
    // Only weapon-qualified states will allow this function to be used.


Damage property no longer allows direct control of how much damage is given. This ability has been split into DamageFunction.

DamageFunction (random(3,12));

Actor flags

Flags can be changed by prefixing the flag in question with a 'b':

bShootable = true;
bVulnerable = false;

However, special conditions apply for some flags which require a built-in function to use, such as A_ChangeLinkFlags and A_ChangeCountFlags.


Constants must be defined outside of the Default { } code block, and they must not be given a type -- these are automatically resolved at compiled time. There is a difference for determining names and strings.

  • String constants use double quotes after the =.
  • Name constants use single quotes after the =.

User variables

User variables are known as local variables in ZScript. These are much more powerful in ZScript and can be assigned to anything and everything, including weapons.

They can be declared like this:

// Declaring variables is done like this.
int localvar, secondvar, thirdvar;
// Assigning values to local variables upon declaration only works inside (non)anonymous functions.
TNT1 A 0 
    double testing = 0.12345;
    double CountTracker = 33.543;

To use them on weapons, and custominventory, it must be invoked. This must be done within either an anonymous function or a defined function with the word 'action' before the return type. Anonymous functions are considered action by default.

TNT1 A 0 { invoker.CountTracker += frandom(1.28,2.56); }

// Or

action void MyFunc()
    invoker.CountTracker += frandom(1.28,2.56);

The A_SetUserVar and A_SetUserArray family will no longer function in ZScript. Instead, direct assignment must be used just like C++.

testing = 3.14 * 8;

Examples: Converting DECORATE

This is an example of how constants are made.

// (needs verification)
const String1 = "This is a string."; // Defines a string using double quotes.
const GLLight1 = 'ActorLight1'; //Defines a name using single quotes.
const Number1 = 1;
const NumFloat = 1.0;
const ofsx = 1;
const ofsy = 1;
// And in the states:
TNT1 A Number1 Offset(ofsx, ofsy) Bright Light(GLLight1);

This is the BeastBall from Heretic's Beast.

class BeastBall : Actor
        Radius 9;
        Height 8;
        Speed 12;
        FastSpeed 20;
        Damage 4;
        RenderStyle "Add";
        SeeSound "beast/attack";
        FRB1 AABBCC 2 A_SpawnItemEx("Puffy", random2[BeastPuff]()*0.015625, random2[BeastPuff]()*0.015625,
                                    random2[BeastPuff]()*0.015625, 0,0,0,0,SXF_ABSOLUTEPOSITION, 64);
        FRB1 DEFGH 4;

This is the ArtiPork item from Raven converted from DECORATE to ZScript.

class ArtiPork : CustomInventory
        Inventory.PickupFlash "PickupFlash";
        Inventory.Icon "ARTIPORK";
        Inventory.PickupSound "misc/p_pkup";
        Inventory.PickupMessage "$TXT_ARTIEGG2";
        Tag "$TAG_ARTIPORK";
        PORK ABCDEFGH 5;
        TNT1 A 0 A_FireProjectile("PorkFX", -15, 0, 0, 0, 1);
        TNT1 A 0 A_FireProjectile("PorkFX", -7.5, 0, 0, 0, 1);
        TNT1 A 0 A_FireProjectile("PorkFX", 0, 0, 0, 0, 1);
        TNT1 A 0 A_FireProjectile("PorkFX", 7.5, 0, 0, 0, 1);
        TNT1 A 0 A_FireProjectile("PorkFX", 15, 0, 0, 0, 1);

Examples: ZScript functions

This is an example of defining a struct of constants and using them in actor classes.

struct SpecialConstants
  const Target = AAPTR_TARGET;
  const Master = AAPTR_MASTER;
  const Tracer = AAPTR_TRACER;
  enum MyEnum
     num1 = 1,
     num2 = 2,
     num3, // Auto-assigned the next incremented value. In this case, 3.
     num4, // Auto-assigned the next incremented value. In this case, 4.

class Monstah : Actor
     TNT1 A 0
        SpecialConstants cns;
        if (CountInv("something",cns.Target) == cns.num2)
           //Do something

This defines the Cacodemon's A_HeadAttack function in ZScript.

// Code (must be attached to Actor)
extend class Actor
   void A_HeadAttack()
       if (target)
           if (CheckMeleeRange())
               int damage = random[pr_headattack](1, 6) * 10;
               A_PlaySound (AttackSound, CHAN_WEAPON);
               int newdam = target.DamageMobj (self, self, damage, "Melee");
               target.TraceBleed (newdam > 0 ? newdam : damage, self);
               // launch a missile
               SpawnMissile (target, "CacodemonBall");

Community guides and documentation

The ZDoom community has made some effort in creating guides designed to help people to switch from DECORATE to ZScript.

See also