TUTORIAL: Altfires, Projectiles, and Accuracy

In the past couple of tutorials, you've learned the absolute basics and common knowledge stuff that you need to make a new weapon. Well, as tutorials go, I didn't really teach you everything; this tutorial will cover a number of other stuff that really doesn't warrant a separate article by itself.

Refire Inaccuracy

In How to Make Your First Basic Gun, we started off with a shotgun, which is always inaccurate due to the way Doom handles bullet spread. Things get slightly trickier when you're dealing with a gun that only fires one bullet at a time, like a pistol or machine gun. Let's look at an example fire state from a chaingun replacement:
Fire:
CHGG A 2 BRIGHT A_FireBullets(5,5,1,7,"BulletPuff",1)
CHGG A 2
CHGG B 2 BRIGHT A_FireBullets(5,5,1,7,"BulletPuff",1)
CHGG B 2
Goto Ready

If you were to try this code as-is in ZDoom, you would find that despite there being a large bullet spread in A_FireBullets, this chaingun is always perfectly accurate and could probably hit the broad side of a barn for as long as the trigger is held, every single time. Sometimes that's what you want, but in this case we obviously want there to be some spread. Well, the reason this code doesn't work as it stands is because of something called refire inaccuracy. This term isn't quite self-explanatory on its own, so let's break that up.

A refire refers to the action function A_Refire, which should go at the end of any automatic weapon's Fire state. Refire checks to see if the player still has the Fire button held down, and if so, it repeats the Fire state from the beginning. The inaccuracy part comes in when the Fire state is repeated. A_Refire does an extra thing when it sends the gun back to its Fire state: any A_FireBullets pointers that are set to have bullet spread and fire only one bullet will actually spread. In other words, the first shot is always perfectly accurate, but keeping the trigger held down will cause the bullets to start spreading.

Try adding an A_Refire to the end of this fire state:
Fire:
CHGG A 2 BRIGHT A_FireBullets(5,5,1,7,"BulletPuff",1)
CHGG A 2
CHGG B 2 BRIGHT A_FireBullets(5,5,1,7,"BulletPuff",1)
CHGG B 2
CHGG A 1 A_Refire
Goto Ready

And the weapon will only be inaccurate if you are not firing in short, controlled bursts.

There's an alternative way to get around this effect, too. If you don't want the first shot to always be accurate, for example if your weapon is a pistol or other single-shot weapon, tell A_FireBullets to fire -1 bullets instead of 1. Like this:
A_FireBullets(5,5,-1,7,"BulletPuff",1)
With that tiny little change, your gun will always be subject to bullet spread. (An extra little tip: if you want your pistol to not be automatic, i.e. if you want the player to have to press Fire once for every bullet, just add the flag +Weapon.NoAutoFire and remove the A_Refire pointer.)

Footnote: It should be noted again for clarity that if A_FireBullets is firing more than one bullet per shot, the shot will always be inaccurate, without the need for A_Refire.

Alternate Fire

A lot of modern games have multiple ways for you to fire your weapon. This neat little idea dates back as far as 1994's Star Wars: Dark Forces, and can add great variety to your arsenal. Adding an alternate fire mode to your weapon is actually really easy, since ZDoom has native support for it. There are only two things you need for an alternate fire: you need to specify an ammo type and usage for it, and you need to give it its own fire state.

First, the ammo type needs to be defined with these actor properties:
Weapon.AmmoType2 "Cell"
Weapon.AmmoUse2 10
Weapon.AmmoGive2 20


Then to define the alternate fire's firing state, you need only do what you'd do for a Fire state, except call said state AltFire. If we wanted to make the aforementioned chaingun fire faster as its alternate fire, we'd do something like this:
AltFire:
CHGG A 1 BRIGHT A_FireBullets(8,8,1,7,"BulletPuff",1)
CHGG A 1
CHGG B 1 BRIGHT A_FireBullets(8,8,1,7,"BulletPuff",1)
CHGG B 1
CHGG A 1 A_Refire
Goto Ready

And there you have it.

Projectile Weapons

Projectiles don't work the same way as bullet attacks. The projectile must be defined as its own actor, with its own states, radius and height properties, etc. Here is an example projectile:
actor AwexomeRocket
{
Radius 11
Height 8
Speed 20
Damage 20
Projectile
SeeSound "weapons/rocklf"
DeathSound "weapons/rocklx"
Obituary "$OB_MPROCKET" // "%o rode %k's rocket."
States
{
Spawn:
MISL A 1 bright
loop
Death:
MISL B 8 bright A_Explode(128,128)
MISL C 6 bright
MISL D 4 bright
stop
}
}

And in true tutorial fashion, we'll break that down bit by bit.

