FpsUI/Models/AmmoModel.cs

AmmoModel represents a weapon ammo system with a magazine and reserve. It tracks mag size, reserve, reload timing and progress, supports firing, starting reloads, auto-reload, resetting and ticking reload progress.

using System;

namespace Goo.FpsUI;

// Ammo logic: a magazine + reserve, a timed reload that pulls rounds from reserve. Engine-free.
public sealed class AmmoModel
{
    public int   MagSize        = 30;    // rounds per full magazine
    public float ReloadDuration = 1.6f;  // seconds a reload takes
    public bool  AutoReload     = true;  // start a reload automatically once the magazine empties

    public int  Mag       { get; private set; } = 30;  // rounds in the magazine
    public int  Reserve   { get; private set; } = 120; // rounds in reserve
    public bool Reloading { get; private set; }         // true mid-reload

    float _reloadTime;  // seconds remaining on the current reload

    // 0 at reload start, ramps to 1 as it completes, 0 when not reloading.
    public float ReloadProgress => Reloading && ReloadDuration > 0f
        ? 1f - Math.Clamp( _reloadTime / ReloadDuration, 0f, 1f )
        : 0f;

    public bool LowOnAmmo       => MagSize > 0 && Mag <= MagSize * 0.2f;        // at or below 20% of a magazine
    public bool NeedsReloadHint => LowOnAmmo && !Reloading && Reserve > 0;      // show the reload cue (low, idle, has reserve)

    public void Reset() => Mag = MagSize;                 // call after setting MagSize
    public void SetReserve( int rounds ) => Reserve = Math.Max( 0, rounds ); // set total reserve

    public bool Fire()    // consume one round, returns true only if a round was actually spent
    {
        if ( Reloading || Mag <= 0 ) return false;
        Mag--;
        return true;
    }

    public void Reload()  // begin a reload, no-op if full, empty reserve, or already reloading
    {
        if ( Reloading || Reserve <= 0 || Mag >= MagSize ) return;
        Reloading = true;
        _reloadTime = ReloadDuration;
    }

    public bool Tick( float dt )  // advance an in-progress reload, true while reloading
    {
        if ( AutoReload && Mag == 0 && !Reloading && Reserve > 0 ) Reload(); // empty mag -> auto-reload
        if ( !Reloading ) return false;
        _reloadTime -= dt;
        if ( _reloadTime > 0f ) return true;
        int take = Math.Min( MagSize - Mag, Reserve );
        Mag += take;
        Reserve -= take;
        Reloading = false;
        return true;
    }
}