TUTORIAL: How To Make Your First Basic Gun

PLATFORM: ZDoom 2.5.0 or later
PROGRAMS NEEDED: Slade 3.0 or later

So you want to make your first new weapon. You've probably got some amazing Grand Designs in mind, and ZDoom can probably provide everything you need to make them work - but slow down, sometimes it can be pretty tricky to figure out what you need to do. Since you're here, you probably don't have much idea what to do with a Decorate lump (and if you do, bear with me), so let's go over the absolute basics.

First off, go get yourself a copy of SLADE 3.0. You will need it for everything related to Doom modding. You might also need to install the Visual Studio 2010 runtimes (if SLADE crashes and complains that you're missing MSVCP100.dll, you need to install them - there is a link to them on Slade's website). Go ahead and start up SLADE; you'll find yourself at the SLADE start page [see screenshot]. Click Create Zip Archive; you could easily also make a WAD, as ZDoom supports both, but PK3s are much better for organization, and it's always good to drill good working habits early on.

You should have an empty, untitled Zip archive in the window. To get started with a decorate lump, go to the menu bar and choose Archive -> New -> Entry (or the icon of the piece of paper with the E on it). SLADE will ask you what to call it; let's call it Decorate.txt (so that ZDoom knows what to do with it). Now since this "lump" (how Doom refers to individual files of data in an archive) has no actual data in it yet, SLADE will treat it as a marker. We'll need to get some text in here eventually, so click on Decorate.txt in the lump list, then in the right panel, click on "Edit as Text." SLADE's internal text editor is now open for your usage.

Our first weapon is going to be a simple automatic shotgun using built-in Doom resources, so don't worry about new graphics, sounds, or anything but code.

