FpsUI/Models/FireControlModel.cs

A small, engine-independent fire control model for weapons. It tracks fire mode (Semi, Burst, Auto), RPM, burst count and returns whether a shot should fire given trigger state each tick, with cooldown and burst queuing logic.

using System;

namespace Goo.FpsUI;

// The three trigger disciplines. Semi: one round per pull. Burst: a fixed count per pull. Auto: held spray.
public enum FireMode { Semi, Burst, Auto }

// Fire-mode gating, engine-free. Feed it the trigger-held state each tick, it returns true only on the
// ticks a round should actually leave the barrel, capped at the RPM cadence. Unit-testable.
public sealed class FireControlModel
{
    public FireMode Mode       = FireMode.Auto; // active fire mode
    public int      BurstCount = 3;             // rounds per pull in Burst mode
    public float    Rpm        = 600f;          // cadence cap (rounds per minute)

    float _cooldown;   // seconds until the next round is allowed
    int   _burstLeft;  // rounds still owed in the current burst
    bool  _prevDown;   // trigger state last tick (for rising-edge detection)

    public void Reset() { _cooldown = 0f; _burstLeft = 0; _prevDown = false; }

    public void Cycle() => Mode = Mode switch  // Semi -> Burst -> Auto -> Semi
    {
        FireMode.Semi  => FireMode.Burst,
        FireMode.Burst => FireMode.Auto,
        _              => FireMode.Semi,
    };

    // True on ticks a round should fire. triggerDown is the held state of the fire button this tick.
    public bool Tick( bool triggerDown, float dt )
    {
        bool pressed = triggerDown && !_prevDown;  // rising edge this tick
        _prevDown = triggerDown;
        if ( _cooldown > 0f ) _cooldown -= dt;
        float interval = Rpm > 0f ? 60f / Rpm : 0f;

        bool fire = false;
        switch ( Mode )
        {
            case FireMode.Auto:
                if ( triggerDown && _cooldown <= 0f ) fire = true;
                break;
            case FireMode.Semi:
                if ( pressed && _cooldown <= 0f ) fire = true;
                break;
            case FireMode.Burst:
                if ( pressed && _burstLeft <= 0 ) _burstLeft = Math.Max( 1, BurstCount ); // queue a burst on press
                if ( _burstLeft > 0 && _cooldown <= 0f ) { fire = true; _burstLeft--; }    // pay it out at cadence
                break;
        }
        if ( fire ) _cooldown = interval;
        return fire;
    }
}