OneMoreRoundWeapon.Actions.cs
using Sandbox;

/// <summary>
/// Mechanical weapon-action gate. Locomotion and ADS remain orthogonal states.
/// This gives future capabilities such as bolt cycling a real mechanical state
/// without creating a combinatorial movement/aim state machine.
/// </summary>
public partial class OneMoreRoundWeapon
{
	private OMRWeaponActionState _mechanicalActionState =
		OMRWeaponActionState.Ready;

	private int _primaryShotsFiredSinceReset;
	private float _lastPrimaryShotAt = -1f;

	/// <summary>
	/// Effective action state for gameplay/debugging. Reloading and the existing
	/// sprint-to-fire queue are derived from their authoritative/current systems;
	/// explicit mechanical state is reserved for capabilities such as Cycling.
	/// </summary>
	public OMRWeaponActionState ActionState
	{
		get
		{
			if ( !IsHeld )
				return OMRWeaponActionState.Holstered;

			if ( IsReloading )
				return OMRWeaponActionState.Reloading;

			if ( IsCyclePendingOrActive )
				return OMRWeaponActionState.Cycling;

			if ( _mechanicalActionState != OMRWeaponActionState.Ready )
				return _mechanicalActionState;

			if ( _sprintFireQueued )
				return OMRWeaponActionState.SprintToFire;

			return OMRWeaponActionState.Ready;
		}
	}

	public int PrimaryShotsFiredSinceReset =>
		_primaryShotsFiredSinceReset;

	public float LastPrimaryShotAt =>
		_lastPrimaryShotAt;

	/// <summary>
	/// Future reusable capabilities can call this when they own a mutually
	/// exclusive mechanical action. Do not use it for ADS/crouch/sprint.
	/// </summary>
	internal void SetMechanicalActionState(
		OMRWeaponActionState state
	)
	{
		_mechanicalActionState = state switch
		{
			OMRWeaponActionState.Holstered => OMRWeaponActionState.Ready,
			OMRWeaponActionState.Reloading => OMRWeaponActionState.Ready,
			OMRWeaponActionState.SprintToFire => OMRWeaponActionState.Ready,
			_ => state
		};

	}

	internal void ClearMechanicalActionState(
		OMRWeaponActionState expectedState
	)
	{
		if ( _mechanicalActionState != expectedState )
			return;

		_mechanicalActionState = OMRWeaponActionState.Ready;
	}

	private void ResetWeaponActionState()
	{
		_mechanicalActionState = OMRWeaponActionState.Ready;
		_primaryShotsFiredSinceReset = 0;
		_lastPrimaryShotAt = -1f;
	}

	private void RecordPrimaryShot()
	{
		_primaryShotsFiredSinceReset++;
		_lastPrimaryShotAt = RealTime.Now;
	}

	private bool IsPrimaryBlockedByMechanicalAction()
	{
		return _mechanicalActionState != OMRWeaponActionState.Ready;
	}

	/// <summary>
	/// BaseCombatWeapon performs the actual shot first so the current projectile
	/// uses the pre-recoil aim direction. OMR then records the mechanical action
	/// and applies recoil to subsequent aim.
	/// </summary>
	public override void PrimaryAttack()
	{
		// Capture the synchronized trigger time at the exact point a valid primary
		// attack is actually committed. High Noon uses this for latency-aware
		// reaction adjudication; ordinary rounds simply ignore the notification.
		NotifyRoundPrimaryShotFired();

		InterruptViewModelSprintForAttack();

		// Preserve the scoped fire-animation scale before BaseCombatWeapon
		// raises b_attack. Bolt-action weapons can leave ADS immediately after
		// the shot; without this latch the AnimGraph can see the scale jump back
		// to 1 while the fire animation is still playing.
		LatchViewModelFireScaleForPrimaryShot();

		base.PrimaryAttack();

		RecordPrimaryShot();
		ApplyRecoil();
		BeginPostShotCycle();
	}

	public override bool CanPrimaryAttack()
	{
		if ( IsPrimaryBlockedByTemporaryGate() )
			return false;

		// The queued sprint shot must be released only through the gate in
		// UpdateSprintToFireGate, where FirePrimary() re-runs this readiness check.
		if ( _sprintFireQueued )
			return false;

		if ( IsPrimaryBlockedByMechanicalAction() )
			return false;

		if ( IsPrimaryBlockedByCycle() )
			return false;

		return base.CanPrimaryAttack();
	}

	public override bool CanSecondaryAttack()
	{
		return false;
	}
}