The basis of any Decorate code ("Actor") is the ACTOR header. This tells ZDoom what the internal name of your actor is, whether it borrows code from another actor (what's known as "inheritance"), and which ID number will refer to it (commonly called a "DoomEdNum" or Doom editor number - this is the number that editors like Doom Builder use to specify which actors will be placed in a map). For our purposes, use a header like this:


ACTOR SuperAutoShotgun : Weapon


"ACTOR" must be included in the header - this tells ZDoom that this is the beginning of a new actor. SuperAutoShotgun is the name of your actor, which must be unique (in other words, it cannot be the same name as any other existing actor, or ZDoom will complain). The ": Weapon" part means that our new gun is going to inherit from the "Weapon" class, which is necessary for ZDoom to treat it as a usable weapon.

Let's go ahead and open up the actor. On the next line, place a left brace (also called a curly bracket) to "open" your block of code. It should look like this:


ACTOR SuperAutoShotgun : Weapon
{


The next spot of code is going to define some basics about our new gun, stuff like its ammo type, how much ammo to use when firing, what message it displays upon being picked up, etc. We'll keep it simple here, borrowing some existing stuff...


ACTOR SuperAutoShotgun : Weapon
{
Inventory.PickupMessage "You got the Auto Shotgun! Boom, boom, boom."
Weapon.AmmoType "Shell"
Weapon.AmmoUse 1
Weapon.SlotNumber 3


It's not entirely necessary to indent your code, but it makes it look a lot cleaner and easier to read, so that for you and others it's easier to track down mistakes and such. The parameters here include Inventory.PickupMessage, which can be pretty much any message you want (you don't even need to worry about it going off the edge of the screen, as ZDoom will word-wrap it if it does); Weapon.AmmoType, which tells the game what item to use for ammo (in this case Shell, which is already defined in Doom); Weapon.AmmoUse, which tells how many Shells to take from the inventory when an attack action occurs; and Weapon.SlotNumber, which is how you assign your weapon to any of the number keys. There's a lot of parameters you can set using this kind of stuff - for a full list, check the ZDoom Wiki's list of Actor Properties.

There are also Flags that you can add and remove, which are similar to Properties, but are actually only on/off switches instead of numbers and strings. Let's throw one in there, on the next line:


+Weapon.NoAlert


That flag will prevent your weapon from waking up monsters when it fires. Okay, it doesn't make that much sense in a realistic context, but whatever. Flags, as stated above, are on/off switches for certain modes of behavior. To enable a flag, prefix it with a plus (+) symbol. If a flag is already set and you wish to remove it, prefix it with a minus (-). In this case, we've enabled the NoAlert function by adding +Weapon.NoAlert to the code. Again, you don't necessarily need to properly capitalize your code, but it's easier to read this way. For more flags you can use, check out the ZDoom Wiki's list of Actor Flags.

Properties can change a lot of stuff, but our weapon needs States to actually function. So on the very next line after the properties and flags, enter the word "States", then on the next line after that, open another sub block using a brace. The result should look a bit like this:


ACTOR SuperAutoShotgun : Weapon
{
Inventory.PickupMessage "You got the Auto Shotgun! Boom, boom, boom."
Weapon.AmmoType "Shell"
Weapon.AmmoUse 1
+Weapon.NoAlert
Weapon.SlotNumber 3
States
{


State blocks are the real meat of an Actor. This is where you can determine what your actor looks like, how it behaves, what it fires, how much damage it does, among many, many other things. A State block begins with the name of the state (ZDoom has a handful of built-in state names that you will need to use), then a line with the name of the sprite to use, the index letter indicating which frame of the sprite to use, the duration of that frame in "tics" (roughly one thirty-fifth of a second - Doom's internal clock runs at 35 frames per second), then any Action Functions you wish to happen at that frame. As a very basic example, let's first make our Ready state, or what happens if your gun is equipped and not doing anything. Stick this immediately after your opened States block:


Ready:
SHTG B 1 A_WeaponReady
Loop


A side note: for purposes of this tutorial, I'm going to be using only sprites that are contained with Doom2.wad; if you want to know how to import your own graphics for use in Doom, have a look at New Sprites and How They Work For Weapons after you're done with this tutorial.

The line "SHTG B 1 A_WeaponReady" is a complete instruction for the game engine. When ZDoom encounters this line, it will display the sprite SHTGB0, for exactly one tic, and execute the A_WeaponReady action function, which tells ZDoom to check if the player has done anything with the weapon controls such as clicked the Fire button or switched weapons. By default, A_WeaponReady also tells ZDoom to enable weapon bobbing, so the weapon does its little bouncy dance when you're running around. (In this case, we've chosen the B frame of the shotgun instead of the A frame; you'll see why later.)

A quick detour in the lesson here: Doom stores its sprites with a very specific naming structure. For example, we're using Doom's shotgun graphic, "SHTGB0". "SHTG" is the name of the sprite, and is the first thing we see in your new Ready state. Sprite names may only be four characters long; ZDoom will crash on startup if you try to use a shorter or longer name. The fifth character, the "B" in this case, is which index letter the sprite is for. Generally, a weapon will use the same name for all sprites, with an index ranging from A to Z (there are some other characters that can be used but I don't recommend it). The last character, in this case a zero, indicates which angle the graphic is intended for.

Angles are meaningless for weapon sprites, since they are literally just pasted on to the view, so weapon sprites are named with angle 0, which tells Doom that this graphic will be displayed regardless of which direction the item is facing. Angles are used for monsters, decorative items, and other things that need to actually appear to face in a direction; more information on how sprite naming works can be found at the ZDoom Wiki page on creating new sprite graphics. In summary, the graphic named "SHTGB0" refers to the sprite SHTG (sprite name), frame B, angle 0 (all angles).

So back to our new state.

You'll notice that we called this state "Ready" and made it loop. Ready is the state ZDoom goes to when a weapon is equipped and ready to fire. This is just one of four states that are required to be in your weapon for it to be functional. The other three are Select, Deselect, and Fire.


Select:
SHTG A 1 A_Raise
Loop
Deselect:
SHTG A 1 A_Lower
Loop


The above two states are also part of the inner workings of ZDoom, and absolutely must be present in your weapon code. (Note that there are some specific exceptions to this for more advanced techniques, like instant weapon switching, custom raise/lower animations, etc.; they go beyond the scope of this tutorial.) The Select state is what is displayed when you select your weapon. It has to have at least one A_Raise in it (which animates the weapon rising on screen and then jumps to the Ready state once it's done) and it must loop. Same with Deselect. That's almost all of your weapon's functions; now we only need a Fire state. Here, we'll see the inner workings of how Decorate handles animation.

First, add a Fire: line, similar to Select and Deselect. On the next line, we'll make this be the actual Fire action - have it use the A frame of SHTG, have it last 7 tics, and after that, we'll work a bit of magic with another Action Function, the ever-important A_FireBullets.


SHTG A 7 A_FireBullets(5.5, 3, 6, 8, "BulletPuff", 1)


A_FireBullets can't be used on its own like A_Raise and A_Lower. It requires a set of six parameters, separated by commas. (As with the indents, you don't necessarily need to put spaces between the commas, but in my opinion, it makes it more readable and you'll probably be thankful you did later down the line.) The parameters for A_FireBullets are as follows:

* Horizontal accuracy, in degrees. This number determines how wide of an angle for Doom to use when randomly determining where your bullets will fly. Larger numbers mean the bullets will strike further left or right.
* Vertical accuracy, also in degrees. Similar to horizontal, this affects how high or low your bullets will hit.
* Number of bullets to fire. Since this is a shotgun, we will tell it to fire six bullets in one shot. The accuracy numbers will ensure that this turns into a nice spread of pellets.
* Damage per bullet. Doom's damage algorithm for bullets will multiply your damage by a random number from 1 to 3, so a pellet dealing 8 damage can deal either 8, 16, or 24 damage on its own. With six pellets in the shot, this shot can deal anywhere from 48 to 144 damage, but usually more towards the middle of that range, as Doom's random number generator tends to favor averages. If that sounded too complex and in-depth, a simpler explanation is that it will likely take one or two shots from this auto shotgun to kill a player at 100 health and no armor.
* Puff actor. This parameter tells ZDoom what actor to spawn if the bullet hits a wall. In this case, use BulletPuff, Doom's default smoke effect.
* Use ammo. The sixth and final parameter is what's called a "boolean", a number that can either be 1 or 0, with 1 meaning yes, and 0 meaning no. In this case, 1 means "Yes, use ammo when firing," and 0 means "No, do not use ammo when firing."

Alright. So that should do it for this line. End the Fire state with Goto Ready (this is what's called a "state jump" - these can be really useful!), and close up both of the open blocks of code using a pair of closing braces. Your block of code should look a bit like this:


ACTOR SuperAutoShotgun : Weapon
{
Inventory.PickupMessage "You got the Auto Shotgun! Boom, boom, boom."
Weapon.AmmoType "Shell"
Weapon.AmmoUse 1
+Weapon.NoAlert
Weapon.SlotNumber 3
States
{
Ready:
SHTG B 1 A_WeaponReady
Loop
Select:
SHTG A 1 A_Raise
Loop
Deselect:
SHTG A 1 A_Lower
Loop
Fire:
SHTG A 7 A_FireBullets(5.5, 3, 6, 8, "BulletPuff", 1)
Goto Ready
}
}


Make absolutely sure you remember to close any blocks you open, or ZDoom will not know that you've finished writing your actor.

Now that we're done, click on Save Changes in the bottom right corner of the SLADE window (to save the lump you're currently working on), then click on File -> Save As from the menu bar (or the green disk icon on the toolbar), and save your archive somewhere. Call it something you'll remember later; like Tutorial-SuperAutoShotgun.pk3 (even though a pk3 is just a renamed zip, calling it a pk3 indicates to players that they should be loading it in ZDoom instead of extracting it - this will save many people a lot of confusion!). Go ahead and launch ZDoom with your file, give yourself all weapons with the IDFA cheat code, then press 3 until you've selected the shotgun that appears to be held upright. Remember how we made the ready state show sprite B instead of A? That's so we could avoid confusing our new shotgun for the original one.

Congratulations, you should now have a ridiculously powerful automatic shotgun at your disposal. Cool, eh? Well, it's not perfect; you'll notice it may be missing a few things...for example, the graphic really doesn't do anything yet, it doesn't kick back like a gun ought to, it doesn't spit fire when it shoots, and most importantly, it doesn't even have a firing sound.

The firing sound is easy to add. A_FireBullets automatically plays the defined AttackSound. AttackSound is another Actor Property, so it should go with the rest of the properties at the top of your actor with all the others. Set your attack sound to weapons/shotgf. That should look like this:


AttackSound "weapons/shotgf"


When fired, your shotgun should now play an appropriate shotgun sound. Go ahead and save the Decorate lump, save the archive (with the blue floppy disk icon, or File -> Save), and test your weapon again. When you're satisfied that it works, pop on back.

While this shotgun now at least sounds like a gun, it does still lack that ever-important fire effect, the muzzle flash. This is what ZDoom's built-in Flash state is for: when called, ZDoom overlays a muzzle flash on top of your gun and lights the room up for a small fraction of a second. To do this, go ahead and add a Flash: state to your weapon, and structure it like this:


Flash:
SHTF A 2 BRIGHT A_Light2
SHTF B 2 BRIGHT A_Light1
TNT1 A 1 A_Light0
Goto LightDone


Now let's break this down piece by piece. Flash is, of course, the name of the state; it must be called Flash, as that's where the "magic" comes from (ZDoom treats the Flash state differently from other states). In the first line, SHTF A 2, you'll see the "BRIGHT" keyword, which tells ZDoom that this frame should be displayed at full brightness, regardless of how dark the area is. A_Light2 is one of the built-in light functions; this will make the room brighten up a little bit. There are three light functions available: A_Light0, A_Light1, and A_Light2. These three functions will set the surrounding light to be brighter or darker. If you make a weapon lighten the area with A_Light2 or A_Light1, be sure to reset it to normal with A_Light0. In this case, we've created a neat little transition from light to dark by going from 2 to 1 to 0.

In the third line, you'll notice we've used the sprite TNT1A0 - this is a special sprite that displays absolutely nothing when it's used. ZDoom treats this sprite differently from other sprites; when it's told to display TNT1A0, it actually will not draw anything at all. Unlike using a blank graphic, ZDoom will not even waste the time checking over every pixel of the graphic for transparent areas; it just doesn't draw anything at all. So if you need your graphic to be invisible for any reason, use TNT1A0.

Also notice that, unlike the Fire state which ends with Goto Ready, the Flash state ends with Goto LightDone. This is because the Flash state is handled in a special way: when called, ZDoom overlays Flash's sprites over the top of the weapon graphic and runs its states separately from the weapon. The Goto LightDone command causes the Flash graphic to completely disappear, which is good because we don't want a plume of fire constantly sticking to the end of our weapon, wasting render time and potentially slowing everything down. (Incidentally, Goto LightDone seems to pretty much do the same thing as ending the Flash state in a Stop command; I'm told, however, that LightDone is probably used for a reason, and I'm not about to tempt fate any longer than I have to.)

The Flash state does not work by itself. It needs to be called using another Action Function in the Fire state: A_GunFlash. This pretty much does what it says on the tin: when called, it makes the Flash state play through its frames to the end. We need to stick it in the Fire state somewhere, but we'll need another line, since you can't have more than one action function on the same line. So here's a little coding trick: zero-duration states.

In ZDoom, if a state is told to have a duration of 0 tics, that state will happen instantly, with the game executing it and not waiting to go to the next line. To make several actions occur at once, you would use a series of states with a duration of 0. There are some pitfalls to this, though; if ZDoom encounters an endless loop consisting of nothing but zero-duration states, the game will freeze and crash. This is what is called a runaway loop; ZDoom is trying to execute all of the zero-duration states before it renders the next frame of gameplay, and if it never runs out of zero-duration states to execute, the game can never continue. The result: the game completely stops functioning.

Go ahead and add a zero-duration state to the beginning of your Fire state. It does not matter which sprite and index you use (SHTGA0 will probably work), but make its duration 0 and give it the action function A_GunFlash.

Your Fire and Flash states should now look similar to this:


Fire:
SHTG A 0 A_GunFlash
SHTG A 7 A_FireBullets(5.5, 3, 6, 8, "BulletPuff", 1)
Goto Ready
Flash:
SHTF A 2 BRIGHT A_Light2
SHTF B 2 BRIGHT A_Light1
TNT1 A 1 A_Light0
Goto LightDone


Save and test your file. Cool, you made your shotgun spew flames from its barrel and make an appropriate boom noise. Now, we'll make it an item that you can pick up and we'll also give it a bit more kick.

Making your weapon into a pickup only requires that we give it an extra state (the Spawn state) and some extra actor properties to tell ZDoom what sound to play when it's picked up, and how much ammo it should include. We'll take care of the new state first. Call this state Spawn, and give it only one line: the sprite is SHOTA0, it should last -1 tics (-1 means the state will last infinitely; this requires less processing by the engine than giving it a duration of 1 and making it loop), and that's it. End the Spawn state with Stop (since the only state lasts forever, it will never reach the Stop line, but it's always good to tie up your loose ends, so add the Stop anyway).

Since it's not likely you're adding this to a map at this early stage, we'll instead make our shotgun replace an existing Doom weapon: the Chainsaw. This makes it very, very easy to test, as in Doom 2, the chainsaw is located directly next to the start of the game. Go to your ACTOR header and add "replaces Chainsaw" to the end. "Replaces" is a special keyword used in place of the editor number (which we didn't use last time) that tells ZDoom to spawn your item in place of the item you specify. In this case, ZDoom will no longer spawn Chainsaws, but instead will spawn your auto shotgun.

Your ACTOR header should look like this:


ACTOR SuperAutoShotgun : Weapon replaces Chainsaw


And your Spawn state should look like this:


Spawn:
SHOT A -1
Stop


Save and test the file (run it with Doom 2), turn left and check out the chainsaw. It should now be a shotgun. But it's not quite right: you can't select it! This is because it does not currently give you any ammo when you pick it up. If you were to find some shotgun shells elsewhere in the level, you could then select the auto shotgun, but we're an impatient bunch and we want to use it RIGHT NOW, so quit out of the game and get back to your code.

The one line we need to add is another Actor Property: Weapon.AmmoGive. For now we'll say the auto shotgun should give 16 ammo when picked up.


Weapon.AmmoGive 16


Save and test again. Grab the shotgun and lay waste!

Okay, so now you've got a weapon in the game that can be picked up and fired. You're all done, right? Not quite. We've still got some changes to make to give this thing a little polish, otherwise if you were to release this thing (please don't release it...), you would probably get some negative feedback, as the animation isn't very good, it's too powerful, and it eats through your entire stock of shotgun shells in about 15 seconds flat.

Firstly, let's make the fire animation a little more interesting by adding some visual kick. The gun isn't just going to stay still when it fires, so we'll make it kick backwards every time it shoots. This will be handled in your Fire state. Let's learn about the Offset keyword.

Remember the BRIGHT keyword in your Flash state? This isn't an action function, it's a "modifier" to that state that goes between the state's duration and its action. Offset works similarly, but it takes two parameters: horizontal and vertical position. Offset uses an "absolute" value; whatever parameters you feed into it will be used to tell ZDoom exactly where on screen the graphic will go.

For purposes of this exercise, reduce the duration on your A_FireBullets state from 7 to 1. We'll take up the other six tics with a simple little animation.

Offsets can be defined once per state, so we'll need to add a few extra lines to the Fire state to pull this off. All of these lines will be SHTGA0, will last 1 tic each, and will each have a unique Offset line.

It should be noted that the "default" values for Offset are 0, 32. Using 0 on either parameter tells ZDoom to keep the previous offset; by default, the weapon stays centered vertically, and 32 pixels up the screen. For the first parameter (the X axis), increasing it will shift the weapon to the right, while decreasing it shifts the weapon to the left (use negative values like -10 to shift it further left). For the second parameter, increasing will shift it down, while decreasing it shifts it up. To restate, your weapon will start off at position 0, 32.

Apply your Offset keywords like so:


SHTG A 1 Offset(0,44)


Your Fire state will probably look something like this when you're done:


Fire:
SHTG A 0 A_GunFlash
SHTG A 1 A_FireBullets(5.5, 3, 6, 8, "BulletPuff", 1)
SHTG A 1 Offset(0,40)
SHTG A 1 Offset(0,48)
SHTG A 1 Offset(0,44)
SHTG A 1 Offset(0,40)
SHTG A 1 Offset(0,37)
SHTG A 1 Offset(0,35)
Goto Ready


There's a subtle bit of simple math going on with how I organized these offsets. If you were to watch a video of a shotgun being fired in slow motion, you will probably notice how when the shot is fired, the weapon suddenly lurches backwards, then slowly reverses direction and moves forward again. To replicate this effect, we use large offsets (moving the weapon down 8 pixels per tic, twice), then on the way back up, we use smaller ones (raising the weapon back up 4 pixels, then another 4, then 3, then 2). Don't worry if you didn't understand much of that; it may take some trials, errors, and experimentation to really get a feel for what works and what doesn't.

Save and test your file again, then let loose with your gun. That thing's bucking like a mad bull!

And yet, as awesome as this gun is, it's still unfairly powerful; you could probably take down a Baron of Hell in a few seconds with this, considering it's doing roughly 100 damage per shot, and firing five shots per second. We need to make these numbers just a bit less ridiculous, so it's time to introduce gameplay balance.

The first thing we can do is reduce how fast the weapon fires. Now, there's not a concrete "fire rate" parameter we can set; your weapon's rate of fire is entirely based on the length of its Fire state in tics. You can gauge your weapon's rate of fire by calculating how many tics are in your Fire state, then dividing 35 by that number. In this case, 35 tics per second divided by 7 tics per shot equals 5 shots per second - that's a bit ridiculous for a shotgun (okay, so some real-world shotguns can achieve that rate of fire, but bear with me, we're trying to make it so Hell's minions can stand some semblance of a fighting chance!).

You can slow down the fire state a little by adding a couple more tics. Stick a single line at the end of the Fire state, with the frame SHTGA0, lasting 4 tics. That'll slow it a bit, but it's also doing a bit too much damage. Try messing with the number of pellets and the amount of damage per pellet in your Fire state (for a weapon replacing the Chainsaw, probably 8 pellets for 4 damage each). That'll make it a little more normal, and it will probably take a couple shots to take down a Pinky Demon. Lastly, go ahead and get rid of +Weapon.NoAlert, so that you can't sneak around levels with a loud gun anymore (which, admittedly, was a bit silly, but heck, it's a good way to learn about how flags work).

To sum up, your Decorate lump should look a bit like this:


ACTOR SuperAutoShotgun : Weapon replaces Chainsaw
{
Inventory.PickupMessage "You got the Auto Shotgun! Boom, boom, boom."
Weapon.AmmoType "Shell"
Weapon.AmmoUse 1
Weapon.AmmoGive 16
Weapon.SlotNumber 3
AttackSound "weapons/shotgf"
States
{
Spawn:
SHOT A -1
Stop
Ready:
SHTG B 1 A_WeaponReady
Loop
Select:
SHTG A 1 A_Raise
Loop
Deselect:
SHTG A 1 A_Lower
Loop
Fire:
SHTG A 0 A_GunFlash
SHTG A 1 A_FireBullets(5.5, 3, 8, 4, "BulletPuff", 1)
SHTG A 1 Offset(0,40)
SHTG A 1 Offset(0,48)
SHTG A 1 Offset(0,44)
SHTG A 1 Offset(0,40)
SHTG A 1 Offset(0,37)
SHTG A 1 Offset(0,35)
SHTG A 3
Goto Ready
Flash:
SHTF A 2 BRIGHT A_Light2
SHTF B 2 BRIGHT A_Light1
TNT1 A 1 A_Light0
Goto LightDone
}
}


Go ahead and test your file one last time. Play through a few levels with it, see what it's like. Now that it's a little less unfair, the weapon should be pretty much complete. Congratulations, you are now an Amateur Gunsmith.