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;
}
}