OMRWeaponCycleProfile.cs
using Sandbox;

/// <summary>
/// Reusable profile for a weapon that must mechanically cycle after firing.
/// Bolt-action rifles are the first consumer, but pump/lever actions can reuse
/// the exact same state machine and presentation events.
/// </summary>
[AssetType(
	Name = "OMR Weapon Cycle Profile",
	Extension = "omrcycle",
	Category = "One More Round/Weapons"
)]
public sealed partial class OMRWeaponCycleProfile : GameResource
{
	[Group( "Identity" ), Title( "Cycle ID" )]
	public string CycleId { get; set; } = string.Empty;

	[Group( "Identity" )]
	public OMRWeaponCycleKind Kind { get; set; } = OMRWeaponCycleKind.BoltAction;

	[Group( "Timing" ), Description( "Automatically enter the cycling state after each successful primary shot." )]
	public bool AutoCycleAfterPrimaryShot { get; set; } = true;

	[Group( "Timing" ), Range( 0f, 2f ), Description( "Delay after the shot before the visible/mechanical cycling presentation begins. Fire is blocked immediately after the shot." )]
	public float StartDelay { get; set; } = 0.12f;

	[Group( "Timing" ), Range( 0f, 4f ), Description( "Duration of the actual cycle after StartDelay. The chamber becomes ready when this completes." )]
	public float Duration { get; set; } = 0.68f;

	[Group( "Handling" ), Description( "ADS policy while the visible/mechanical cycle is active. RetainIfAlreadyAiming keeps a scoped shot scoped while the aim input remains held, but prevents a hip-fired shot from entering ADS until the cycle completes." )]
	public OMRCycleAimPolicy AimPolicy { get; set; } = OMRCycleAimPolicy.Legacy;

	[Group( "Handling" ), Description( "Legacy compatibility used only when Aim Policy is Legacy. Allow ADS to remain held while the weapon cycles." )]
	public bool AllowAimDuringCycle { get; set; } = true;

	[Group( "Handling" ), Description( "Prevent reload from starting while the cycle is pending/in progress." )]
	public bool BlockReloadDuringCycle { get; set; } = true;

	public float TotalLockoutTime =>
		System.MathF.Max( StartDelay, 0f ) +
		System.MathF.Max( Duration, 0f );
}