CoveredAimChecks.cs
using Sandbox;
using System;

namespace PaintballBaddies;

/// <summary>Opt-in live covered-player muzzle and pitch checks; removed after the run.</summary>
public sealed class CoveredAimChecks : Component
{
    [Property] public string Character { get; set; } = "mei";
    private PlayerController player;
    private PaintballMarker weapon;
    private float elapsed;
    private int stage;
    private float reloadStarted;
    private readonly float[] pitches = { -30, 30, 0 };
    private void Check( string label, bool pass, string detail ) => Log.Info( $"COVERED_AIM {(pass ? "PASS" : "FAIL")} {label}: {detail}" );

    protected override void OnStart()
    {
        player = Scene.GetAllComponents<PlayerController>().First();
        weapon = player.Components.Get<PaintballMarker>();
        player.UseInputControls = player.UseLookControls = false;
        weapon.AcceptInput = false;
    }

    protected override void OnUpdate()
    {
        elapsed += Time.Delta;
        if ( stage == 0 && elapsed > 1 )
        {
            player.Renderer.Model = Model.Load( $"models/characters/{Character}_protected_morph/{Character}_protected.vmdl" );
            player.WorldPosition = new Vector3( 0, 0, 410 );
            player.Body.MotionEnabled = false;
            player.EyeAngles = new Angles( pitches[0], 0, 0 );
            Check( "covered model loaded", player.Renderer.Model.IsValid(), player.Renderer.Model.Name );
            stage = 1;
        }
        else if ( stage >= 1 && stage <= 3 && elapsed > stage + 1.2f )
        {
            var marker = Scene.GetAllComponents<ModelRenderer>().First( x => x.GameObject.Name == "VX-9 training marker" );
            var target = player.WorldPosition + Vector3.Forward * 500 + Vector3.Up * 70;
            Check( $"shot accepted {stage}", weapon.FireAt( target ), $"pitch={pitches[stage-1]}" );
            var barrel = marker.WorldTransform.PointToWorld( PaintballMarker.MuzzleOffset );
            var ball = Scene.GetAllComponents<ModelRenderer>().Where( x => x.GameObject.Name == "Paintball" )
                .OrderBy( x => (x.WorldPosition - barrel).Length ).FirstOrDefault();
            var distance = ball.IsValid() ? (ball.WorldPosition - barrel).Length : float.MaxValue;
            Check( $"paintball starts at barrel {stage}", distance < .01f, $"distance={distance} inches" );
            var actual = marker.WorldRotation.Angles().pitch;
            Check( $"marker follows pitch {stage}", MathF.Abs( actual - pitches[stage-1] ) < 1, $"actual={actual} expected={pitches[stage-1]}" );
            stage++;
            if ( stage <= 3 ) player.EyeAngles = new Angles( pitches[stage-1], 0, 0 );
            else
            {
                weapon.Reload(); reloadStarted = elapsed;
                Check( "authored reload duration", MathF.Abs( weapon.ActiveReloadDuration - 2.4f ) < .001f, $"duration={weapon.ActiveReloadDuration}" );
            }
        }
        else if ( stage == 4 && elapsed - reloadStarted > 1.8f )
        {
            Check( "reload stays blocked beyond legacy timing", weapon.Ammo == 37 && weapon.ReloadRemaining > 0 && !weapon.FireAt( player.WorldPosition + Vector3.Forward * 500 ), $"ammo={weapon.Ammo} remaining={weapon.ReloadRemaining}" );
            var presentation = player.Components.Get<ReloadPresentationTrack>();
            Check( "character reload presentation active", presentation.IsValid() && presentation.TrackPath == $"animations/{Character}_reload_presentation.json"
                && Scene.GetAllComponents<ModelRenderer>().Any( x => x.GameObject.Name == "Reload presentation 0" ), $"{Character} track and pod spawned" );
            Check( "open hopper equipped", Scene.GetAllComponents<ModelRenderer>().Any( x => x.GameObject.Name == "VX-9 training marker"
                && x.Model?.Name == "models/weapons/vx9_speed_feed/vx9_speed_feed.vmdl" ), "speed-feed marker" );
            stage = 5;
        }
        else if ( stage == 5 && elapsed - reloadStarted > 2.6f )
        {
            Check( "authored reload completes and conserves ammo", weapon.Ammo == 40 && weapon.Reserve == 157 && weapon.ReloadRemaining == 0, $"ammo={weapon.Ammo} reserve={weapon.Reserve}" );
            Check( "gear stays docked after reload", player.Components.Get<ReloadPresentationTrack>() is { Docked: true }
                && Scene.GetAllComponents<ModelRenderer>().Any( x => x.GameObject.Name == "Reload presentation 0" ), "belt pod persists" );
            player.Renderer.Model = Model.Load( "models/characters/viper/viper_helmet_hair_preview.vmdl" );
            stage = 6;
        }
        else if ( stage == 6 && elapsed - reloadStarted > 3.1f )
        {
            Check( "gear removed on body change", !player.Components.Get<ReloadPresentationTrack>().IsValid()
                && !Scene.GetAllComponents<ModelRenderer>().Any( x => x.GameObject.Name.StartsWith( "Reload presentation " ) ), "no orphan props" );
            stage = 7; Log.Info( "COVERED_AIM COMPLETE" );
        }
    }
}