đą «< BACK TO START
đ” « Previous: Anonymous functions đ” » Next: Pointers and casting
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:
int
this is 0, for string
itâs an empty string), and then you have to modify it somewhere (for example, inside an actor state). In the example above speedups
is initially equal to 0 and itâs increased by 1 when the Imp enters its Pain state for the first time and calls speedups++
.speedups += 1
(or speedups++
, which is the same), it increases by 1 and will keep that value throughout whatever the Imp does.Local variables:
double foo = frandom(0.8, 1.2)
will create a temporary variable foo
equal to a random value between 0.8 and 1.2 every time the Pain state sequence is entered. (Note that actors can enter the Pain state multiple times per tic when hit by multiple attacks, such as a shotgun blast.)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:
Default {}
block (since youâre giving it a default value).WeirdImp.speedups
and not just speedups
in the Default {}
block.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.
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:
protected
â this variable can be changed only from this class and classes inheriting from it but it canât be changed from anywhere elseprivate
â this variable is only available to this class and nothing elseItâ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.
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.
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.
int
â holds an integer number (such as 1, 2, 3, 10, 500, etc.)
damage
and health
. Thatâs why projectiles canât deal 2.5 points of damage, only 2 or 3.double
â holds a float-point number (such as 2.5). Note that float
is also an existing type, but double
is used in ZScript instead because itâs essentially the same thing but it has higher precision. Thatâs usually not something you need to worry about as a user, just remember that a float-point number should be stored in a double
variablle.
angle
, height
, radius
, speed
, alpha
, bouncefactor
are all doubles.bool
â a boolean variable holds a true
/false
value. You can set and check it against true and false, such as if (foo == true)
(to check if itâs true) and foo = false
; (to set foo to true).
bFLAGNAME
. You can change the majority of flags on the fly by using those names; for example, you can do bSHOOTABLE = false;
to suddenly make an actor unshootable.
if (foo == true)
is simply if (foo)
. And if (foo == false)
can be replaced with if (!foo)
(!
means ânotâ and inverts any check)."True"
is a string holding the text âTrueâ, while true
is a boolean value.false
, while a non-zero value is true
. However, while if (mybool > 0)
is technically correct, you shouldnât use this syntax because youâll just confuse yourself (and others who might be reading your code). Whenever possible, always use variables in such a way that their type is obvious from just looking at them.String
â holds case-sensitive text (such as âAdamâ)
string foo = "Bar";
creates a variable foo that holds the text âBarâ.name
â holds case-insensitive text (i.e. âadamâ, âAdamâ and âADAMâ are all the same)
name
s is possible with single quotation marks. You can still use double quotes, but itâs a good idea not to do that, so that when you look at the variable, youâll immediately know itâs a name
and not a string
(same as you should do if (mybool == true)
not if (mybool > 0)
. In fact, for custom variables in the majority of cases itâs better to use a name than a string, since there are relatively few applications for case-sensitive text.Bouncetype
.Vector2
â holds two float-point values that are accessible with .x
and .y
postfixes. So, if you declare vector2 foo
, you can read its components with foo.x
and foo.y
. Vector2 type is used for things like position or velocity in 2D space (i.e. pos.x
and pos.y
or vel.x
and vel.y
respectively). The contents of this type of variable is two float-point values enclosed in parentheses, such as (15.0, 14.2)
.Vector3
â similar to vector2, but holds 3 values accessible with postfixes .x
, .y
and .z
. Actors use vector3 pos
to store their XYZ position in a map, and vector3 vel
to store their velocity alongside XYZ axes.
(15, 12.3, 0)
that means itâs located 15 units to the east, 12.3 units to the north and 0 units vertically away from the mapâs origin point. Since all coordinates are relative to that (0,0,0) origin, this makes all coordinates vectors; a vector in this context is basically a line that starts at (0,0,0) and ends wherever the object is.(15, 0, 1.2)
, every tic it moves 15 units north, 0 units east/west and 1.2 units upward. Basically, actor movement is their vector3 of velocity being constantly added to their vector3 position.class<Actor>
â a variable that holds a actor class name.
<Actor>
part can be substituted for something else, if you want to limit this variable to being able to hold a name of a specific subclass, for example Class<Ammo>
.name
. Class<Actor>
isnât just a line of text; it also contains information that tells the game that this is, in fact, an existing actor class. In contrast, a name
simply contains text and nothing else.Actor
â a variable that holds an instance of an actor (i.e. a pointer to it). Itâs not a name of an actor class, but a pointer to a specific actor that exists in the level. Learn more in Pointers and Casting.StateLabel
â holds a reference to the name of a state sequence, aka state label, such as âReadyâ, âFireâ, etc. Note, state labels are not strings, theyâre a special type of data, and in fact strings canât be converted to state labels or vice versa. StateLabel
type is commonly used as function arguments: for example, in the A_Jump
function the first argument is an integer number defining the jump chance, while the second argument is a state label that tells the state machine which state sequence to go to.state
â holds a reference to a state. Not to be confused with state sequences or state labels. (See State control for details on the differences.) For example, doing state st = FindState("Ready"),
creates a variable st
that holds a pointer to the first state in the Ready
sequence.
curstate
: it holds a pointer to the state the actor is currently in. It can be used in combination with InStateSequence
function to check which state sequence the actor is in with if ( InStateSequence(pointer.curstate, pointer.FindState("Label") )
, where poitner
is a pointer to actor and âLabelâ is a state label.SpawnState
which holds a pointer to the first state in the Spawn sequence.SpriteID
â holds a reference to a sprite. All actors have sprite
, which is a SpriteID
-type variable that always holds their current sprite; you can use it to make one actor copy the appearance of another by doing pointer1.sprite = pointer2.sprite
. (Note that âappearanceâ includes many more characteristics, such as scale, alpha, renderstyle, etc.)
SpriteID
isnât a sprite name; instead itâs a special internal identifier of a sprite. Converting a sprite name to a SpriteID requires GetSpriteIndex()
function. E.g.: sprite = GetSpriteIndex("BAL1")
will set the current actorâs sprite to BAL1, the sprite used by Impâs fireballs. Note that you can also modify frame
to modify the frame letter of a sprite, where 0
is A
, 1
is B
and so on.SpawnState.sprite
in any actor to get the first sprite used in the actorâs Spawn state sequence. In this sense sprite
is identical to curstate.sprite
.TextureID
â holds a reference to an image (any kind of image that exists in the loaded archive), but not a sprite that can be used in an actor (in contrast to SpriteID). Similarly to SpriteID, this isnât a name of the texture but rather an internal identifier. You normally wonât need this in actors; instead this is commonly used in UI and HUDs.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:
line
-type BlockingLine
pointer that contains a pointer to a linedef the actor crosses or hits (for example, projectiles get it when they explode due to hitting a wall).sector
type variable cursector
, which holds the sector the actor is currently in.SecPlane
-type variables: floorplane
and ceilingplane
. 3D floors, similarly, have bottom
and top
pointers to their bottom and top planes.đą «< BACK TO START
đ” « Previous: Anonymous functions đ” » Next: Pointers and casting