OMRViewModelAimAlignment.cs
using Sandbox;
using System;
/// <summary>
/// Attachment-aware ADS correction applied from OneMoreRoundWeapon.PlaceViewModel.
///
/// IMPORTANT: this class never samples a world-space bone after the viewmodel root has
/// been moved. OneMoreRoundWeapon supplies a root-relative lens pose captured before
/// BaseCombatWeapon.PlaceViewModel. That makes centering independent of the previous
/// frame's root transform and avoids the A/B oscillation that was visible while strafing.
/// </summary>
[Title( "OMR Viewmodel Aim Alignment" )]
public sealed class OMRViewModelAimAlignment : Component
{
[Property, Group( "Diagnostics" )]
public bool LogCorrection { get; set; } = false;
public float CurrentBlend { get; private set; }
public Vector3 CurrentCorrection { get; private set; }
public bool HasCapturedEyeRelief { get; private set; }
public bool EyeReliefGuardActive { get; private set; }
public float CapturedEyeReliefDepth { get; private set; }
public float CurrentEyeReliefDepth { get; private set; }
private float _nextLogAt;
public void ApplyAlignment(
OneMoreRoundWeapon weapon,
CameraComponent camera,
in CameraView view,
in OMRViewModelBoneAnchor.RootRelativePose alignmentPose
)
{
CurrentBlend = 0f;
CurrentCorrection = Vector3.Zero;
EyeReliefGuardActive = false;
if (
weapon is null ||
!weapon.IsValid ||
camera is null ||
!camera.IsValid ||
Scene is null ||
Scene.Camera != camera ||
weapon.ViewModel != GameObject
)
{
ResetEyeReliefCapture();
return;
}
OMROpticProfile profile = weapon.ActiveOpticProfile;
if (
profile is null ||
!profile.IsValid ||
!profile.EnableViewModelAimAlignment
)
{
ResetEyeReliefCapture();
return;
}
if ( !profile.ProtectViewModelEyeRelief && HasCapturedEyeRelief )
{
ResetEyeReliefCapture();
}
float blend = ResolveBlend( weapon.AimFraction, profile );
CurrentBlend = blend;
float alignmentStart = MathX.Clamp(
profile.ViewModelAimAlignmentStart,
0f,
1f
);
if ( weapon.AimFraction <= alignmentStart + 0.0001f )
{
ResetEyeReliefCapture();
}
if ( blend <= 0.0001f )
return;
// BaseCombatWeapon has already pinned the root to this frame's final CameraView.
// Rebuild the current optical-center position from the CURRENT root plus the
// root-relative animated pose captured before the root moved.
Vector3 predictedAnchorWorld = GameObject.WorldTransform.PointToWorld(
alignmentPose.Position
);
Rotation cameraRotation = view.Rotation;
Vector3 cameraPosition = view.Position;
Vector3 cameraToAnchor = predictedAnchorWorld - cameraPosition;
float currentRight = Vector3.Dot(
cameraToAnchor,
cameraRotation.Right
);
float currentUp = Vector3.Dot(
cameraToAnchor,
cameraRotation.Up
);
float currentForward = Vector3.Dot(
cameraToAnchor,
cameraRotation.Forward
);
CurrentEyeReliefDepth = currentForward;
float depthCorrection = profile.ViewModelAimDepthOffset;
if ( profile.ProtectViewModelEyeRelief )
{
float captureThreshold = MathX.Clamp(
profile.ViewModelEyeReliefCaptureThreshold,
0f,
1f
);
if (
!HasCapturedEyeRelief &&
weapon.AimFraction >= captureThreshold
)
{
HasCapturedEyeRelief = true;
CapturedEyeReliefDepth = currentForward;
}
if ( HasCapturedEyeRelief )
{
float tolerance = MathF.Max(
profile.ViewModelEyeReliefTolerance,
0f
);
float minimumDepth =
CapturedEyeReliefDepth - tolerance;
if ( currentForward < minimumDepth )
{
depthCorrection += minimumDepth - currentForward;
EyeReliefGuardActive = true;
}
}
}
Vector2 target = profile.ViewModelAimScreenOffset;
Vector3 fullCorrection =
cameraRotation.Right * (target.x - currentRight) +
cameraRotation.Up * (target.y - currentUp) +
cameraRotation.Forward * depthCorrection;
Vector3 applied = fullCorrection * blend;
// Always rebuild from the canonical camera baseline. Never += the previous
// root transform, and never measure against a helper that we just moved.
GameObject.WorldPosition = cameraPosition + applied;
GameObject.WorldRotation = cameraRotation;
CurrentCorrection = applied;
if ( LogCorrection && RealTime.Now >= _nextLogAt )
{
_nextLogAt = RealTime.Now + 0.5f;
Log.Info(
$"[OMR VM ALIGN] Weapon:{weapon.WeaponId} | " +
$"Aim:{weapon.AimFraction:0.###} | Blend:{blend:0.###} | " +
$"AnchorPlane:({currentRight:0.###},{currentUp:0.###}) | " +
$"Depth:{currentForward:0.###} | EyeBase:{(HasCapturedEyeRelief ? CapturedEyeReliefDepth : -1f):0.###} | " +
$"EyeGuard:{EyeReliefGuardActive} | Correction:{applied}"
);
}
}
private void ResetEyeReliefCapture()
{
HasCapturedEyeRelief = false;
EyeReliefGuardActive = false;
CapturedEyeReliefDepth = 0f;
}
private static float ResolveBlend(
float aimFraction,
OMROpticProfile profile
)
{
float start = MathX.Clamp(
profile.ViewModelAimAlignmentStart,
0f,
1f
);
float full = MathX.Clamp(
profile.ViewModelAimAlignmentFull,
start,
1f
);
float t;
if ( full <= start + 0.0001f )
{
t = aimFraction >= full ? 1f : 0f;
}
else
{
t = MathX.Clamp(
(aimFraction - start) / (full - start),
0f,
1f
);
}
return t * t * (3f - 2f * t);
}
}