OMRWeaponStats.cs
using Sandbox;
using System;
using System.Collections.Generic;
/// <summary>
/// Plain runtime data representing the effective authored/gameplay values of a
/// weapon. Instances are copied before modifiers are applied, so the source
/// OMRWeaponDefinition is never mutated during gameplay.
/// </summary>
public sealed class OMRWeaponStats
{
// Identity is carried with the snapshot for diagnostics and filtering. It is
// not intended to be changed by ordinary stat modifiers.
public string WeaponId { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public OMRWeaponArchetype Archetype { get; set; } = OMRWeaponArchetype.Unknown;
// Fire / ballistics
public float Damage { get; set; }
public int Pellets { get; set; }
public float Range { get; set; }
public float Radius { get; set; }
public float Force { get; set; }
public Vector2 SpreadBase { get; set; }
public Vector2 SpreadGrowth { get; set; }
public float SpreadRecovery { get; set; }
public bool PrimaryAutomatic { get; set; }
public float PrimaryDelay { get; set; }
public int ClipMaxSize { get; set; }
public float ReloadTime { get; set; }
public float DeployTime { get; set; }
public bool AutoReload { get; set; }
public bool CanCancelReload { get; set; }
// Handling
public float IronSightsFireScale { get; set; }
public float AimFovScale { get; set; }
public float AimFovTransitionTime { get; set; }
public float AimAnimationSpeed { get; set; }
public float SprintToFireDelay { get; set; }
public float SprintFirePoseHoldTime { get; set; }
// Accuracy
public OMRMovementAccuracyModel MovementAccuracyModel { get; set; }
public float MovingSpeedThreshold { get; set; }
public float MovementAccuracyStartFraction { get; set; }
public float MovementAccuracyFullFraction { get; set; }
public float MovingSpreadMultiplier { get; set; }
public float CrouchedSpreadMultiplier { get; set; }
public float AirborneSpreadMultiplier { get; set; }
public float SlidingSpreadMultiplier { get; set; }
public float AimSpreadMultiplier { get; set; }
// Recoil
public OMRGameplayRecoilModel RecoilModel { get; set; }
public Vector2 RecoilPitch { get; set; }
public Vector2 RecoilYaw { get; set; }
public OMRRecoilPattern RecoilPatternAsset { get; set; }
public Vector2 RecoilPatternScale { get; set; } = Vector2.One;
public List<Vector2> RecoilPattern { get; set; } = new();
public Vector2 RecoilPatternNoise { get; set; }
public float RecoilPatternResetDelay { get; set; }
public bool RecoilPatternLoop { get; set; }
public bool RecoilRecoveryEnabled { get; set; }
public float RecoilRecoveryFraction { get; set; }
public float RecoilRecoveryDelay { get; set; }
public float RecoilRecoveryPitchSpeed { get; set; }
public float RecoilRecoveryYawSpeed { get; set; }
public float CameraRecoilRoll { get; set; }
public float CameraRecoilFrequency { get; set; }
public float CameraRecoilDuration { get; set; }
public float AdsCameraRecoilScale { get; set; }
// Crosshair
public float CrosshairSpreadVisualScale { get; set; }
public float CrosshairMinimumGap { get; set; }
public float CrosshairMaximumGap { get; set; }
public float CrosshairLineLength { get; set; }
public float CrosshairLineWidth { get; set; }
public Color CrosshairColor { get; set; }
public OMRWeaponStats Clone()
{
return new OMRWeaponStats
{
WeaponId = WeaponId,
DisplayName = DisplayName,
Archetype = Archetype,
Damage = Damage,
Pellets = Pellets,
Range = Range,
Radius = Radius,
Force = Force,
SpreadBase = SpreadBase,
SpreadGrowth = SpreadGrowth,
SpreadRecovery = SpreadRecovery,
PrimaryAutomatic = PrimaryAutomatic,
PrimaryDelay = PrimaryDelay,
ClipMaxSize = ClipMaxSize,
ReloadTime = ReloadTime,
DeployTime = DeployTime,
AutoReload = AutoReload,
CanCancelReload = CanCancelReload,
IronSightsFireScale = IronSightsFireScale,
AimFovScale = AimFovScale,
AimFovTransitionTime = AimFovTransitionTime,
AimAnimationSpeed = AimAnimationSpeed,
SprintToFireDelay = SprintToFireDelay,
SprintFirePoseHoldTime = SprintFirePoseHoldTime,
MovementAccuracyModel = MovementAccuracyModel,
MovingSpeedThreshold = MovingSpeedThreshold,
MovementAccuracyStartFraction = MovementAccuracyStartFraction,
MovementAccuracyFullFraction = MovementAccuracyFullFraction,
MovingSpreadMultiplier = MovingSpreadMultiplier,
CrouchedSpreadMultiplier = CrouchedSpreadMultiplier,
AirborneSpreadMultiplier = AirborneSpreadMultiplier,
SlidingSpreadMultiplier = SlidingSpreadMultiplier,
AimSpreadMultiplier = AimSpreadMultiplier,
RecoilModel = RecoilModel,
RecoilPitch = RecoilPitch,
RecoilYaw = RecoilYaw,
RecoilPatternAsset = RecoilPatternAsset,
RecoilPatternScale = RecoilPatternScale,
RecoilPattern = RecoilPattern is null
? new List<Vector2>()
: new List<Vector2>( RecoilPattern ),
RecoilPatternNoise = RecoilPatternNoise,
RecoilPatternResetDelay = RecoilPatternResetDelay,
RecoilPatternLoop = RecoilPatternLoop,
RecoilRecoveryEnabled = RecoilRecoveryEnabled,
RecoilRecoveryFraction = RecoilRecoveryFraction,
RecoilRecoveryDelay = RecoilRecoveryDelay,
RecoilRecoveryPitchSpeed = RecoilRecoveryPitchSpeed,
RecoilRecoveryYawSpeed = RecoilRecoveryYawSpeed,
CameraRecoilRoll = CameraRecoilRoll,
CameraRecoilFrequency = CameraRecoilFrequency,
CameraRecoilDuration = CameraRecoilDuration,
AdsCameraRecoilScale = AdsCameraRecoilScale,
CrosshairSpreadVisualScale = CrosshairSpreadVisualScale,
CrosshairMinimumGap = CrosshairMinimumGap,
CrosshairMaximumGap = CrosshairMaximumGap,
CrosshairLineLength = CrosshairLineLength,
CrosshairLineWidth = CrosshairLineWidth,
CrosshairColor = CrosshairColor
};
}
public static OMRWeaponStats FromDefinition( OMRWeaponDefinition definition )
{
if ( definition is null || !definition.IsValid )
return null;
OMROpticProfile optic =
definition.OpticProfile is not null &&
definition.OpticProfile.IsValid
? definition.OpticProfile
: null;
return new OMRWeaponStats
{
WeaponId = definition.WeaponId?.Trim() ?? string.Empty,
DisplayName = definition.DisplayName?.Trim() ?? string.Empty,
Archetype = definition.Archetype,
Damage = definition.Damage,
Pellets = definition.Pellets,
Range = definition.Range,
Radius = definition.Radius,
Force = definition.Force,
SpreadBase = definition.SpreadBase,
SpreadGrowth = definition.SpreadGrowth,
SpreadRecovery = definition.SpreadRecovery,
PrimaryAutomatic = definition.PrimaryAutomatic,
PrimaryDelay = definition.PrimaryDelay,
ClipMaxSize = definition.ClipMaxSize,
ReloadTime = definition.ReloadTime,
DeployTime = definition.DeployTime,
AutoReload = definition.AutoReload,
CanCancelReload = definition.CanCancelReload,
IronSightsFireScale = definition.IronSightsFireScale,
AimFovScale = optic?.AimFovScale ?? definition.AimFovScale,
AimFovTransitionTime = optic?.AimTransitionTime ?? definition.AimFovTransitionTime,
AimAnimationSpeed = optic?.AimAnimationSpeed ?? definition.AimAnimationSpeed,
SprintToFireDelay = definition.SprintToFireDelay,
SprintFirePoseHoldTime = definition.SprintFirePoseHoldTime,
MovementAccuracyModel = definition.MovementAccuracyModel,
MovingSpeedThreshold = definition.MovingSpeedThreshold,
MovementAccuracyStartFraction = definition.MovementAccuracyStartFraction,
MovementAccuracyFullFraction = definition.MovementAccuracyFullFraction,
MovingSpreadMultiplier = definition.MovingSpreadMultiplier,
CrouchedSpreadMultiplier = definition.CrouchedSpreadMultiplier,
AirborneSpreadMultiplier = definition.AirborneSpreadMultiplier,
SlidingSpreadMultiplier = definition.SlidingSpreadMultiplier,
AimSpreadMultiplier = definition.AimSpreadMultiplier,
RecoilModel = definition.RecoilModel,
RecoilPitch = definition.RecoilPitch,
RecoilYaw = definition.RecoilYaw,
RecoilPatternAsset = definition.RecoilPatternAsset,
RecoilPatternScale = definition.RecoilPatternScale,
RecoilPattern = definition.RecoilPattern is null
? new List<Vector2>()
: new List<Vector2>( definition.RecoilPattern ),
RecoilPatternNoise = definition.RecoilPatternNoise,
RecoilPatternResetDelay = definition.RecoilPatternResetDelay,
RecoilPatternLoop = definition.RecoilPatternLoop,
RecoilRecoveryEnabled = definition.RecoilRecoveryEnabled,
RecoilRecoveryFraction = definition.RecoilRecoveryFraction,
RecoilRecoveryDelay = definition.RecoilRecoveryDelay,
RecoilRecoveryPitchSpeed = definition.RecoilRecoveryPitchSpeed,
RecoilRecoveryYawSpeed = definition.RecoilRecoveryYawSpeed,
CameraRecoilRoll = definition.CameraRecoilRoll,
CameraRecoilFrequency = definition.CameraRecoilFrequency,
CameraRecoilDuration = definition.CameraRecoilDuration,
AdsCameraRecoilScale = definition.AdsCameraRecoilScale,
CrosshairSpreadVisualScale = definition.CrosshairSpreadVisualScale,
CrosshairMinimumGap = definition.CrosshairMinimumGap,
CrosshairMaximumGap = definition.CrosshairMaximumGap,
CrosshairLineLength = definition.CrosshairLineLength,
CrosshairLineWidth = definition.CrosshairLineWidth,
CrosshairColor = definition.CrosshairColor
};
}
public static OMRWeaponStats Capture( OneMoreRoundWeapon weapon )
{
if ( weapon is null )
return null;
OMROpticProfile optic =
weapon.OpticProfile is not null &&
weapon.OpticProfile.IsValid
? weapon.OpticProfile
: null;
return new OMRWeaponStats
{
WeaponId = weapon.WeaponId?.Trim() ?? string.Empty,
DisplayName = weapon.DisplayName?.Trim() ?? string.Empty,
Archetype = weapon.WeaponArchetype,
Damage = weapon.Ballistics.Damage,
Pellets = weapon.Ballistics.Pellets,
Range = weapon.Ballistics.Range,
Radius = weapon.Ballistics.Radius,
Force = weapon.Ballistics.Force,
SpreadBase = weapon.Ballistics.SpreadBase,
SpreadGrowth = weapon.Ballistics.SpreadGrowth,
SpreadRecovery = weapon.Ballistics.SpreadRecovery,
PrimaryAutomatic = weapon.PrimaryAutomatic,
PrimaryDelay = weapon.PrimaryDelay,
ClipMaxSize = weapon.ClipMaxSize,
ReloadTime = weapon.ReloadTime,
DeployTime = weapon.DeployTime,
AutoReload = weapon.AutoReload,
CanCancelReload = weapon.CanCancelReload,
IronSightsFireScale = weapon.IronSightsFireScale,
AimFovScale = optic?.AimFovScale ?? weapon.AimFovScale,
AimFovTransitionTime = optic?.AimTransitionTime ?? weapon.AimFovTransitionTime,
AimAnimationSpeed = optic?.AimAnimationSpeed ?? weapon.AimAnimationSpeed,
SprintToFireDelay = weapon.SprintToFireDelay,
SprintFirePoseHoldTime = weapon.SprintFirePoseHoldTime,
MovementAccuracyModel = weapon.MovementAccuracyModel,
MovingSpeedThreshold = weapon.MovingSpeedThreshold,
MovementAccuracyStartFraction = weapon.MovementAccuracyStartFraction,
MovementAccuracyFullFraction = weapon.MovementAccuracyFullFraction,
MovingSpreadMultiplier = weapon.MovingSpreadMultiplier,
CrouchedSpreadMultiplier = weapon.CrouchedSpreadMultiplier,
AirborneSpreadMultiplier = weapon.AirborneSpreadMultiplier,
SlidingSpreadMultiplier = weapon.SlidingSpreadMultiplier,
AimSpreadMultiplier = weapon.AimSpreadMultiplier,
RecoilModel = weapon.RecoilModel,
RecoilPitch = weapon.RecoilPitch,
RecoilYaw = weapon.RecoilYaw,
RecoilPatternAsset = weapon.RecoilPatternAsset,
RecoilPatternScale = weapon.RecoilPatternScale,
RecoilPattern = weapon.RecoilPattern is null
? new List<Vector2>()
: new List<Vector2>( weapon.RecoilPattern ),
RecoilPatternNoise = weapon.RecoilPatternNoise,
RecoilPatternResetDelay = weapon.RecoilPatternResetDelay,
RecoilPatternLoop = weapon.RecoilPatternLoop,
RecoilRecoveryEnabled = weapon.RecoilRecoveryEnabled,
RecoilRecoveryFraction = weapon.RecoilRecoveryFraction,
RecoilRecoveryDelay = weapon.RecoilRecoveryDelay,
RecoilRecoveryPitchSpeed = weapon.RecoilRecoveryPitchSpeed,
RecoilRecoveryYawSpeed = weapon.RecoilRecoveryYawSpeed,
CameraRecoilRoll = weapon.CameraRecoilRoll,
CameraRecoilFrequency = weapon.CameraRecoilFrequency,
CameraRecoilDuration = weapon.CameraRecoilDuration,
AdsCameraRecoilScale = weapon.AdsCameraRecoilScale,
CrosshairSpreadVisualScale = weapon.CrosshairSpreadVisualScale,
CrosshairMinimumGap = weapon.CrosshairMinimumGap,
CrosshairMaximumGap = weapon.CrosshairMaximumGap,
CrosshairLineLength = weapon.CrosshairLineLength,
CrosshairLineWidth = weapon.CrosshairLineWidth,
CrosshairColor = weapon.CrosshairColor
};
}
/// <summary>
/// Applies only sanity constraints required for the weapon to remain valid.
/// This deliberately avoids imposing balance opinions on the resolved values.
/// </summary>
public void Normalize()
{
WeaponId = WeaponId?.Trim() ?? string.Empty;
DisplayName = DisplayName?.Trim() ?? string.Empty;
Damage = MathF.Max( Damage, 0f );
Pellets = Math.Max( Pellets, 1 );
Range = MathF.Max( Range, 0.01f );
Radius = MathF.Max( Radius, 0f );
Force = MathF.Max( Force, 0f );
SpreadBase = ClampVectorMinimum( SpreadBase, 0f );
SpreadGrowth = ClampVectorMinimum( SpreadGrowth, 0f );
SpreadRecovery = MathF.Max( SpreadRecovery, 0f );
PrimaryDelay = MathF.Max( PrimaryDelay, 0.001f );
ClipMaxSize = Math.Max( ClipMaxSize, 1 );
ReloadTime = MathF.Max( ReloadTime, 0f );
DeployTime = MathF.Max( DeployTime, 0f );
IronSightsFireScale = MathF.Max( IronSightsFireScale, 0f );
AimFovScale = MathF.Max( AimFovScale, 0.01f );
AimFovTransitionTime = MathF.Max( AimFovTransitionTime, 0f );
AimAnimationSpeed = MathF.Max( AimAnimationSpeed, 0.01f );
SprintToFireDelay = MathF.Max( SprintToFireDelay, 0f );
SprintFirePoseHoldTime = MathF.Max( SprintFirePoseHoldTime, 0f );
MovingSpeedThreshold = MathF.Max( MovingSpeedThreshold, 0f );
MovementAccuracyStartFraction = MathX.Clamp( MovementAccuracyStartFraction, 0f, 1f );
MovementAccuracyFullFraction = MathX.Clamp( MovementAccuracyFullFraction, MovementAccuracyStartFraction, 1f );
MovingSpreadMultiplier = MathF.Max( MovingSpreadMultiplier, 0f );
CrouchedSpreadMultiplier = MathF.Max( CrouchedSpreadMultiplier, 0f );
AirborneSpreadMultiplier = MathF.Max( AirborneSpreadMultiplier, 0f );
SlidingSpreadMultiplier = MathF.Max( SlidingSpreadMultiplier, 0f );
AimSpreadMultiplier = MathF.Max( AimSpreadMultiplier, 0f );
RecoilPattern ??= new List<Vector2>();
RecoilPatternNoise = new Vector2( MathF.Abs( RecoilPatternNoise.x ), MathF.Abs( RecoilPatternNoise.y ) );
RecoilPatternResetDelay = MathF.Max( RecoilPatternResetDelay, 0f );
RecoilRecoveryFraction = MathX.Clamp( RecoilRecoveryFraction, 0f, 1f );
RecoilRecoveryDelay = MathF.Max( RecoilRecoveryDelay, 0f );
RecoilRecoveryPitchSpeed = MathF.Max( RecoilRecoveryPitchSpeed, 0f );
RecoilRecoveryYawSpeed = MathF.Max( RecoilRecoveryYawSpeed, 0f );
CameraRecoilRoll = MathF.Max( CameraRecoilRoll, 0f );
CameraRecoilFrequency = MathF.Max( CameraRecoilFrequency, 0f );
CameraRecoilDuration = MathF.Max( CameraRecoilDuration, 0f );
AdsCameraRecoilScale = MathF.Max( AdsCameraRecoilScale, 0f );
CrosshairSpreadVisualScale = MathF.Max( CrosshairSpreadVisualScale, 0f );
CrosshairMinimumGap = MathF.Max( CrosshairMinimumGap, 0f );
CrosshairMaximumGap = MathF.Max( CrosshairMaximumGap, CrosshairMinimumGap );
CrosshairLineLength = MathF.Max( CrosshairLineLength, 0f );
CrosshairLineWidth = MathF.Max( CrosshairLineWidth, 0f );
}
public void ApplyTo( OneMoreRoundWeapon weapon, bool clampLoadedAmmoToMagazine )
{
if ( weapon is null )
return;
WeaponId = WeaponId?.Trim() ?? string.Empty;
DisplayName = DisplayName?.Trim() ?? string.Empty;
weapon.WeaponId = WeaponId;
weapon.DisplayName = DisplayName;
weapon.WeaponArchetype = Archetype;
var ballistics = weapon.Ballistics;
ballistics.Damage = Damage;
ballistics.Pellets = Pellets;
ballistics.Range = Range;
ballistics.Radius = Radius;
ballistics.Force = Force;
ballistics.SpreadBase = SpreadBase;
ballistics.SpreadGrowth = SpreadGrowth;
ballistics.SpreadRecovery = SpreadRecovery;
weapon.Ballistics = ballistics;
weapon.PrimaryAutomatic = PrimaryAutomatic;
weapon.PrimaryDelay = PrimaryDelay;
weapon.ClipMaxSize = ClipMaxSize;
weapon.ReloadTime = ReloadTime;
weapon.DeployTime = DeployTime;
weapon.AutoReload = AutoReload;
weapon.CanCancelReload = CanCancelReload;
weapon.IronSightsFireScale = IronSightsFireScale;
weapon.AimFovScale = AimFovScale;
weapon.AimFovTransitionTime = AimFovTransitionTime;
weapon.AimAnimationSpeed = AimAnimationSpeed;
weapon.SprintToFireDelay = SprintToFireDelay;
weapon.SprintFirePoseHoldTime = SprintFirePoseHoldTime;
weapon.MovementAccuracyModel = MovementAccuracyModel;
weapon.MovingSpeedThreshold = MovingSpeedThreshold;
weapon.MovementAccuracyStartFraction = MovementAccuracyStartFraction;
weapon.MovementAccuracyFullFraction = MovementAccuracyFullFraction;
weapon.MovingSpreadMultiplier = MovingSpreadMultiplier;
weapon.CrouchedSpreadMultiplier = CrouchedSpreadMultiplier;
weapon.AirborneSpreadMultiplier = AirborneSpreadMultiplier;
weapon.SlidingSpreadMultiplier = SlidingSpreadMultiplier;
weapon.AimSpreadMultiplier = AimSpreadMultiplier;
weapon.RecoilModel = RecoilModel;
weapon.RecoilPitch = RecoilPitch;
weapon.RecoilYaw = RecoilYaw;
weapon.RecoilPatternAsset = RecoilPatternAsset;
weapon.RecoilPatternScale = RecoilPatternScale;
weapon.RecoilPattern = RecoilPattern is null
? new List<Vector2>()
: new List<Vector2>( RecoilPattern );
weapon.RecoilPatternNoise = RecoilPatternNoise;
weapon.RecoilPatternResetDelay = RecoilPatternResetDelay;
weapon.RecoilPatternLoop = RecoilPatternLoop;
weapon.RecoilRecoveryEnabled = RecoilRecoveryEnabled;
weapon.RecoilRecoveryFraction = RecoilRecoveryFraction;
weapon.RecoilRecoveryDelay = RecoilRecoveryDelay;
weapon.RecoilRecoveryPitchSpeed = RecoilRecoveryPitchSpeed;
weapon.RecoilRecoveryYawSpeed = RecoilRecoveryYawSpeed;
weapon.CameraRecoilRoll = CameraRecoilRoll;
weapon.CameraRecoilFrequency = CameraRecoilFrequency;
weapon.CameraRecoilDuration = CameraRecoilDuration;
weapon.AdsCameraRecoilScale = AdsCameraRecoilScale;
weapon.CrosshairSpreadVisualScale = CrosshairSpreadVisualScale;
weapon.CrosshairMinimumGap = CrosshairMinimumGap;
weapon.CrosshairMaximumGap = CrosshairMaximumGap;
weapon.CrosshairLineLength = CrosshairLineLength;
weapon.CrosshairLineWidth = CrosshairLineWidth;
weapon.CrosshairColor = CrosshairColor;
// Runtime magazine-size reductions must not leave a physically impossible
// loaded count. Increasing capacity deliberately does not conjure ammo.
if ( clampLoadedAmmoToMagazine && weapon.Clip1 > weapon.ClipMaxSize )
{
weapon.Clip1 = weapon.ClipMaxSize;
}
}
private static Vector2 ClampVectorMinimum( Vector2 value, float minimum )
{
return new Vector2(
MathF.Max( value.x, minimum ),
MathF.Max( value.y, minimum )
);
}
}