Page 1 of 5

RRWM reloading system [1.5.0] [ZS/DEC/SBARINFO]

Posted: Fri Nov 06, 2020 11:00 am
by Player701
RRWM reloading system

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).
Download the latest release (1.5.0) - Based on RRWM 1.5.0 released on 2023-12-31
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:
  1. Set an ammo type derived from ReloadableAmmo.
  2. Set AmmoUse to a positive value.
  3. Set ReloadableWeapon.ClipCapacity to a positive value and a multiple of AmmoUse.
  4. Instead of calling A_WeaponReady in your Ready state, you should call A_ReloadableWeaponReady.
  5. Define a full reload state or a sequential reload state (see p. 2.1 or p. 2.2).
  6. Optionally, define a dry fire state (see p. 3).
A_ReloadableWeaponReady optionally accepts the same flags as A_WeaponReady does, except for WRF_ALLOWRELOAD, which is obviously redundant.

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:
  1. The sequence must run in a loop.
  2. Call A_ReloadOneRoundp anywhere in your state sequence when you want to load the amount of ammo equal to AmmoUse1.
  3. 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.
You can also add a ReloadDone state where you could put some animation depicting your weapon finishing the reloading sequence. The weapon will jump to the ReloadDone state when A_CheckReloadDone is called and there is no more ammo left to load. If there is no ReloadDone state, it will jump to the Ready state instead.

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:
  1. 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.
  2. 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.
  3. 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.
To add an animation to your weapon that will be displayed when players try to fire it while empty, define a DryFire state. For alternate fire, define a DryAltFire state instead (both states can be defined at the same time). Put your animation in this state. Now there are only two things left to take care of:
  1. 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).
  2. Add a call to A_DryReFire to the end of your dry fire state sequence.
NB: Normally, A_ReFire allows the user to specify a state label to jump to, but since that'd make it impossible for the reloading system's code to override it, you have to use the default form (without an argument). You can select a fire state sequence via GetFireState (see "Additional virtual methods" further below) in ZScript, or use jump functions in the beginning of the Fire state in DECORATE.

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:
  1. 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.
  2. 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:
  1. Using the reload button. This is obviously available in all modes.
  2. 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.
  3. 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.
By default, the mode is set to full auto. To change it, search for the first appearance of GetReloadMode in classes/core/ReloadableWeapon.zs and edit the return value. The value must be either RM_FULLAUTO, RM_SEMIAUTO, or RM_MANUAL. It is not a requirement that the mode remains the same for the duration of the game. You can make it depend on a CVar, which is exactly what RRWM itself does.


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:
  1. Include all classes from the example package subfolder classes/core/sbarinfo_support into your mod.
  2. 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.
To draw the clip amount, use the DrawNumber SBARINFO command with MagazineCounter as an argument. You should place this command in a conditional block so that it is only displayed when the player's current weapon is reloadable. This can be done with the following code:

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;
}
The example package provides an SBARINFO that implements the standard Doom HUD (based on the corresponding SBARINFO file from gzdoom.pk3) and can also display the magazine counter for reloadable weapons. The counter is available in both the status bar and the fullscreen HUD.

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)
Jumps to the specified state label if the amount of ammo in the weapon's clip matches the condition provided. cond can be one of the following:
  • 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
In ZScript, instead of A_JumpIfClipAmount, you should use A_JumpIf with an expression involving invoker.GetClipAmount(). For example, A_JumpIfClipAmount(JC_LessThanOrEqual, 5, "SomeState") is equivalent to A_JumpIf(invoker.GetClipAmount() <= 5, "SomeState").

Code: Select all

state A_JumpIfFullClip(statelabel label)
Jumps to the specified state label if the weapon's clip is currently at capacity. This can be useful to avoid potential bugs in weapon code if the clip capacity is changed later.

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();
Use this method instead of GetUpState if you need to choose a select state based on an external factor (e.g. a fire mode).

Code: Select all

