ReX's Nexus: Creating Custom Items for ZDooM

Using DECORATE to Create Custom Items

ZDooM (and its derivatives GZDooM & SkullTag) allows the creation of items that look and behave completely differently from those in "vanilla" DooM. These new items, known as "actors", need to be defined; this is accomplished via a text-based "lump" (name for any entry in a DooM wad) known as DECORATE. The purpose of this article is not to provide an introduction to defining custom items; rather, it highlights some lessons that I recently learned while creating my own custom items. For information on creating DECORATE definitions, refer to these sources:

DECORATE-related Tutorials

New User's Guide to Editing with DECORATE

Creating Non-interactive Decorations

DooM World Forum Discussion on DECORATE

A. Power-Ups, Inventories, and Hubs

It turns out that many power-ups can't be carried from one map to another in a hub. For example if you want the player to pick up a radiation suit in one map and carry it for use in another map in the hub, the "standard" DooM radiation suit will not work. The item is activated as soon as you pick it up; but more importantly, as soon as you teleport into a new map you lose the power-up, even if there's plenty of time left on the item. In my opinion the biggest oversight in ZDooM's definitions in this regard, is the lack of portability of a computer map. You can pick up a computer map on one map in a hub and find that you've "lost" it when you travel to another map. [To be fair, however, when you return to the map in which you picked up the unit it is usable again.] So, in this section I will outline the steps to make a radiation suit that can be carried to, and used in, any map within a hub.

First, here's an example of a radiation suit that can be picked up in a map and put into an inventory for use in that or any other map in a hub.

////////////////////////////////////////
// Custom Radiation Suit //
////////////////////////////////////////
ACTOR SlimeSuit : PowerupGiver 20006
{

States
{
Spawn: }
}

So let's examine the definition above, which is virtually the same as the one for DooM's radiation suit:

B. Creating A Custom Berserk

Most DooM items are automatically activated as soon as you pick them up. This, of course, defeats the purpose of having an inventory in which you can store items for later use. In addition, some items change the player's state upon being picked up; the Berserk is an example of such an item. When you pick it up your health automatically maxes out at 100 HP, your weapon is lowered and your fist is readied, and your punches are given greater strength. You don't want these state changes to occur, but you want to be able to call them up when you're ready to use this item. So here's an example:

///////////////////////////////
// Custom Berserk //
///////////////////////////////
ACTOR Pugilist : CustomInventory 20011
{

States
{
Spawn: Use: }
} C. Creating A Custom SoulSphere

Now let's look at a slightly more complicated example of an item that gives the player a powerup but does not have a "Use" state at all. The SoulSphere gives the player 100 HP but allows the maximum health to exceed 100 HP to a maximum of 200 HP. You want to be able to retain this property, but make it usable at will. So here's an example:

////////////////////////////////////
// Custom SoulSphere //
////////////////////////////////////
ACTOR BlueSphereHealth : Health
{

}
ACTOR BlueSphere : CustomInventory 20014
{ States
{
Spawn: Use: }
} D. Creating An Inventory Item That Is Usable Only After One or More Conditions Are Met

In an adventure scenario, and particularly within a hub, it may be necessary to prevent a player from using an inventory item prematurely. For example, let's say the player needs a radiation suit in Map05 of a hub in order to make progress through a nukage area, but the suit is available only in Map02. If the player picks the suit up in Map02 and accidentally uses it before getting to Map05, s/he is essentialy stuck in the game, as the nukage area cannot be traversed without the suit. So here's an example of how to set up the actor in such a manner that the suit (called a "special" SlimeSuit in this tutorial) cannot be used before reaching the nukage area:

//////////////////////////////////////////////////////
// Custom Radiation Suit (Special) //
//////////////////////////////////////////////////////
ACTOR SlimeSuitProtection : RadSuit
{

}
ACTOR SlimeSuit : CustomInventory 20006
{ States
{
Spawn: Use: FailState:
}
} So now let's take a look at the "switch" that determines when the SlimeSuit can be activated - Script 666. Essentially, we're setting up the condition like a reverse switch, with the condition indicating that the player is not at the area where the SlimeSuit is required. So long as the condition is true the return value will be 0, and the expression will jump to the fail state. As soon as the player arrives at the appropriate area, the return value changes to 1, the JumpIf condition becomes false, and the definition jumps to the A_GiveInventory frame, which activates the SlimeSuit.

Because it serves as a switch, Script 666 will look different in maps where SlimeSuit use is "denied" and in maps where it is "allowed". [As an aside, the script number is arbitrary. You can pick any number, so long as it's not already in use in all of the maps. A suitably high number (e.g., 999) would work just as well, as it's unlikely that a map has that many scripts - assuming they are consecutive.] First, a look at a map where SlimeSuit use is denied:

An ACS_ExecuteWithResult script must always be accompanied by a script with SetResultValue. The Special SlimeSuit definition is running the ACS_ExecuteWithResult script, which in turn is looking for the result from a script that returns a value via a SetResultValue. This value can either be a numeric value or a True/False value. In our example it's a numeric value, namely 0. Remember, you want the A_JumpIf expression to fail in this instance, which means the value needs to be less than 1. Any time you try to "use" the SlimeSuit in your inventory, ACS_ExecuteWithResult runs Scrpit 666, gets a returned value less than 1, jumps to the FailState state, and prevents the item from being used. Now lets look at the map where SlimeSuit use is allowed:

Pretty self-explanatory - by setting the return value to 1, you are now allowing the definition to jump to the instruction that activates the SlimeSuit. This will allow you to use the SlimeSuit as soon as you enter the map. But what if you didn't want the player to use the SlimeSuit right away upon entering the map? You'd have to create a switch within a switch that would remain turned off until the player reached the proper point in the map. You'd set this up in two steps, as follows:

Script 9 is set up to throw the first switch, allowing Script 666 to throw the second switch. [Script 9 also has a message, but that's entirely optional.] Script 9 can either be activated by pressing an actual switch, or by crossing a linedef, or by entering a sector, or by any other means by which a script may be triggered. Until Script 9 is triggered, the variable 'Map05' will not be switched on, and Script 666 will return a value of 0. As soon as the player reaches the designated point on the map and triggers Script 9, Script 666 will return a value of 1. This will allow the DECORATE definition to properly complete the "Use" state and activate the SlimeSuit.

Confused yet?