ZScript_Basics

🟱 «< BACK TO START

đŸ”” « Previous: Anonymous functions đŸ”” » Next: Pointers and casting


Variables and data types

Overview

If you’ve used ACS, you’re probably familiar with variables. Variables can be defined and used in ZScript in a similar manner, but there are more data types that they can hold.

A variable is a piece of data that holds some information — a number, a string of text, coordinates, etc. When declaring variables, you give them names which you can use to reference them later.

Usually custom variables look like this:

string foo; //creates a variable 'foo' that holds a text string
int bar; //creates a variable 'bar' that holds an integer number
bool tobeornot; //creates a variable 'tobeornot' that holds a 'true' or 'false' value

Here’s a simple example of declaring a variable in a class and using it:

class SpeedyImp : DoomImp 
{
    int speedups;    //creates a variable that can hold an integer number

    Default 
    {
        health 300;
    }

    States 
    {
    Pain:
        TNT1 A 0 
        {
            if (speedups < 5)    //check if the value is less than 5
            {
                speedups += 1;    //if so, increase the value by one
                speed *= 1.2;    //and multiply imp's speed by 1.2
            }
        }
        goto super::Pain;        // go to the normal Pain state
    }
}

Note: speed is the default speed actor property, not a custom variable, you can just read and change it directly.

Whenever this Imp is hurt, it’ll increase its speed by x1.2. But this will only happen as long as speedups is less than 5—so, no more than 5 times.

A variable like that can be declared anywhere in the class but not inside the Default or States blocks. You can access it from anywhere in the class, as well as classes that inherit from SpeedyImp. This type of variables is known as fields.

A different method is declaring a variable inside an anonymous function. If you do that, that variable will exist only within that anonymous function:

class WeirdImp : DoomImp
{
    int speedups;            //this variable is available anywhere in the class

    Default 
    {
        health 500;
    }

    States 
    {
    Pain:
        TNT1 A 0 
        {
            if (speedups < 10) 
            {
                speedups++;            //++ is the same as +=1
                // Create a temporary variable 'foo' that holds 
                // a random value between 0.8 and 1.2:
                double foo = frandom(0.8, 1.2);    
                speed *= foo;        // multiply speed by that value
                scale /= foo;        // divide scale by the same value
            }
        }
        goto super::Pain;
    }
}

This weird Imp will randomly change its speed and scale (inversely related) when being hit, up to 10 times. Notice that speed is multiplied and scale is divided always by the same value: so, first a random value between 0.8 and 1.2 is selected, and then, after it’s chosen, the multiplication and division happen. So, when it’s damaged, it either becomes faster and smaller, or slower and bigger.

Variable foo in this example exists only inside that anonymous function and isn’t available anywhere else. This is a local variable.

Let’s summarize the differences between these two types:

Class-scope variables aka fields:

Local variables:

Turning variables into actor properties

You can turn a variable into a custom actor property using this syntax:

//pseudocode:
type varname;
property propname : varname;

An actual example:

class WeirdImp : DoomImp 
{
    int speedups;                    //defines variable 'speedups'
    property speedups : speedups;    //assigns the variable to a property with the same name

    Default 
    {
        WeirdImp.speedups 10;        //defines the default value for the variable
    }
}

Notes on the example:

This property will be available to the WeirdImp class, as well as to all classes inheriting from it. If you’re planning to have a lot of custom properties for all the actors in your mod, it’s a good idea to define a custom version of the Actor class, define all properties in it, and then use it as a base class for all your custom classes.

Access modifiers

Usually variables are declared according to the following syntax:

//pseudocode:
accessmodifier type variablename;

//actual example:
private int myspecialnumber;
protected string myspecialtext;

Access modifier lets you restrict access to the variable, defining what can read and change its value. The available options are:

It’s usually not something you need to worry about, but in general if you know that you’re declaring a variable that will never be (and shouldn’t be) changeable from any other class, it’s a good idea to make it private (or protected if you want it to be accessible to child classes, but not to other classes). This approach is known as encapsulation, and the gist of it is: sometimes it’s important to be sure that this data doesn’t get changed accidentally from somewhere else, so better protect it.

Accessing variables from weapon states

If you’ve defined a variable in a weapon, to access it from the weapon’s state you will need to use the invoker prefix:

class MyPlasma : Weapon
{
    int heatCounter; //this will hold the amount of heat accumulated by the weapon

    States
    {
    Ready:
        WEAP A 1
        {
            A_WeaponReady();
            invoker.heatCounter = 0; //reset the heat to 0 in Ready state
        }
        loop;
    Fire:
        WEAP B 1
        {
            A_FireProjectile("Plasmaball");
            invoker.heatCounter++; //accumulate heat when firing
            //if the heat is too high, jump to Cooldown sequence:
            if (invoker.heatCounter >= 50)
            {
                return ResolveState("Cooldown");
            }
            //otherwise continue to the next state as normal:
            return ResolveState(null);
        }
    [...] //the rest of the weapon's code
    }
}

This is required to clearly differentiate a variable defined on the weapon from variables defined on the player. Player variables, on the other hand, can be accessed by using the variable’s name directly:

//this weapon has a zoom function which slows the player down by 20%:
Zoom:
    WEAP A 1
    {
        speed = default.speed * 0.8;
        A_ZoomFactor(1.3);
    }

This is only true for the weapon states, however. If you access a variable from the weapon’s virtual function, this rule doesn’t apply.

You will find more information on accessing and manipulating data in weapon context in the Weapons, overlays and PSprite chapter.

Data types

Of course, int isn’t the only existing variable type. It’s important to have a general understanding of these data types, since actor properties and function arguments in Doom are also essentially variables and they also hold data of various types.

Hence here’s a list of various data types. You don’t need to immediately learn them by heart, but rather use it as a reference. This list might not be exhaustive.

Elements of the map itself can also be interacted with in ZScript, and as such you can get pointers to them. One commonly used way to do that is the LineTrace function that fires an invisible ray and returns a pointer to what it hits (an actor, a sector, a linedef, etc.). Also, actors contain a number of native fields that are meant to hold pointers to map elements; some of these a mentioned below.

Some of the data types that can contain pointers to map elements are:


🟱 «< BACK TO START

đŸ”” « Previous: Anonymous functions đŸ”” » Next: Pointers and casting