protected virtual void PreRaise();
Use this method when you need to initialize or reset the internal state of your weapon before it is raised. This is called just before GetSelectState.

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);
Use this method instead of GetDownState if you need to choose a deselect state based on an external factor. fromReload is true when the weapon is being deselected from an interrupted reloading sequence (see p. 3.2 for details).

Code: Select all

protected virtual void PreLower();
Use this method when you need to adjust the internal state of your weapon before it is lowered. This is called just before GetDeselectState.

Code: Select all

protected virtual state GetFireState(bool altFire, EFireType type);
Use this method instead of GetAtkState or GetAltAtkState if you need to choose a fire state based on an external factor. altFire is true when the player is using the alternate fire button instead of the normal one. type is equal to one of the following values:
  • 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);
Similar to GetFireState, you can override this method to choose your own dry fire state. type is equal to one of the following values:
  • 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();
Override this method to choose your own reload state.

Code: Select all

protected virtual void PreReload();
Use this method when you need to adjust the internal state of your weapon before it is reloaded. This is called just before `GetReloadState`.

Code: Select all

protected virtual state GetReloadDoneState();
Override this method to choose your own state for finishing a sequential reload (see p. 3.2 for details). If this method returns null, the weapon will jump to its ready state instead.


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.
These weapons use third-party resources that come directly from RRWM. Please see the RRWM credits file for more information about the origin of the resources.


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!

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Fri Nov 27, 2020 11:32 am
by Player701
RRWM reloading system has been updated to version 1.3.0a. Download the new release HERE. The link in the first post has been updated too.

In terms of functionality, this version is completely identical to 1.2.3, but the PK3 file now uses a more common compression algorithm to make it easier to work with. Most importantly, this means that you shouldn't get any more problems trying to open the package and examine its contents with editing tools like SLADE. Also, the release archive is now provided in ZIP format instead of the less popular 7Z, so that you don't have to install any third-party software to unpack it.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 5:23 am
by yum13241
I'm having an issue.

MW_ADDON_v1.0.pk3:zscripts/weapons.zs, line 163: Cannot use non-action function A_ReloadFullClip here.

ZSBattleRifle:
Spoiler:


ZRMonstrousWeapon:
Spoiler:


BRAmmo:
Spoiler:

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 5:41 am
by Player701
All actions functions provided by ReloadableWeapon are only supported in weapon context, so your states block should be called States(Weapon) instead of just States. Note that since you have a spawn state as well, you need to separate your states blocks:

Code: Select all

States(Actor)
{
    Spawn:
        UACR X -1;
        Stop;
}

States(Weapon)
{
    // All other states go here
}
You also need to use A_ReloadableWeaponReady instead of just A_WeaponReady to handle reload requests properly, otherwise your weapon will always start the reloading animation when you press the reload key, even if its clip is full or if you don't have enough ammo.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 8:06 am
by yum13241
Thanks! But for some reason the game locks up my PC when I fire the weapon.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 9:40 am
by Player701
Please post your updated code here so I can investigate.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 9:47 am
by yum13241
ZSBattleRIfle:
Spoiler:


BRAmmo:
Spoiler:


ZMonstrousWeapon:
Spoiler:


ZRMonstrousWeapon:
Spoiler:


This also happens with my railgun altfire:
ZSRailgun:
Spoiler:


If you want the whole mod just ask, but you'll need a private devbuild of my mod which I'm fine with distributing, since I'm making an addon.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 9:57 am
by Player701
Sadly, I am unable to reproduce the freeze on my end. But I've also noticed something else: namely, you don't need two ammo types for your rifle weapon - the first one is used both for firing and reloading. The second ammo type can be used for an alternate attack, which does not support reloading. However, I didn't change the code apart from the sprite names, and I still couldn't get the game to lock up. So I guess you'll have to send me the entire mod, but I probably won't be able to investigate the problem until tomorrow.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 12:44 pm
by yum13241
Download both files here: https://mega.nz/folder/GQ5GkART#svoLCgJQh9s6FUkjV70jzg (the folder is named Player701, if it isn't then something horribly went wrng)

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sat Dec 24, 2022 1:17 pm
by Player701
Sorry, I'm still unable to reproduce your lock-up issue.

