OMRRecoilPattern.cs
using Sandbox;
using System;
/// <summary>
/// Reusable authored recoil trajectory.
///
/// The curves describe cumulative aim displacement across the whole spray:
/// - Vertical Rise is positive-up, in degrees.
/// - Horizontal Drift is signed yaw displacement, in degrees.
///
/// Runtime recoil is derived from the difference between consecutive samples,
/// so designers can draw the desired overall spray path instead of entering a
/// pitch/yaw delta for every individual shot.
/// </summary>
[AssetType(
Name = "OMR Recoil Pattern",
Extension = "omrcoil",
Category = "One More Round/Weapons"
)]
public sealed partial class OMRRecoilPattern : GameResource
{
[Group( "Pattern" ), Range( 1, 200 ), Description( "Number of shots represented by the complete 0..1 curve range." )]
public int ShotCount { get; set; } = 30;
[Group( "Trajectory" ), Title( "Vertical Rise" )]
[Description( "Cumulative upward aim displacement in degrees. Keep the curve near 0 at time 0; larger values mean the spray has climbed farther upward." )]
[TimeRange( 0f, 1f )]
[ValueRange( 0f, 16f )]
public Curve VerticalRise { get; set; } = new Curve(
new Curve.Frame( 0f, 0f ),
new Curve.Frame( 1f, 9f )
);
[Group( "Trajectory" ), Title( "Horizontal Drift" )]
[Description( "Cumulative horizontal yaw displacement in degrees. Positive and negative values move the trajectory to opposite sides." )]
[TimeRange( 0f, 1f )]
[ValueRange( -2f, 2f )]
public Curve HorizontalDrift { get; set; } = new Curve(
new Curve.Frame( 0f, 0f ),
new Curve.Frame( 1f, 0f )
);
/// <summary>
/// Returns the gameplay pitch/yaw kick for a zero-based shot index.
/// Negative pitch moves aim upward in the current OMR convention.
/// </summary>
public Vector2 GetKickForShot( int shotIndex, bool loop )
{
int count = Math.Max( ShotCount, 1 );
int index;
if ( loop )
{
index = PositiveModulo( shotIndex, count );
}
else
{
// Preserve the previous list-pattern behavior: after the authored
// pattern is exhausted, keep repeating the final authored kick.
index = Math.Min( Math.Max( shotIndex, 0 ), count - 1 );
}
float previousProgress = index / (float)count;
float currentProgress = (index + 1) / (float)count;
float previousRise = VerticalRise.Evaluate( previousProgress );
float currentRise = VerticalRise.Evaluate( currentProgress );
float previousDrift = HorizontalDrift.Evaluate( previousProgress );
float currentDrift = HorizontalDrift.Evaluate( currentProgress );
float riseDelta = currentRise - previousRise;
float yawDelta = currentDrift - previousDrift;
return new Vector2(
-riseDelta,
yawDelta
);
}
/// <summary>
/// Cumulative trajectory sample, useful for diagnostics and future editor
/// previews. X = upward rise in degrees, Y = horizontal drift in degrees.
/// </summary>
public Vector2 EvaluateTrajectory( float progress )
{
float t = MathX.Clamp( progress, 0f, 1f );
return new Vector2(
VerticalRise.Evaluate( t ),
HorizontalDrift.Evaluate( t )
);
}
private static int PositiveModulo( int value, int modulus )
{
if ( modulus <= 0 )
return 0;
int result = value % modulus;
return result < 0 ? result + modulus : result;
}
}