OMRWeaponDefinition.cs
using Sandbox;
using System;
using System.Collections.Generic;

/// <summary>
/// Canonical authored data for an OMR weapon.
///
/// This is deliberately independent from OMR round flow and HUD code. A weapon
/// definition describes the weapon itself; game modes may later transform these
/// values through the resolved-stat pipeline without mutating this asset.
/// </summary>
[AssetType(
	Name = "OMR Weapon Definition",
	Extension = "omrwep",
	Category = "One More Round/Weapons"
)]
public sealed partial class OMRWeaponDefinition : GameResource
{
	// =========================================================
	// IDENTITY
	// =========================================================

	[Group( "Identity" ), Title( "Weapon ID" ), Description( "Stable code-facing identifier. Keep this unique and independent from prefab filenames." )]
	public string WeaponId { get; set; } = string.Empty;

	[Group( "Identity" )]
	public string DisplayName { get; set; } = "Weapon";

	[Group( "Identity" )]
	public OMRWeaponArchetype Archetype { get; set; } = OMRWeaponArchetype.Unknown;

	// =========================================================
	// FIRE / BALLISTICS
	// =========================================================

	[Group( "Fire" ), Range( 0f, 500f )]
	public float Damage { get; set; } = 12f;

	[Group( "Fire" ), Range( 1, 32 )]
	public int Pellets { get; set; } = 1;

	[Group( "Fire" ), Range( 1f, 100000f )]
	public float Range { get; set; } = 4096f;

	[Group( "Fire" ), Range( 0f, 64f )]
	public float Radius { get; set; } = 1f;

	[Group( "Fire" ), Range( 0f, 50000f )]
	public float Force { get; set; } = 3000f;

	[Group( "Fire" ), Title( "Base Spread" )]
	public Vector2 SpreadBase { get; set; } = new( 0.2f, 0.2f );

	[Group( "Fire" ), Title( "Spread Growth" )]
	public Vector2 SpreadGrowth { get; set; } = new( 1.5f, 1.5f );

	[Group( "Fire" ), Range( 0f, 10f ), Title( "Spread Recovery" )]
	public float SpreadRecovery { get; set; } = 0.2f;

	[Group( "Fire" )]
	public bool PrimaryAutomatic { get; set; } = false;

	[Group( "Fire" ), Range( 0.01f, 10f ), Title( "Fire Interval" )]
	public float PrimaryDelay { get; set; } = 0.25f;

	[Group( "Fire" ), Range( 1, 500 ), Title( "Magazine Size" )]
	public int ClipMaxSize { get; set; } = 8;

	[Group( "Fire" ), Range( 0f, 30f )]
	public float ReloadTime { get; set; } = 2f;

	[Group( "Fire" ), Range( 0f, 10f )]
	public float DeployTime { get; set; } = 0.5f;

	[Group( "Fire" )]
	public bool AutoReload { get; set; } = true;

	[Group( "Fire" )]
	public bool CanCancelReload { get; set; } = true;

	[Group( "Reload" ), Title( "Incremental Reloading" ), Description( "Load one round at a time instead of filling the whole magazine. Intended for shotguns and other shell-by-shell weapons." )]
	public bool IncrementalReloading { get; set; } = false;

	[Group( "Reload" ), Range( 0f, 10f ), Title( "Reload Start Time" ), Description( "Delay before the first round is inserted during an incremental reload. Zero falls back to Reload Time." )]
	public float ReloadStartTime { get; set; } = 0f;

	[Group( "Reload" ), Range( 0f, 10f ), Title( "First Shell Extra Time" ), Description( "Optional extra delay after the first inserted round before subsequent incremental reload steps." )]
	public float FirstShellReloadTime { get; set; } = 0f;

	// =========================================================
	// CAPABILITIES
	// =========================================================

	[Group( "Capabilities" ), Title( "Allow Aim" ), Description( "When false, this weapon cannot enter OMR ADS even if the player holds the aim action. Useful for event-only hipfire weapons." )]
	public bool AllowAim { get; set; } = true;

	[Group( "Capabilities" ), Title( "Allow Reload" ), Description( "When false, all manual/automatic reload attempts are rejected. Round reset may still refill the authored magazine." )]
	public bool AllowReload { get; set; } = true;