Projectiles do not need to inherit from anything. Unlike weapons, there is no parent class that needs to be used for proper functionality. So in this case, it's just ACTOR AwexomeRocket.

Radius 11
Height 8
Speed 20
Damage 20

These lines determine the physics of your projectile. Radius and Height are especially important - they roughly correspond to the dimensions, in pixels, of the projectile's graphic, and will determine how "large" the projectile is in the game. This doesn't directly affect the visible size of the graphic, but rather, its bounding box; the larger the box, the easier it is for the projectile to hit a target. Try not to make the projectile too large, or your projectile will be prone to hitting walls in front of (or even just next to) the player. We also have Speed and Damage, which should be fairly obvious as to their functions.

A word on damage: Projectile damage is your damage number multiplied by 1d8 - this means that the number you specify for damage is the absolute minimum damage your projectile will do; the number is multiplied by a random number from one to eight, which is the "final" damage dealt. In other words, this rocket can do as much as 160 HP of damage in one hit. If you add the actor flag +StrifeDamage, projectile damage is instead 1d4, resulting in smaller numbers overall, but less deviation, so your rocket does damage more consistently (but you will need to increase the number to compensate). It is also possible to define your own damage formula from scratch by putting your damage number in parentheses. So if you wanted to recreate Strife's damage formula, your Damage property would look like this:

Damage (20*random(1,4))

You can also use parentheses to ensure that a projectile always does the same damage every time:

Damage (40)

Up next is the single most important part of a projectile actor:
Projectile
This is a very special actor flag. This tells ZDoom to treat this actor as a projectile instead of a monster or decoration. In reality, it actually sets a few flags automatically, such as +NoGravity, +Dropoff, +Missile, and others, so that the projectile does not do dumb things like refuse to go over long drops or crash into invisible lines. After setting Projectile, you can actually negate flags to add special effects. For example, if you wanted this rocket to be a grenade instead, you would follow Projectile with this flag:
-NoGravity
Which would then make the rocket drop to the ground when fired. Moving on...

SeeSound "weapons/rocklf"
DeathSound "weapons/rocklx"
Obituary "%o is hit in the groin by %k's rocket."

These are fairly important as well. SeeSound is the sound your projectile will make when it is fired. Unlike with bullet-based weapons, a projectile weapon's AttackSound is not used when missiles are fired; the projectile's SeeSound is used instead. DeathSound is the sound your projectile makes when it explodes. The Obituary is what the game displays in deathmatch when another player is killed by this projectile. In game, %o will be replaced with the victim's name, and %k is replaced with the killer's name. This ZDoom wiki page contains other tags you can use, including markers for a player's gender.

States
{
Spawn:
MISL A 1 bright
loop
Death:
MISL B 8 bright A_Explode(128,128)
MISL C 6 bright
MISL D 4 bright
stop
}
}

And the last bit, the States. Projectiles' states are simple; all you need is a Spawn state (the animation of your projectile in flight) and a Death state (the explosion animation). You'll probably see a function here you may not recognize: A_Explode. You can read all about it at this wiki page; there are some really neat extra effects you can pull off with it, but the short and simple of it is that A_Explode is what you want if you want your projectile to hurt a big group of monsters.

So now that we've got a projectile defined, we just need something to fire it. For a weapon, we'll accomplish this with the A_FireCustomMissile function. Observe:
Fire:
TRIF A 5 Bright A_FireCustomMissile("AwexomeRocket")
TRIF B 5 Bright
TRIG A 10
TRIG B 0 A_Refire
Goto Ready

Ordinarily, all A_FireCustomMissile needs for an argument is the actor name of your projectile. If you don't want it to cost ammo every time it is fired, that is the third argument, following the Angle (further details are in the wiki page for A_FireCustomMissile). So a simple projectile attack consists entirely of the missile's name, since the default parameters will launch your projectile straight forward, and will cost the specified amount of ammo (that's in Weapon.AmmoType1, by the way, or Weapon.AmmoType2 if it's for the altfire) every time it is fired. Simple enough, eh? If for some reason you don't want it to cost ammo, it'd look more like this:

A_FireCustomMissile("AwexomeRocket", 0, 0)

Because you can't just throw the argument in there willy-nilly. Arguments go in a specific order, and if you want to change one of the "later" ones, you need to specify all of the arguments that come before it. In this case, a zero is passed to the second argument, Angle, which is already the default, so that we can then pass a zero to the Flags argument to make it not take ammo. Again, the wiki page for A_FireCustomMissile will be of much help if you want to do more with this function.

If you want to see all the other stuff you can pull off with functions like these, just have a look at the Action Functions page at ZDoom Wiki. The Wiki is your friend!