WeaponRuntimeProbe.cs
using Sandbox;
using System.Text;
using System.Linq;
/// <summary>
/// Low-frequency weapon diagnostic snapshots used while the reusable weapon
/// framework is being migrated. This component intentionally does no gameplay
/// work and is safe to remove/disable from production prefabs.
/// </summary>
public sealed class WeaponRuntimeProbe : Component
{
[Property]
public BaseCombatWeapon Weapon { get; set; }
[Property]
public bool EnabledDiagnostics { get; set; } = true;
[Property]
public float FirstSnapshotDelay { get; set; } = 0.25f;
[Property]
public float SecondSnapshotDelay { get; set; } = 1.5f;
[Property]
public float FinalSnapshotDelay { get; set; } = 4.0f;
private float _elapsed;
private bool _firstLogged;
private bool _secondLogged;
private bool _finalLogged;
protected override void OnStart()
{
Weapon ??= Components.Get<BaseCombatWeapon>();
if ( Weapon is null )
{
Log.Warning( "WeaponRuntimeProbe could not find BaseCombatWeapon." );
return;
}
Log.Info(
$"[WEAPON PROBE] Started | Object:{GameObject.Name} | Proxy:{IsProxy} | NetworkOwner:{Network.OwnerId}"
);
}
protected override void OnUpdate()
{
if ( !EnabledDiagnostics || Weapon is null )
return;
_elapsed += Time.Delta;
if ( !_firstLogged && _elapsed >= FirstSnapshotDelay )
{
_firstLogged = true;
LogSnapshot( "EARLY" );
}
if ( !_secondLogged && _elapsed >= SecondSnapshotDelay )
{
_secondLogged = true;
LogSnapshot( "MID" );
}
if ( !_finalLogged && _elapsed >= FinalSnapshotDelay )
{
_finalLogged = true;
LogSnapshot( "FINAL" );
}
}
private void LogSnapshot( string stage )
{
var owner = Weapon.Owner;
var holder = Weapon.HolderRenderer;
bool hasHoldBoneTransform = false;
if ( holder is not null && !string.IsNullOrWhiteSpace( Weapon.HoldBone ) )
{
hasHoldBoneTransform = holder.TryGetBoneTransform( Weapon.HoldBone, out _ );
}
string ownerName = owner is not null ? owner.GameObject.Name : "NULL";
string holderName = holder is not null ? holder.GameObject.Name : "NULL";
string worldPrefabName = Weapon.WorldModelPrefab is not null ? Weapon.WorldModelPrefab.Name : "NULL";
string worldModelName = Weapon.WorldModel is not null ? Weapon.WorldModel.Name : "NULL";
string viewPrefabName = Weapon.ViewModelPrefab is not null ? Weapon.ViewModelPrefab.Name : "NULL";
string viewModelName = Weapon.ViewModel is not null ? Weapon.ViewModel.Name : "NULL";
string weaponModelName = Weapon.WeaponModel is not null ? Weapon.WeaponModel.GameObject.Name : "NULL";
Log.Info( $"[WEAPON PROBE] ===== {stage} =====" );
Log.Info( $"[WEAPON PROBE] WeaponObject:{GameObject.Name} | Proxy:{IsProxy} | NetworkOwner:{Network.OwnerId}" );
Log.Info( $"[WEAPON PROBE] Owner:{ownerName} | IsHeld:{Weapon.IsHeld} | Clip:{Weapon.Clip1}/{Weapon.ClipMaxSize} | Reloading:{Weapon.IsReloading}" );
Log.Info( $"[WEAPON PROBE] Fire | Automatic:{Weapon.PrimaryAutomatic} | Delay:{Weapon.PrimaryDelay:0.###} | Damage:{Weapon.Ballistics.Damage:0.##} | Pellets:{Weapon.Ballistics.Pellets} | Range:{Weapon.Ballistics.Range:0.##}" );
Log.Info( $"[WEAPON PROBE] Spread | Current:{Weapon.CurrentSpread} | Base:{Weapon.Ballistics.SpreadBase} | Growth:{Weapon.Ballistics.SpreadGrowth} | Recovery:{Weapon.Ballistics.SpreadRecovery:0.###}" );
if ( Weapon is OneMoreRoundWeapon omrWeapon )
{
string definitionName = omrWeapon.UsesWeaponDefinition
? omrWeapon.Definition.ResourceName
: "LEGACY PREFAB VALUES";
Log.Info( $"[WEAPON PROBE] OMR | ID:{omrWeapon.WeaponId} | Name:{omrWeapon.DisplayName} | Archetype:{omrWeapon.WeaponArchetype}" );
Log.Info( $"[WEAPON PROBE] Definition:{definitionName} | Revision:{omrWeapon.StatRevision} | ADS:{omrWeapon.IsAiming} | Action:{omrWeapon.ActionState} | AccuracyFlags:{omrWeapon.AccuracyFlags}" );
Log.Info( $"[WEAPON PROBE] Runtime | ShotsSinceReset:{omrWeapon.PrimaryShotsFiredSinceReset} | ClaimAccepted:{omrWeapon.AcceptedShotClaims} | ClaimRejected:{omrWeapon.RejectedShotClaims}" );
string lastPresentationEvent = omrWeapon.LastPresentationEvent?.ToString() ?? "None";
Log.Info( $"[WEAPON PROBE] Presentation | Events:{omrWeapon.PresentationEventCount} | Last:{lastPresentationEvent} | LastAt:{omrWeapon.LastPresentationEventTime:0.###}" );
Log.Info( $"[WEAPON PROBE] Modifiers:{DescribeModifiers( omrWeapon )}" );
OMRWeaponStats baseStats = omrWeapon.BaseStats;
OMRWeaponStats resolvedStats = omrWeapon.ResolvedStats;
if ( baseStats is not null && resolvedStats is not null )
{
Log.Info(
$"[WEAPON PROBE] Base -> Resolved | Damage:{baseStats.Damage:0.##}->{resolvedStats.Damage:0.##} | Delay:{baseStats.PrimaryDelay:0.###}->{resolvedStats.PrimaryDelay:0.###} | Clip:{baseStats.ClipMaxSize}->{resolvedStats.ClipMaxSize} | Reload:{baseStats.ReloadTime:0.###}->{resolvedStats.ReloadTime:0.###}"
);
Log.Info(
$"[WEAPON PROBE] Base -> Resolved Spread | Base:{baseStats.SpreadBase}->{resolvedStats.SpreadBase} | Growth:{baseStats.SpreadGrowth}->{resolvedStats.SpreadGrowth} | ADS:{baseStats.AimSpreadMultiplier:0.###}->{resolvedStats.AimSpreadMultiplier:0.###}"
);
Log.Info(
$"[WEAPON PROBE] Base -> Resolved Recoil | Pitch:{baseStats.RecoilPitch}->{resolvedStats.RecoilPitch} | Yaw:{baseStats.RecoilYaw}->{resolvedStats.RecoilYaw}"
);
}
Log.Info( $"[WEAPON PROBE] Handling | ADS FOV Scale:{omrWeapon.AimFovScale:0.###} | ADS Time:{omrWeapon.AimFovTransitionTime:0.###} | SprintToFire:{omrWeapon.SprintToFireDelay:0.###}" );
string opticName = omrWeapon.HasOpticProfile
? omrWeapon.OpticProfile.ResourceName
: "NONE";
string opticPresentation = omrWeapon.HasOpticProfile
? omrWeapon.OpticProfile.PresentationMode.ToString()
: "None";
Log.Info( $"[WEAPON PROBE] Optic | Profile:{opticName} | Presentation:{opticPresentation} | AimFraction:{omrWeapon.AimFraction:0.###} | AccuracyBlend:{omrWeapon.AimAccuracyBlend:0.###} | ScopeActive:{omrWeapon.IsScopePresentationActive} | SensScale:{omrWeapon.CurrentOpticSensitivityScale:0.###} | PeripheralFovScale:{omrWeapon.CurrentPeripheralAimFovScale:0.###} | ViewModelFovScale:{omrWeapon.CurrentViewModelAimFovScale:0.###}" );
OMRViewModelCamera viewModelCamera = Scene?
.GetAllComponents<OMRViewModelCamera>()
.FirstOrDefault( x => x is not null && x.IsValid );
if ( viewModelCamera is not null )
{
Log.Info(
$"[WEAPON PROBE] ViewModelCamera | FOV:{viewModelCamera.CurrentViewModelFieldOfView:0.##} | AimScale:{viewModelCamera.CurrentViewModelAimFovScale:0.###} | MatchSource:{viewModelCamera.MatchSourceFieldOfView}"
);
}
OMRScopePiPCamera pipCamera = Scene?
.GetAllComponents<OMRScopePiPCamera>()
.FirstOrDefault( x => x is not null && x.IsValid );
if ( pipCamera is not null )
{
CameraComponent scopeCamera = pipCamera.ScopeCamera;
Texture scopeTexture = pipCamera.OutputTexture;
Vector4? clipBounds = pipCamera.CurrentClipSpaceBounds;
string clipText = clipBounds.HasValue
? $"L{clipBounds.Value.x:0.###},D{clipBounds.Value.y:0.###},R{clipBounds.Value.z:0.###},U{clipBounds.Value.w:0.###}"
: "NONE";
Log.Info(
$"[WEAPON PROBE] ScopePiP | Rendering:{pipCamera.IsRendering} | " +
$"Texture:{scopeTexture?.Index ?? -1} | Resolution:{pipCamera.OutputResolution} | " +
$"OpticalRefFOV:{pipCamera.CurrentOpticalReferenceFieldOfView:0.##} | LensProjectionFOV:{pipCamera.CurrentLensProjectionFieldOfView:0.##} | " +
$"OpticalFOV:{pipCamera.CurrentScopeFieldOfView:0.##} | RenderFOV:{pipCamera.CurrentRenderFieldOfView:0.##} | " +
$"OpticalMagnification:{pipCamera.CurrentScopeMagnification:0.##}x | LensFraction:{pipCamera.CurrentLensScreenFraction:0.###} | " +
$"LensMeasured:{pipCamera.IsLensScreenFractionMeasured} | LensSource:{pipCamera.CurrentLensMeasurementSource} | LensRaw:{pipCamera.CurrentRawMeasuredLensScreenFraction:0.###} | " +
$"TargetApparent:{pipCamera.CurrentTargetApparentMagnification:0.##}x | RenderCrop:{pipCamera.CurrentRenderCropMagnification:0.##}x | " +
$"ExpectedApparent:{pipCamera.CurrentExpectedApparentMagnification:0.##}x | PresentationZoom:{pipCamera.PresentationZoomScale:0.##}x | " +
$"ClipBounds:{clipText} | EyeToPiP:{pipCamera.CurrentEyeToScopeCameraDistance:0.##} | EyeToOptic:{pipCamera.CurrentEyeToPhysicalOpticDistance:0.##} | " +
$"PiPPosBlend:{pipCamera.CurrentPiPPositionBlend:0.###} | EyeBoxAngle:{pipCamera.CurrentEyeBoxAngularError:0.##}deg | " +
$"EyeBoxAlign:{pipCamera.CurrentEyeBoxAlignment:0.###} | EyeBoxOffset:{pipCamera.CurrentEyeBoxOffset} | " +
$"ManualRT:{pipCamera.LastManualRenderSucceeded} | CameraEnabled:{scopeCamera?.Enabled ?? false} | Priority:{scopeCamera?.Priority ?? -1} | " +
$"CameraRawFOV:{(scopeCamera?.FieldOfView ?? 0f):0.##} | CameraViewFOV:{(scopeCamera?.View.FieldOfView ?? 0f):0.##} | " +
$"Anchor:{pipCamera.ResolvedCameraAnchor?.Name ?? "MAIN_CAMERA"}"
);
}
Log.Info( $"[WEAPON PROBE] FireAnim | EffectiveScale:{omrWeapon.EffectiveViewModelFireScale:0.###} | Locked:{omrWeapon.IsViewModelFireScaleLocked} | Remaining:{omrWeapon.ViewModelFireScaleLockRemaining:0.###}" );
string cycleName = omrWeapon.HasCycleProfile
? omrWeapon.CycleProfile.ResourceName
: "NONE";
string cycleAimPolicy = omrWeapon.HasCycleProfile
? omrWeapon.CycleProfile.AimPolicy.ToString()
: "None";
Log.Info( $"[WEAPON PROBE] Cycle | Profile:{cycleName} | AimPolicy:{cycleAimPolicy} | StartedADS:{omrWeapon.CycleStartedWhileAiming} | AimAllowedNow:{omrWeapon.CanAimDuringCurrentCycle()} | Chambered:{omrWeapon.IsChambered} | Pending:{omrWeapon.IsCyclePendingOrActive} | MechanicalProgress:{omrWeapon.CycleProgress:0.###} | PresentationActive:{omrWeapon.IsCyclePresentationActive} | PresentationProgress:{omrWeapon.CyclePresentationProgress:0.###}" );
if ( omrWeapon.TryGetLocomotionSnapshot( out WeaponLocomotionSnapshot locomotion ) )
{
Log.Info(
$"[WEAPON PROBE] Locomotion | Ground:{locomotion.IsGrounded} Air:{locomotion.IsAirborne} Crouch:{locomotion.IsCrouched} Slide:{locomotion.IsSliding} WantsSprint:{locomotion.WantsSprint} Sprint:{locomotion.IsSprinting}"
);
Log.Info(
$"[WEAPON PROBE] Motion | Speed:{locomotion.HorizontalSpeed:0.0}/{locomotion.ReferenceMoveSpeed:0.0} Normalized:{locomotion.NormalizedMoveSpeed:0.###} Vertical:{locomotion.VerticalVelocity:0.0} Accel:{locomotion.Acceleration} Landing:{locomotion.LandingImpulse:0.0}"
);
}
Log.Info( $"[WEAPON PROBE] Accuracy | Model:{omrWeapon.MovementAccuracyModel} | MoveBlend:{omrWeapon.CurrentMovementAccuracyBlend:0.###} | Effective:{omrWeapon.CurrentAccuracyMultiplier:0.###} | Move:{omrWeapon.MovingSpreadMultiplier:0.###} | Crouch:{omrWeapon.CrouchedSpreadMultiplier:0.###} | Air:{omrWeapon.AirborneSpreadMultiplier:0.###} | Slide:{omrWeapon.SlidingSpreadMultiplier:0.###} | ADS:{omrWeapon.AimSpreadMultiplier:0.###}" );
string recoilPatternSource = omrWeapon.RecoilPatternAsset is not null && omrWeapon.RecoilPatternAsset.IsValid
? $"Asset:{omrWeapon.RecoilPatternAsset.ResourceName}({omrWeapon.RecoilPatternAsset.ShotCount} shots)"
: $"LegacySteps:{omrWeapon.RecoilPattern?.Count ?? 0}";
Log.Info( $"[WEAPON PROBE] Recoil | Model:{omrWeapon.RecoilModel} | Source:{recoilPatternSource} | Scale:{omrWeapon.RecoilPatternScale} | Index:{omrWeapon.CurrentRecoilPatternIndex} | LastKick:{omrWeapon.LastGameplayRecoilKick}" );
Log.Info( $"[WEAPON PROBE] Recoil Recovery | Enabled:{omrWeapon.RecoilRecoveryEnabled} | Active:{omrWeapon.IsRecoveringGameplayRecoil} | Remaining:{omrWeapon.RemainingGameplayRecoilRecovery} | Fraction:{omrWeapon.RecoilRecoveryFraction:0.###} | Delay:{omrWeapon.RecoilRecoveryDelay:0.###} | PitchSpeed:{omrWeapon.RecoilRecoveryPitchSpeed:0.###} | YawSpeed:{omrWeapon.RecoilRecoveryYawSpeed:0.###}" );
}
Log.Info( $"[WEAPON PROBE] HoldBone:{Weapon.HoldBone} | HolderRenderer:{holderName} | CreateBoneObjects:{(holder is not null ? holder.CreateBoneObjects : false)} | HoldBoneTransform:{hasHoldBoneTransform}" );
Log.Info( $"[WEAPON PROBE] WorldModelPrefab:{worldPrefabName} | WorldModel:{worldModelName}" );
Log.Info( $"[WEAPON PROBE] ViewModelPrefab:{viewPrefabName} | ViewModel:{viewModelName} | WeaponModel:{weaponModelName}" );
Log.Info( "[WEAPON PROBE] ==================" );
}
private static string DescribeModifiers( OneMoreRoundWeapon weapon )
{
if ( weapon.ActiveStatModifiers is null || weapon.ActiveStatModifiers.Count == 0 )
return "NONE";
StringBuilder builder = new();
for ( int i = 0; i < weapon.ActiveStatModifiers.Count; i++ )
{
IOMRWeaponStatModifier modifier = weapon.ActiveStatModifiers[i];
if ( modifier is null )
continue;
if ( builder.Length > 0 )
builder.Append( " -> " );
builder.Append( modifier.ModifierId );
builder.Append( "@" );
builder.Append( modifier.Priority );
}
return builder.Length > 0
? builder.ToString()
: "NONE";
}
}