	// =========================================================
	// ADS / HANDLING
	// =========================================================

	[Group( "Handling" ), Range( 0f, 1f )]
	public float IronSightsFireScale { get; set; } = 0.2f;

	[Group( "Handling" ), Range( 0.1f, 1f )]
	public float AimFovScale { get; set; } = 0.8f;

	[Group( "Handling" ), Range( 0f, 2f )]
	public float AimFovTransitionTime { get; set; } = 0.15f;

	[Group( "Handling" ), Range( 0.1f, 4f )]
	public float AimAnimationSpeed { get; set; } = 1f;

	[Group( "Handling" ), Range( 0f, 0.5f )]
	public float SprintToFireDelay { get; set; } = 0.15f;

	[Group( "Handling" ), Range( 0.05f, 0.5f )]
	public float SprintFirePoseHoldTime { get; set; } = 0.22f;

	// =========================================================
	// REUSABLE CAPABILITIES
	// =========================================================

	[Group( "Optic" ), Description( "Optional reusable optic profile. When assigned, its FOV/ADS timing values become the authored handling baseline and its precision/sensitivity thresholds drive runtime optic state." )]
	public OMROpticProfile OpticProfile { get; set; }

	[Group( "Cycling" ), Description( "Optional post-shot cycling profile for bolt/pump/lever style weapons. Leave empty for ordinary self-loading weapons." )]
	public OMRWeaponCycleProfile CycleProfile { get; set; }

	// =========================================================
	// ACCURACY
	// =========================================================

	[Group( "Accuracy" )]
	public OMRMovementAccuracyModel MovementAccuracyModel { get; set; } =
		OMRMovementAccuracyModel.LegacyThreshold;

	[Group( "Accuracy" ), Range( 0f, 1000f )]
	public float MovingSpeedThreshold { get; set; } = 25f;

	[Group( "Accuracy" ), Range( 0f, 1f )]
	public float MovementAccuracyStartFraction { get; set; } = 0.10f;

	[Group( "Accuracy" ), Range( 0f, 1f )]
	public float MovementAccuracyFullFraction { get; set; } = 1.0f;

	[Group( "Accuracy" ), Range( 1f, 6f )]
	public float MovingSpreadMultiplier { get; set; } = 1.3f;

	[Group( "Accuracy" ), Range( 0.1f, 2f )]
	public float CrouchedSpreadMultiplier { get; set; } = 0.85f;

	[Group( "Accuracy" ), Range( 1f, 10f )]
	public float AirborneSpreadMultiplier { get; set; } = 2.2f;

	[Group( "Accuracy" ), Range( 1f, 10f )]
	public float SlidingSpreadMultiplier { get; set; } = 1.6f;

	[Group( "Accuracy" ), Range( 0f, 1f )]
	public float AimSpreadMultiplier { get; set; } = 0.3f;

	// =========================================================
	// RECOIL
	// =========================================================

	[Group( "Recoil" )]
	public OMRGameplayRecoilModel RecoilModel { get; set; } =
		OMRGameplayRecoilModel.LegacyRandom;

	[Group( "Recoil" )]
	public Vector2 RecoilPitch { get; set; } = new( -0.45f, -0.25f );

	[Group( "Recoil" )]
	public Vector2 RecoilYaw { get; set; } = new( -0.12f, 0.12f );

	[Group( "Recoil" ), Title( "Recoil Pattern Asset" ), Description( "Preferred visual authoring path for Pattern recoil. Draw cumulative vertical/horizontal trajectories in an .omrcoil resource. When assigned, this takes priority over Legacy Pattern Steps." )]
	public OMRRecoilPattern RecoilPatternAsset { get; set; }

	[Group( "Recoil" ), Title( "Pattern Scale" ), Description( "Per-weapon multiplier applied to the authored pattern asset or legacy pattern steps. X = pitch/vertical strength, Y = yaw/horizontal strength." )]
	public Vector2 RecoilPatternScale { get; set; } = Vector2.One;

	[Group( "Recoil" ), Title( "Legacy Pattern Steps" ), Description( "Compatibility fallback. X = pitch, Y = yaw, in degrees per shot. Prefer Recoil Pattern Asset for new weapons." )]
	public List<Vector2> RecoilPattern { get; set; } = new();