I've only noticed that your rifle weapon has the call to A_ReFire in the Hold state commented out, but you actually do need it - otherwise, it will jump to the hold state every time you press the fire button after firing once. This is not related to the reloading system and is in fact a requirement for the hold state to work properly.

I don't know why you set AmmoGive1 to 0 though, so I have to give myself some ammo in order to fire. Aside from that, everything seems just fine to me.

The railgun alt-fire also works for me - but since it does not use the reloading system, I can only say that your problem is likely being caused by something else. Perhaps you're using a very old version of GZDoom (RRWM 1.3.0, which serves as the base for this reloading library, requires at least GZDoom 4.5.0), or you have some third-party mod in your autoloads that's messing something up.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sun Dec 25, 2022 4:36 am
by yum13241
Even with A_Refire uncommented it still crashes. Maybe Linux shenanigans again... Even without autoloads something goes wrong. Maybe there's undefined behavior going on, since it locks up my PC shortly after I stop firing. Even my chaingun replacement crashed my game. Something is wrong...

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Sun Dec 25, 2022 8:10 am
by Player701
Sorry, I have no idea... Based on what you're saying, your issue doesn't seem to be related to the reloading system at all.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Mon Jan 09, 2023 1:02 pm
by Ultimate Freedoomer
I was trying to make the clip work with the ALTHUD, but it isn't showing up when I have that turned on.

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Mon Jan 09, 2023 1:10 pm
by Player701
I've found the AltHUD somewhat difficult to work with because the ability to modify it appears to have been added mostly as an afterthought. Therefore, it is not supported by default. However, you can use the following ZScript code to add a magazine counter to the AltHUD:

Code: Select all

class ReloadableAltHud : AltHud
{
    override int DrawAmmo(PlayerInfo CPlayer, int x, int y)
    {
        // Draw the ammo table (this will also ensure the table data is present)
        int result = Super.DrawAmmo(CPlayer, x, y);

        let rel = ReloadableWeapon(CPlayer.ReadyWeapon);

        if (rel != null)
        {
            // OK, this code is copy-pasted from AltHud.
            // Unfortunately, there doesn't seem to be a better solution,
            // but at least we've got something!

            int ammocurlen;
            int ammomaxlen;
            [ammocurlen, ammomaxlen] = GetAmmoTextLengths(CPlayer);

            string buf = string.Format("%0*d/%0*d", ammocurlen, 0, ammomaxlen, 0);
            int def_width = ConFont.StringWidth(buf);

            int clipAmount = rel.GetClipAmount();
            int len = GetDigitCount(clipAmount) * HudFont.GetCharWidth("0");

            x -= def_width + 25 + len;

            if (hud_ammo_order > 0)
            {
                x -= 4;
            }

            y += (ConFont.GetHeight() + HudFont.GetHeight()) / 2;

            for (int i = orderedammos.Size() - 1; i >= 0; i--)
            {
                if (orderedammos[i] == rel.Ammo1.GetClass())
                {
                    DrawHudNumber(HudFont, Font.CR_RED, clipAmount, x, y);
                    break;
                }

                y -= 10;
            }
        }

        return result;
    }
}
You will then need to enable this modded AltHUD in MAPINFO with the following gameinfo directive:

Code: Select all

AltHudClass = "ReloadableAltHud"

Re: RRWM reloading system [1.3.0a] [ZS/DEC/SBARINFO]

Posted: Mon Jan 09, 2023 1:41 pm
by Ultimate Freedoomer
Do I have to put it in 1 of the PK3"s directories? Edit: NVM, I didn't realize it only shows clips for the selected weapons