LowCoverClearanceReview.cs
using Sandbox;
namespace PaintballBaddies;

/// <summary>Temporary native checks of real arena geometry, from both local X faces.</summary>
public sealed class LowCoverClearanceReview : Component
{
    public List<string> Results {get;}=new();
    public bool Done {get;private set;}
    public int Index {get;private set;}
    public int Count=>surfaces?.Length??0;
    CoverSurface[] surfaces;PlayerController player;CoverController cover;PaintballMarker marker;
    Vector3 oldPosition,direction;Angles oldAngles;bool oldLook;float elapsed;int phase,side;
    System.IDisposable hook;
    protected override void OnStart()
    {
        Scene.GetAllComponents<PaintballControls>().First().StartPractice();
        player=MultiplayerSession.LocalPlayer(Scene);cover=player.Components.Get<CoverController>();marker=player.Components.Get<PaintballMarker>();
        oldPosition=player.WorldPosition;oldAngles=player.EyeAngles;oldLook=player.UseLookControls;
        player.UseLookControls=false;cover.TestInput=true;
        hook=Scene.AddHook(GameObjectSystem.Stage.StartUpdate,-45,()=>{Input.AnalogMove=Vector3.Zero;Input.AnalogLook=Angles.Zero;Input.SetAction("attack1",false);Input.SetAction("attack2",false);},nameof(LowCoverClearanceReview),"Temporary cover geometry check");
        surfaces=Scene.GetAllComponents<CoverSurface>().Where(x=>!x.StandingOnly&&x.Top-x.WorldPosition.z>=30&&x.Top-x.WorldPosition.z<67
            &&(x.Components.Get<BoxCollider>().IsValid()||x.Components.Get<ModelCollider>()?.Model is {IsValid:true})).OrderBy(x=>x.GameObject.Name).ToArray();
        if(Count==0){Done=true;return;}Place();
    }
    void Place()
    {
        cover.Leave();cover.TestAim=false;marker.ReviewAimOverride=false;
        var surface=surfaces[Index];var box=surface.Components.Get<BoxCollider>();
        var bounds=surface.Components.Get<ModelCollider>()?.Model.Bounds??default;
        var localCenter=box.IsValid()?box.Center:bounds.Center;
        var localSize=box.IsValid()?box.Scale:bounds.Size;
        direction=surface.WorldRotation.Forward*(side==0?1:-1);
        var center=surface.WorldTransform.PointToWorld(localCenter);
        player.WorldPosition=(center-direction*(localSize.x*surface.WorldScale.x*.5f+42)).WithZ(surface.WorldPosition.z+2);
        player.EyeAngles=Rotation.LookAt(direction).Angles();player.Body.Velocity=Vector3.Zero;player.WishVelocity=Vector3.Zero;
        elapsed=0;phase=0;
    }
    void Check(bool ok,string text)=>Results.Add((ok?"PASS ":"FAIL ")+surfaces[Index].GameObject.Name+" face "+side+" "+text);
    protected override void OnUpdate()
    {
        if(Done)return;elapsed+=Time.Delta;
        var surface=surfaces[Index];
        if(phase==0&&elapsed>.4f)
        {
            bool attached=cover.TryEnter()&&cover.ReservedSurface==surface&&cover.LowCover;
            Check(attached,"attaches as low cover");
            if(!attached)
            {
                var probe=Scene.Trace.Ray(player.WorldPosition+Vector3.Up*28,player.WorldPosition+Vector3.Up*28+direction*70).IgnoreGameObjectHierarchy(player.GameObject).Run();
                Results.Add($"INFO {surface.GameObject.Name} root={player.WorldPosition} airborne={player.IsAirborne} probe={probe.GameObject?.Name} normal={probe.Normal} distance={probe.Distance} selected={cover.ReservedSurface?.GameObject.Name}");
                Advance();return;
            }phase=1;elapsed=0;
        }
        else if(phase==1&&elapsed>.55f)
        {
            var sheltered=Scene.Trace.Ray(player.EyePosition,player.EyePosition+direction*180).IgnoreGameObjectHierarchy(player.GameObject).Run();
            Check(player.IsDucking&&sheltered.GameObject==surface.GameObject,"crouched eye protected");
            cover.TestAim=true;marker.ReviewAimOverride=true;phase=2;elapsed=0;
        }
        else if(phase==2&&elapsed>1)
        {
            // Other distant scenery is irrelevant; reject this cover blocking either barrel segment.
            var eye=PaintballFlight.Trace(Scene,player.EyePosition,marker.Muzzle,player.GameObject);
            var lane=PaintballFlight.Trace(Scene,marker.Muzzle,marker.Muzzle+direction*160,player.GameObject);
            Check(cover.Peeking&&!player.IsDucking&&eye.GameObject!=surface.GameObject&&lane.GameObject!=surface.GameObject,
                $"standing shot clears cover (top {surface.Top:F1}, muzzle {marker.Muzzle.z:F1}, lane {lane.GameObject?.Name})");
            Check(marker.FireAt(marker.Muzzle+direction*500),"standing shot accepted");Advance();
        }
    }
    void Advance(){side++;if(side==2){Index++;side=0;}if(Index>=Count){Done=true;cover.Leave();return;}Place();}
    protected override void OnDestroy()
    {
        hook?.Dispose();Input.ReleaseActions();
        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;player.WishVelocity=Vector3.Zero;}
    }
}