OneMoreRoundWeapon.RoundRules.cs
using Sandbox;
using System;

/// <summary>
/// Temporary round-scoped weapon rules.
///
/// These are deliberately runtime-only and owner-local. They do not mutate the
/// authored .omrwep definition and are cleared whenever the owning PlayerState
/// leaves the special round state. This gives round modes such as High Noon a
/// precise fire gate and spread override without baking event rules into the
/// revolver definition.
/// </summary>
public partial class OneMoreRoundWeapon
{
	private bool _temporaryPrimaryGateEnabled;
	private float _temporaryPrimaryGateOpensAt;
	private bool _temporaryPrimaryGateRequireRelease;
	private bool _temporaryPrimaryGateReleased;

	private bool _temporarySpreadOverrideEnabled;
	private Vector2 _temporarySpreadOverride;

	private bool _roundAllowsAim = true;

	/// <summary>
	/// Round-scoped ADS gate. This is separate from the authored AllowAim
	/// capability so Hipfire Only never mutates the source .omrwep.
	/// </summary>
	public bool RoundAllowsAim =>
		_roundAllowsAim;

	public void SetRoundAimAllowed(
		bool allowed
	)
	{
		if ( _roundAllowsAim == allowed )
			return;

		_roundAllowsAim = allowed;

		if ( !allowed && IsAiming )
		{
			SetAiming( false );
		}
	}

	/// <summary>
	/// Blocks primary fire until a synchronized absolute Time.Now.
	///
	/// When requireRelease is true, holding attack through the opening moment
	/// does not count. The trigger must be released and pressed again.
	/// </summary>
	public void ConfigureTemporaryPrimaryGate(
		float opensAt,
		bool requireRelease
	)
	{
		_temporaryPrimaryGateEnabled = true;
		_temporaryPrimaryGateOpensAt = opensAt;
		_temporaryPrimaryGateRequireRelease = requireRelease;

		// If the trigger is already held, it must be released after this gate is
		// configured before a shot can become valid.
		_temporaryPrimaryGateReleased =
			!requireRelease &&
			Time.Now >= opensAt;
	}

	public void SetTemporarySpreadOverride(
		Vector2 spread
	)
	{
		_temporarySpreadOverrideEnabled = true;
		_temporarySpreadOverride = new Vector2(
			MathF.Max( spread.x, 0f ),
			MathF.Max( spread.y, 0f )
		);
	}

	public void ClearTemporaryRoundRules()
	{
		_temporaryPrimaryGateEnabled = false;
		_temporaryPrimaryGateOpensAt = 0f;
		_temporaryPrimaryGateRequireRelease = false;
		_temporaryPrimaryGateReleased = false;

		_temporarySpreadOverrideEnabled = false;
		_temporarySpreadOverride = Vector2.Zero;
	}

	private void UpdateTemporaryPrimaryGate()
	{
		if ( !_temporaryPrimaryGateEnabled )
			return;

		bool attackDown =
			Input.Down( "attack1" );

		// Any held trigger before the gate opens disarms the release condition.
		// This prevents pressing a fraction before DRAW and simply holding it.
		if ( Time.Now < _temporaryPrimaryGateOpensAt )
		{
			if ( attackDown )
				_temporaryPrimaryGateReleased = false;

			return;
		}

		if (
			_temporaryPrimaryGateRequireRelease &&
			!attackDown
		)
		{
			_temporaryPrimaryGateReleased = true;
		}
	}

	private bool IsPrimaryBlockedByTemporaryGate()
	{
		if ( !_temporaryPrimaryGateEnabled )
			return false;

		if ( Time.Now < _temporaryPrimaryGateOpensAt )
			return true;

		if (
			_temporaryPrimaryGateRequireRelease &&
			!_temporaryPrimaryGateReleased
		)
			return true;

		return false;
	}

	private bool TryGetTemporarySpreadOverride(
		out Vector2 spread
	)
	{
		spread =
			_temporarySpreadOverride;

		return _temporarySpreadOverrideEnabled;
	}

	private void NotifyRoundPrimaryShotFired()
	{
		PlayerState player =
			Owner?
				.GameObject
				.Components
				.Get<PlayerState>();

		player?.
			NotifyRoundPrimaryShotFired(
				Time.Now
			);
	}
}