TUTORIAL: Inventory "Variables" and Making a Sniper Rifle

It's high time we learned how to use what is arguably one of the most important things to know when making a reasonably complicated Decorate weapon: the use of dummy inventory items as a "variable" system. If you're not a programmer by trade, a variable is basically the term to describe a number that is stored in memory to be referred to at a given point elsewhere in the program.

The simplest use of a variable is as a Boolean (we talked about those in the very first tutorial), a number that is only 0 or 1. And what we'll do here is use this knowledge to create a simple zooming sniper rifle by checking the status of our variable and using A_JumpIfInventory to make the weapon behave a certain way.

First, let's define our dummy item.
ACTOR SniperZoomed : Inventory
Inventory.MaxAmount 1

If you want there to be multiple states of zoom, you'll probably want to increase the MaxAmount to something above 1, but for purposes of this tutorial, we only have two states: Zoomed (1) and Unzoomed (0). We'll refer back to SniperZoomed in our weapon, which I'll just be constructing out of the basic Doom shotgun graphics. (Incidentally, Inventory.MaxAmount 1 is defined by default as part of the Inventory class, so you technically don't need to put it in here if you're only going to have two states. You will need to define a different maximum, though, if you want to have larger values.)

Go ahead and set up your weapon as usual - make it take Shells as an ammo type, and assign it the usual Select and Deselect states. The Ready state is where things will get a bit different, so don't write one up just yet.

Our sniper rifle will have two different Ready states. The first ("real") Ready state will simply display the shotgun in its usual position, but will also include an A_JumpIfInventory checking for the presence of the SniperZoomed inventory item, and if it is present, will jump to a special state we'll call ReadyZoomed. This is what is known as a custom state label; you can name it pretty much anything you want (as long as it's not reserved by ZDoom for a special function, like Flash), and refer to it using any of the various Jump or Goto commands.

Go ahead and write up a Ready state for your new sniper weapon, and on a new zero-duration state immediately before your A_WeaponReady line, add a line that checks for SniperZoomed and jumps to ReadyZoomed. Then, make a ReadyZoomed label, with only an A_WeaponReady line using the sprite TNT1A0 (the "blank" sprite I mentioned a couple tutorials ago), and end it with Goto Ready. Here's one I made earlier:
SHTG A 0 A_JumpIfInventory("SniperZoomed",1,"ReadyZoomed")
SHTG A 1 A_WeaponReady
TNT1 A 1 A_WeaponReady
Goto Ready

We haven't really done anything with the actual zooming yet, but don't worry, we'll get to that in the AltFire state. Our altfire will be pretty straightforward. When the altfire key is pressed, the weapon checks if SniperZoomed already exists. If it doesn't, the weapon gives that item through A_GiveInventory, sets A_ZoomFactor to an appropriate level, and then goes back to the Ready state. If the earlier inventory check shows that SniperZoomed is already in the inventory, it jumps instead to an UnZoom state which removes the SniperZoomed item from the inventory, resets A_ZoomFactor to a normal level, and then returns to the Ready state. If you've been paying attention, it shouldn't be much problem for you to write this state yourself, but in case you're having problems, here's one I baked earlier.
SHTG A 0 A_JumpIfInventory("SniperZoomed",1,"UnZoom")
SHTG A 0 A_GiveInventory("SniperZoomed",1)
SHTG A 0 A_ZoomFactor(3)
Goto Ready
TNT1 A 0 A_TakeInventory("SniperZoomed",1)
TNT1 A 0 A_ZoomFactor(1)
Goto Ready

A_ZoomFactor is a recent addition to ZDoom. The argument it takes is what the player's FOV (field of view) will be divided by while the zoom is in effect. So an A_ZoomFactor of 3 will divide the default FOV (in this case probably 90) by 3, resulting in an FOV of 30 degrees and a view that is zoomed approximately 3 times normal. If that's confusing, just remember that a higher A_ZoomFactor will result in a more zoomed-in view, and an A_ZoomFactor of 1 resets the view back to normal.

If you were to save and test this right now, there would be some issues. For starters, while the weapon is no longer in view while the zoom is in effect, it suddenly reappears when you fire, and if you try to switch away from the weapon while zoomed, the game "remembers" the zoom and immediately jumps back to it when you switch back to the sniper rifle. So we'll need to do two things here: first, we'll need to make the Fire state check the value of SniperZoomed as well, so we can create a substitute fire state that does not display the weapon graphics. Second, we'll add some extra code to the Deselect state that removes the zoom and inventory item, so that the zoom does not "stick."

Let's take care of this blatant error with the fire state first. Do the usual A_JumpIfInventory at the very beginning of the fire state, and have it jump to a FireZoomed state that is identical to the normal Fire state except that all the sprites are TNT1A0. (You could also make it display a sniper scope graphic instead, but we'll ignore that for now.) Probably would look a bit like this:
SHTG A 0 A_JumpIfInventory("SniperZoomed",1,"FireZoomed")
SHTG A 2 BRIGHT A_FireBullets(0,0,1,30,"BulletPuff",1)
Goto Ready
TNT1 A 27 A_FireBullets(0,0,1,50,"BulletPuff",1)
Goto Ready

You might notice that I made the FireZoomed state cause more bullet damage than the unzoomed version. This is a little gameplay balancing trick - to cause the most damage with this sniper rifle, the player would need to aim carefully using the Zoom function and restrict their view of the world around them. It's what the gaming business would call "risk vs reward" - if you take greater risks by giving yourself a penalty (like your field of view being narrow and being unable to see things sneaking up on you), you stand a chance of getting a greater reward (like doing more damage to your target and potentially not having to spend as much ammo taking him down).

As for the Deselect fix, it's as simple as adding a two zero-duration lines to its beginning:
TNT1 A 0 A_TakeInventory("SniperZoomed",1)
TNT1 A 0 A_ZoomFactor(1)
SHTG A 1 A_Lower

Don't worry about the first two commands repeating when looped; it won't have any effect after they've been executed once. Save and test your weapon with these changes and you'll have a neat zooming sniper-shotgun that obeys the laws of physics.

(Another word from Ed the Bat: In this case, you could actually end your Deselect state with Wait instead of Loop; this tells ZDoom to loop only the last state. If your Deselect state is particularly complex, this could potentially save some strain on the processor.)

Inventory variables like the SniperZoomed flag we used in this tutorial can come in very, very handy. There's tons of things you can do with them, from weapons that reload to weapons that can charge, weapons that can overheat, or even a melee weapon with a combination attack. The use of inventory variables is arguably the most powerful thing available to weapon makers today.