HandedCoverReview.cs
using Sandbox;
namespace PaintballBaddies;

/// <summary>Opt-in actual-cabinet test of both right-handed firing corners.</summary>
public sealed class HandedCoverReview : Component
{
    public List<string> Results {get;}=new();
    public bool Done {get;private set;}
    public int Stage {get;private set;}
    public float PauseAtStage {get;set;}=-1;
    PlayerController player;CoverController cover;PaintballMarker marker;
    Vector3 oldPosition,origin;Angles oldAngles;bool oldLook;float elapsed;
    int side=1,angle;
    void Check(bool ok,string label){Results.Add((ok ? "PASS " : "FAIL ")+label);Log.Info(Results.Last());}
    protected override void OnStart()
    {
        Scene.GetAllComponents<PaintballControls>().First().StartPractice();
        player=Scene.GetAllComponents<PlayerController>().First();cover=player.Components.Get<CoverController>();marker=player.Components.Get<PaintballMarker>();
        oldPosition=player.WorldPosition;oldAngles=player.EyeAngles;oldLook=player.UseLookControls;
        player.UseLookControls=false;cover.Leave();cover.TestInput=true;Place();
    }
    void Place(){cover.TestAim=false;marker.ReviewAimOverride=false;player.WorldPosition=new(15,side*24,2);player.EyeAngles=Angles.Zero;player.Body.Velocity=Vector3.Zero;elapsed=0;}
    protected override void OnUpdate()
    {
        if(Done)return;elapsed+=Time.Delta;
        if(elapsed<.9f)return;elapsed=0;
        if(Stage==0){Check(cover.TryEnter(),$"side {side} cabinet attachment");Stage=1;return;}
        if(Stage==1){Check(cover.CornerAvailable && !cover.LowCover,$"side {side} standing edge detected");origin=player.WorldPosition;cover.TestAim=true;marker.ReviewAimOverride=true;Stage=2;return;}
        if(Stage==2)
        {
            var eye=PaintballFlight.Trace(Scene,player.EyePosition,marker.Muzzle,player.GameObject);
            var lane=PaintballFlight.Trace(Scene,marker.Muzzle,marker.Muzzle+player.EyeAngles.ToRotation().Forward*120,player.GameObject);
            var camera=Scene.Camera;var sight=Scene.Trace.Ray(camera.WorldPosition,camera.WorldPosition+camera.WorldRotation.Forward*180).IgnoreGameObjectHierarchy(player.GameObject).WithoutTags("paintball_debris").Run();
            Check(cover.Peeking && !player.IsDucking,$"side {side} angle {angle} standing peek");
            Check(!eye.Hit && !lane.Hit,$"side {side} angle {angle} actual gun clears cabinet ({eye.GameObject?.Name}, {lane.GameObject?.Name})");
            Check(!sight.Hit,$"side {side} angle {angle} camera sight clears cabinet ({sight.GameObject?.Name})");
            Check(marker.FireAt(marker.Muzzle+player.EyeAngles.ToRotation().Forward*400),$"side {side} angle {angle} firing accepted");
            if(angle<2){angle++;player.EyeAngles=new(0,angle==1 ? 12 : -12,0);return;}
            Stage=3;if(PauseAtStage==side){Scene.TimeScale=0;PauseAtStage=-1;}return;
        }
        if(Stage==3){cover.TestAim=false;marker.ReviewAimOverride=false;Stage=4;return;}
        Check(cover.Attached&&!cover.Peeking&&(player.WorldPosition-origin).Length<2,$"side {side} returns to original shelter");
        cover.Leave();Check(player.UseInputControls,$"side {side} leave restores input");
        if(side==1){side=-1;angle=0;Stage=0;Place();}else Done=true;
    }
    protected override void OnDestroy()
    {
        Scene.TimeScale=1;
        if(cover.IsValid()){cover.Leave();cover.TestInput=false;cover.TestAim=false;}
        if(marker.IsValid())marker.ReviewAimOverride=null;
        if(player.IsValid()){player.WorldPosition=oldPosition;player.EyeAngles=oldAngles;player.UseLookControls=oldLook;player.Body.Velocity=Vector3.Zero;}
    }
}