OneMoreRoundWeapon.Locomotion.cs
using Sandbox;

/// <summary>
/// Generic locomotion/handling bridge for OneMoreRoundWeapon.
///
/// The weapon deliberately knows only the reusable contracts declared in
/// WeaponInteractionContracts.cs. OMR-specific movement components remain on
/// the player/controller side of that boundary.
/// </summary>
public partial class OneMoreRoundWeapon : IWeaponHandlingState
{
	private GameObject _locomotionProviderOwner;
	private IWeaponLocomotionProvider _locomotionProvider;

	/// <summary>
	/// Current OMR behavior: ADS prevents physical sprint from resolving.
	/// Future action-state work can extend this without PlayerLocomotion needing
	/// to know why the item is blocking sprint.
	/// </summary>
	public bool BlocksSprint => IsAiming;

	/// <summary>
	/// Reserved handling hooks. They intentionally resolve to 1 during this
	/// migration so Phase 2 changes no movement feel.
	/// </summary>
	public float MovementSpeedScale => 1f;
	public float AimingMovementSpeedScale => 1f;

	public void SetSprintAimSuppressed( bool suppressed )
	{
		if ( suppressed )
		{
			SuppressAimForSprint();
		}
		else
		{
			ReleaseAimSuppressionForSprint();
		}
	}

	/// <summary>
	/// Resolves the current controller-side locomotion provider once per owner
	/// hierarchy, then consumes its lightweight snapshot. If another game uses
	/// this weapon framework without an adapter, BaseCombatWeapon.Owner supplies
	/// a conservative PlayerController fallback.
	/// </summary>
	public bool TryGetLocomotionSnapshot(
		out WeaponLocomotionSnapshot snapshot
	)
	{
		GameObject ownerObject =
			Inventory?.GameObject;

		if ( ownerObject != _locomotionProviderOwner )
		{
			ResolveLocomotionProvider( ownerObject );
		}

		if ( _locomotionProvider is not null )
		{
			snapshot = _locomotionProvider.WeaponLocomotion;

			if ( snapshot.IsValid )
				return true;
		}

		PlayerController owner = Owner;
		if ( owner is not null )
		{
			snapshot =
				WeaponLocomotionSnapshot.FromPlayerController(
					owner
				);

			return snapshot.IsValid;
		}

		snapshot = WeaponLocomotionSnapshot.Invalid;
		return false;
	}

	private void ResolveLocomotionProvider(
		GameObject ownerObject
	)
	{
		_locomotionProviderOwner = ownerObject;
		_locomotionProvider = null;

		if (
			ownerObject is null ||
			!ownerObject.IsValid
		)
		{
			return;
		}

		foreach ( Component component in ownerObject.Components.GetAll() )
		{
			if ( component is IWeaponLocomotionProvider provider )
			{
				_locomotionProvider = provider;
				return;
			}
		}
	}

	private void ResetLocomotionProviderCache()
	{
		_locomotionProviderOwner = null;
		_locomotionProvider = null;
	}
}