WeaponInteractionContracts.cs
using Sandbox;
using System;

/// <summary>
/// Minimal aim state exposed by a held item to camera, locomotion and HUD
/// integration code. Consumers should depend on this contract rather than a
/// concrete weapon class whenever they only need to know whether the item is
/// currently aiming.
/// </summary>
public interface IWeaponAimState
{
	bool IsAiming { get; }
}

/// <summary>
/// Optional richer optic state used by camera/scope presentation. Consumers can
/// fall back to IWeaponAimState for ordinary weapons that do not expose it.
/// </summary>
public interface IWeaponOpticState : IWeaponAimState
{
	float AimFraction { get; }
	float AimAccuracyBlend { get; }
	bool IsScopePresentationActive { get; }
	float CurrentOpticSensitivityScale { get; }

	/// <summary>
	/// Current PiP peripheral-camera FOV multiplier after ADS blending. 1 means
	/// the world/periphery remains at its ordinary gameplay FOV.
	/// </summary>
	float CurrentPeripheralAimFovScale { get; }

	/// <summary>
	/// Current additional viewmodel-camera FOV multiplier after ADS blending.
	/// 1 preserves the existing viewmodel projection.
	/// </summary>
	float CurrentViewModelAimFovScale { get; }

	OMROpticProfile ActiveOpticProfile { get; }
}

/// <summary>
/// Narrow handling contract exposed by a held item to character locomotion.
///
/// The movement controller does not need to know how a weapon implements ADS,
/// sprint suppression, attachments or other internal mechanics. It only needs
/// the resolved restrictions/scales relevant to movement.
/// </summary>
public interface IWeaponHandlingState : IWeaponAimState
{
	/// <summary>
	/// True while the current weapon state should prevent physical sprint from
	/// resolving. OMR currently maps this to ADS, but another weapon/game may
	/// also use it for a heavy action or another handling state.
	/// </summary>
	bool BlocksSprint { get; }

	/// <summary>
	/// General movement multiplier contributed by the held item. Kept at 1 for
	/// the current OMR arsenal; the contract exists now so later handling stats do
	/// not require another locomotion dependency rewrite.
	/// </summary>
	float MovementSpeedScale { get; }

	/// <summary>
	/// Additional movement multiplier while aiming. Kept at 1 for the current
	/// OMR arsenal until ADS movement tuning moves into WeaponDefinition.
	/// </summary>
	float AimingMovementSpeedScale { get; }

	/// <summary>
	/// Gives locomotion temporary priority over ADS while sprint intent is active.
	/// The item decides how that suppression is represented internally.
	/// </summary>
	void SetSprintAimSuppressed( bool suppressed );
}

/// <summary>
/// Character/controller-side source of weapon-relevant locomotion facts.
///
/// The reusable weapon layer consumes this interface instead of reaching into
/// a specific movement implementation such as PlayerSprint or PlayerSlide.
/// Another S&box game can provide the same contract from a different controller.
/// </summary>
public interface IWeaponLocomotionProvider
{
	WeaponLocomotionSnapshot WeaponLocomotion { get; }
}

/// <summary>
/// Read-only snapshot of the physical movement facts that weapon mechanics and
/// first-person presentation are allowed to consume.
///
/// It deliberately contains resolved facts rather than input buttons. Intent is
/// included only where it matters to weapon handling (currently sprint intent).
/// </summary>
public readonly struct WeaponLocomotionSnapshot
{
	public bool IsValid { get; }
	public bool IsGrounded { get; }
	public bool IsAirborne { get; }
	public bool IsCrouched { get; }
	public bool IsSliding { get; }
	public bool WantsSprint { get; }
	public bool IsSprinting { get; }

	public Vector3 Velocity { get; }
	public Vector3 HorizontalVelocity { get; }
	public Vector3 Acceleration { get; }

	public float HorizontalSpeed { get; }
	public float VerticalVelocity { get; }
	public float ReferenceMoveSpeed { get; }
	public float NormalizedMoveSpeed { get; }
	public float LandingImpulse { get; }

	public static WeaponLocomotionSnapshot Invalid => default;

	public WeaponLocomotionSnapshot(
		bool isValid,
		bool isGrounded,
		bool isAirborne,
		bool isCrouched,
		bool isSliding,
		bool wantsSprint,
		bool isSprinting,
		Vector3 velocity,
		Vector3 acceleration,
		float referenceMoveSpeed,
		float landingImpulse
	)
	{
		IsValid = isValid;
		IsGrounded = isGrounded;
		IsAirborne = isAirborne;
		IsCrouched = isCrouched;
		IsSliding = isSliding;
		WantsSprint = wantsSprint;
		IsSprinting = isSprinting;

		Velocity = velocity;
		HorizontalVelocity = velocity.WithZ( 0f );
		Acceleration = acceleration;

		HorizontalSpeed = HorizontalVelocity.Length;
		VerticalVelocity = velocity.z;
		ReferenceMoveSpeed = MathF.Max( referenceMoveSpeed, 1f );
		NormalizedMoveSpeed = MathX.Clamp(
			HorizontalSpeed / ReferenceMoveSpeed,
			0f,
			1f
		);
		LandingImpulse = MathF.Max( landingImpulse, 0f );
	}

	/// <summary>
	/// Conservative fallback for BaseCombatWeapon owners whose controller does
	/// not provide IWeaponLocomotionProvider. It preserves basic movement,
	/// crouch and airborne behavior while leaving game-specific sprint/slide
	/// states false.
	/// </summary>
	public static WeaponLocomotionSnapshot FromPlayerController(
		PlayerController controller
	)
	{
		if ( controller is null )
			return Invalid;

		return new WeaponLocomotionSnapshot(
			isValid: true,
			isGrounded: controller.IsOnGround,
			isAirborne: controller.IsAirborne,
			isCrouched: controller.IsDucking,
			isSliding: false,
			wantsSprint: false,
			isSprinting: false,
			velocity: controller.Velocity,
			acceleration: Vector3.Zero,
			referenceMoveSpeed: MathF.Max( controller.RunSpeed, 1f ),
			landingImpulse: 0f
		);
	}
}