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();
}