	[Group( "Recoil" ), Description( "Symmetric random noise added after the authored pattern kick is resolved. X = pitch noise, Y = yaw noise." )]
	public Vector2 RecoilPatternNoise { get; set; } = Vector2.Zero;

	[Group( "Recoil" ), Range( 0f, 5f )]
	public float RecoilPatternResetDelay { get; set; } = 0.35f;

	[Group( "Recoil" )]
	public bool RecoilPatternLoop { get; set; } = false;

	[Group( "Recoil Recovery" ), Description( "When enabled, a configurable fraction of gameplay recoil returns gradually after firing stops." )]
	public bool RecoilRecoveryEnabled { get; set; } = false;

	[Group( "Recoil Recovery" ), Range( 0f, 1f ), Description( "Fraction of each gameplay recoil kick eligible for automatic return. 0 = none, 1 = full return." )]
	public float RecoilRecoveryFraction { get; set; } = 0.65f;

	[Group( "Recoil Recovery" ), Range( 0f, 2f ), Description( "Seconds after the last shot before gameplay recoil begins returning." )]
	public float RecoilRecoveryDelay { get; set; } = 0.18f;

	[Group( "Recoil Recovery" ), Range( 0f, 30f ), Description( "Maximum pitch return speed in degrees per second." )]
	public float RecoilRecoveryPitchSpeed { get; set; } = 3.25f;

	[Group( "Recoil Recovery" ), Range( 0f, 30f ), Description( "Maximum yaw return speed in degrees per second." )]
	public float RecoilRecoveryYawSpeed { get; set; } = 4.5f;

	[Group( "Recoil" ), Range( 0f, 10f )]
	public float CameraRecoilRoll { get; set; } = 0.45f;

	[Group( "Recoil" ), Range( 0f, 20f )]
	public float CameraRecoilFrequency { get; set; } = 1f;

	[Group( "Recoil" ), Range( 0f, 5f )]
	public float CameraRecoilDuration { get; set; } = 0.3f;

	[Group( "Recoil" ), Range( 0f, 1f )]
	public float AdsCameraRecoilScale { get; set; } = 0.65f;

	// =========================================================
	// CROSSHAIR
	// =========================================================

	[Group( "Crosshair" ), Range( 0.1f, 10f )]
	public float CrosshairSpreadVisualScale { get; set; } = 2.5f;

	[Group( "Crosshair" )]
	public float CrosshairMinimumGap { get; set; } = 4f;

	[Group( "Crosshair" )]
	public float CrosshairMaximumGap { get; set; } = 70f;

	[Group( "Crosshair" )]
	public float CrosshairLineLength { get; set; } = 9f;

	[Group( "Crosshair" )]
	public float CrosshairLineWidth { get; set; } = 2f;

	[Group( "Crosshair" )]
	public Color CrosshairColor { get; set; } = Color.White;

	// =========================================================
	// PRESENTATION / ENGINE ASSETS
	// =========================================================

	[Group( "Presentation" )]
	public PrefabFile ViewModelPrefab { get; set; }

	[Group( "Presentation" )]
	public PrefabFile WorldModelPrefab { get; set; }

	[Group( "Audio" )]
	public SoundEvent AttackSound { get; set; }

	[Group( "Audio" )]
	public SoundEvent DryFireSound { get; set; }

	[Group( "Reload Audio" ), Description( "Timed presentation sounds measured in seconds from reload start. The runtime weapon copies and sorts these cues when the definition is applied." )]
	public List<OMRReloadSoundCue> ReloadSoundCues { get; set; } = new();

	[Group( "Reload Audio" ), Description( "Optional sound played for each inserted round when the weapon uses incremental reloading." )]
	public SoundEvent IncrementalReloadInsertSound { get; set; }

	[Group( "Cycle Audio" ), Description( "Timed bolt/pump/lever sounds measured in seconds from CycleStarted (after the cycle profile StartDelay). A single full mechanical recording normally uses one cue at 0 seconds." )]
	public List<OMRCycleSoundCue> CycleSoundCues { get; set; } = new();
}