Nutshell
This is a standalone version of the weapon reloading system used in my gameplay mod RRWM.
It requires a custom HUD to display the magazine amount. (either ZScript or SBARINFO, see p. 5 further below for details)
Requires GZDoom 4.3.1+, in earlier versions this may not work properly or at all.
Here are some particular features of this implementation:
- Weapon does not need the AMMO_OPTIONAL flag. When there is no ammo, it won't be possible to select it unless "check ammo for weapon switch" is disabled in gameplay options (this is vanilla GZDoom behavior).
- No need to perform ammo checks manually.
- Ammo in weapon's magazine (aka clip) is contained within the weapon itself, and cannot be tampered with by inventory functions or cheats.
- Dropped weapon retains the ammo loaded into its magazine. Weapons dropped on death also preserve their magazine contents.
- Supports alternate attack using the same or a different ammo type. When using a different ammo type, alternate attack is not subject to the reloading feature.
- Additional ways to reload the weapon besides using the reload button (see p. 4.1 further below for details).
- Proper handling of the infinite ammo cheat/powerup (though you still have to reload the weapon periodically).
Bitbucket repository
NB: This code is provided as-is and will not receive feature updates unless they come from upstream (the RRWM Bitbucket repository). However, if you find a bug, please report it here and I'll try to fix the code and provide an updated version. You can also report bugs via the RRWM issue tracker. Click here to report a bug via the issue tracker.
How to use
Using this system in your own project requires only minimal experience with ZScript, and only if you want to fine-tune the reloading mechanics (see pp. 4.1-4.3 for details). You can use DECORATE for your classes and SBARINFO for your HUD. However, ZScript gives you more advantages such as the ability to customize your weapon even further by overriding the provided virtual methods (see "Additional virtual methods" further below for more information).
1) Make an ammo class derived from ReloadableAmmo
All ammo types for your reloadable weapons must be derived from ReloadableAmmo. This class contains some logic to work around a certain special case in the weapon auto-switching behavior. You can use either ZScript or DECORATE to define your ammo class.
2) Make a weapon class derived from ReloadableWeapon
You can use either ZScript or DECORATE to define your weapon class. This is how you should set it up:
- Set an ammo type derived from ReloadableAmmo.
- Set AmmoUse to a positive value.
- Set ReloadableWeapon.ClipCapacity to a positive value and a multiple of AmmoUse.
- Instead of calling A_WeaponReady in your Ready state, you should call A_ReloadableWeaponReady.
- Define a full reload state or a sequential reload state (see p. 2.1 or p. 2.2).
- Optionally, define a dry fire state (see p. 3).
NB: Weapon flags PRIMARY_USES_BOTH and ALT_USES_BOTH are not supported.
2.1) Add a full reload state
If your weapon has a single animation depicting a complete reload (magazine swap), you should define a full reload state. Just add a Reload state to the weapon, put your animation in there, and at the point where the actual reloading should happen, call A_ReloadFullClip. You should return to the Ready state at the end of your reloading animation.
2.2) Add a sequential reload state
If your weapon is reloaded round by round (e.g. like a shotgun), you should define a sequential reload state. Your Reload state sequence must be designed in the following way:
- The sequence must run in a loop.
- Call A_ReloadOneRoundp anywhere in your state sequence when you want to load the amount of ammo equal to AmmoUse1.
- Call A_CheckReloadDone anywhere in your state sequence where you want your weapon to jump out of it when there is no more ammo to load.
Optionally, if you want your reloading sequence to be interruptible, add a call to A_ReloadableWeaponReady anywhere after the call to A_ReloadOneRound. This enables the reloading sequence to be interrupted in the following ways:
- The weapon can be deselected, and will jump to the DeselectReload state upon a weapon change request. If there is no DeselectReload state, it will jump to the Deselect state instead.
- The weapon can be fired, and will jump to the FireReload state upon pressing the fire button. If there is no FireReload state, it will jump to the Fire state instead. If alternate fire is supported, the same logic is used to jump to either AltFireReload or AltFire states.
- The reloading sequence can be terminated prematurely by pressing the reload button again. The weapon will jump to the ReloadDone state (or the Ready state, if ReloadDone is not defined) as soon as A_CheckReloadDone is called next time.
3) Optional: Add dry fire states
You can make your weapon do something (e.g. play a clicking sound) when the player attempts to fire it with no ammo. This can happen under different circumstances, for example:
- The weapon runs out of ammo while firing, and the player keeps holding the fire button.
- The reloading mode is set to manual, and the player tries to fire the weapon. See p. 4.1 for more information about reloading modes.
- Make sure you have a call to A_ReFire somewhere in your main fire / alt-fire state. NB: You must use the default form of A_ReFire (see below for details).
- Add a call to A_DryReFire to the end of your dry fire state sequence.
Now you're all set. When A_ReFire is called next time and the weapon is out of ammo, it will jump to its DryFire or DryAltFire state. A call to A_DryReFire ensures that the weapon gets deselected as soon as the fire button is released, provided that reloading or using an alternate attack is not possible.
There are two additional dry fire state labels that can be made use of:
- Dry[Alt]Hold: If defined, the weapon will jump to this state instead of Dry[Alt]Fire when A_DryReFire is called and the fire button is still held.
- Dry[Alt]FireCooldown: If defined, the weapon will jump to this state instead of Dry[Alt]Fire when it runs out of ammo while firing. The weapon will jump to the Dry[Alt]Fire state as usual if it's already out of ammo when the player attempts to fire it. This can happen if "check ammo for weapon switch" is disabled or if the reloading mode is set to manual (see p. 4.1 directly below for more information).
4.1) Optional setup: Reloading mode
This reloading system supports three reloading modes: full auto, semi-auto, and manual. The mode determines what exactly the player can do to reload the weapon. Depending on the mode, the following options may be available:
- Using the reload button. This is obviously available in all modes.
- Attempting to fire the weapon with an empty magazine when there is enough ammo to reload. This is available in full auto and semi-auto modes.
- Automatic reload of an empty weapon after a certain time delay (see further below for how to adjust it). This is only available in full auto mode.
4.2) Optional setup: Auto-reload delay (only for full auto reloading mode)
If the reloading mode is set to full auto, an empty weapon will reload itself automatically after a certain time delay. This delay can be adjusted by changing the value of the AUTORELOAD_DELAY constant in ReloadableWeapon. Search for the first appearance of AUTORELOAD_DELAY in classes/core/ReloadableWeapon.zs (it's near the very top of the class definition) and change the value to your own preference.
4.3) Optional setup: Auto-reload ammo level threshold (only for full auto reload mode)
If the reloading mode is set to full auto, you can make the weapon initiate reloading automatically only when there is a certain amount of ammo available in reserve. This amount is expressed as a percentage of the weapons's magazine capacity. For example, if your weapon has a magazine capacity of 10 and you've set the threshold to 30%, the weapon will only reload automatically when you have 10 * 30% = 3 or more rounds left in reserve. Default value is 0, which means the weapon will always initiate an automatic reload if the player has at least AmmoUse1 ammo in reserve.
To change the threshold value, search for the first appearance of GetAutoReloadThreshold in classes/core/ReloadableWeapon.zs and edit the return value. Note that it is a percentage (goes from 0 to 100) and not a fraction (goes from 0 to 1). It is not a requirement that the value remains constant: RRWM also ties it to a user CVar, and you can do the same if you wish.
5) Implement a custom HUD with a magazine counter
You are encouraged to use a ZScript-based HUD to display the clip amount. However, SBARINFO-based HUDs are suported as well. To use an SBARINFO-based HUD, you have to do the following setup:
- Include all classes from the example package subfolder classes/core/sbarinfo_support into your mod.
- Add MagazineCheck to the starting inventory of your player class. The example package already includes a player class called SbarinfoSupportPlayer with a correctly set up starting inventory. You will also have it when you include the SBARINFO support classes, so you can just modify it instead of creating a new class from scratch.
Code: Select all
InInventory MagazineCheck, 1
{
// This block is executed if the player's current weapon is reloadable.
// Adjust the parameters here to your liking.
drawnumber 3, HUDFONT_DOOM, untranslated, MagazineCounter, 44, 171;
}
ZScript coders should use ReloadableWeapon::GetClipAmount to display the counter in their HUDs. This method is available for use in the UI context. Don't forget to check that the player's ReadyWeapon is actually a ReloadableWeapon before attempting to draw the counter. The example package includes a bare-bones ZScript HUD with the clip and ammo counters only. Uncomment the StatusBarClass line in MAPINFO and switch to the fullscreen HUD in-game to witness the example ZScript HUD in action.
DECORATE jump functions
For convenience purposes, the ReloadableWeapon class provides jump functions that test the weapon's clip amount against a specific condition, jumping if the condition matches. Note that the jump functions are redundant when using ZScript, use A_JumpIf instead (see examples below).
NB: Do NOT use the jump functions to jump between different kinds of weapon state sequences (e.g. from ready to reload, or from reload to fire, and so on), it will almost surely cause unpredictable results!
Code: Select all
state A_JumpIfClipAmount(cond, int amt, statelabel label)
- JC_Equal: matches if the amount if equal to amt
- JC_LessThan: matches if the amount is less than (but not equal to) amt
- JC_GreaterThan: matches if the amount is greater than (but not equal to) amt
- JC_LessThanOrEqual: matches if the amount is less than OR equal to amt
- JC_GreaterThanOrEqual: matches if the amount is greater than OR equal to amt
Code: Select all
state A_JumpIfFullClip(statelabel label)
In ZScript, use A_JumpIf with invoker.IsFullClip() instead. For example, A_JumpIfFullClip("SomeState") is equivalent to A_JumpIf(invoker.IsFullClip(), "SomeState").
Additional virtual methods
For advanced coders, ReloadableWeapon provides several virtual methods that can be overridden in derived classes.
Code: Select all
protected virtual state GetSelectState();
Code: Select all
protected virtual void PreRaise();
NB: You must ensure this method is idempotent, i.e. calling it multiple times in a row produces the same effect as calling it just once. This is because in most cases, this method is called twice upon a level change - first when the level actually changes, and then when the weapon is brought up - but the latter is not guaranteed to happen in all cases.
Code: Select all
protected virtual state GetDeselectState(bool fromReload);
Code: Select all
protected virtual void PreLower();
Code: Select all
protected virtual state GetFireState(bool altFire, EFireType type);
- FT_Normal - normal fire
- FT_Hold - weapon is returning to the fire state after a call to A_ReFire
- FT_FromReload - weapon is firing from an interrupted reloading sequence (see p. 3.2 for details).
Code: Select all
protected virtual state GetDryFireState(bool altFire, EDryFireType type);
- DFT_Normal - normal dry fire
- DFT_Hold - weapon is returning to the dry fire state after a call to A_DryReFire
- DFT_Cooldown - weapon is entering the dry fire state having run out of ammo while firing.
Code: Select all
protected virtual state GetReloadState();
Code: Select all
protected virtual void PreReload();
Code: Select all
protected virtual state GetReloadDoneState();
Under the hood
The RRWM reloading system relies heavily on the setup of interaction between the internal classes PlayerPawn and Weapon. The exact behaviors that it leverages are undocumented, but so is most of the code in gzdoom.pk3, so this isn't considered an issue. It is unlikely that the system will break in newer GZDoom versions, since it has already survived several major GZDoom releases without any fundamental changes.
Do note, however, that altering any aspect of the system's behavior might not be a trivial task. It was not designed to be constantly modified and expanded because its exact goals had been outlined from the very beginning. This code does exactly what it was supposed to do; the system's core features have been playtested for countless hours. It is still not guaranteed to be bug-free, though, especially considering that some of the optional features have been added mostly for completeness' sake and are not used extensively (if at all) in RRWM itself.
Flowchart
Here you can find the complete flowchart of events that can happen when the player is using a reloadable weapon, depending on the weapon's state and the player's actions. The chart should be read from the top left. Download the image to your PC to zoom in. The flowchart is also included in the release archive.
Examples
The release package contains two example weapons, implemented in DECORATE. The weapons are included in the player's starting inventory.
- DukeShotgun (slot 3): A simple reloadable shotgun.
- ZenGun (slot 4): An SMG with a normal and an alternate attack.
Crediting
You can use this code in your own projects and modify it as you see fit. But you also have to credit the author as Player701 and mention that you are using the RRWM reloading system.
Thank you very much!