NetworkReview.cs
using Sandbox;
using System;
namespace PaintballBaddies;

/// <summary>Opt-in, temporary editor fixture. Never placed in the playable scene.</summary>
public sealed partial class NetworkReview : Component
{
    public Dictionary<Guid,string> Reports {get;}=new();
    private int recoveryTaps;
    private float nextTap;
    private float nextReport,walkTime;
    public string SoundResult {get;private set;}="";
    private IDisposable walkHook;
    private IDisposable aimHook;
    private float aimTime;
    private bool aimPrevious,aimInjected;
    private bool controlled;
    private IDisposable carryAuditHook;
    public int CarrySamples {get;private set;}
    public float CarryMaxError {get;private set;}
    public string CarryWorst {get;private set;}="";
    private int ownerEchoSamples,ownerEchoVisuals,remoteBallVisuals,localPredictionVisuals;
    protected override void OnStart()
    {
        carryAuditHook=Scene.AddHook(GameObjectSystem.Stage.FinishUpdate,200,AuditCarry,nameof(NetworkReview),"Measure final shield hand alignment");
        walkHook=Scene.AddHook(GameObjectSystem.Stage.StartFixedUpdate,-50,()=>
        {
            if(controlled){Input.AnalogMove=Vector3.Zero;Input.AnalogLook=Angles.Zero;}
            if(soak && MultiplayerSession.Find(Scene)?.InRound==true)
            {
                Input.AnalogMove=RealTime.NowDouble%8<4 ? Vector3.Right : Vector3.Left;
                return;
            }
            if(walkTime<=0){if(controlled)Input.SetAction("forward",false);return;}
            walkTime-=Time.Delta;Input.AnalogMove=Vector3.Forward;Input.SetAction("forward",true);
        },nameof(NetworkReview),"Temporary owner movement test");
        aimHook=Scene.AddHook(GameObjectSystem.Stage.StartUpdate,-40,()=>
        {
            if(controlled)
            {
                Input.AnalogLook=Angles.Zero;Input.AnalogMove=Vector3.Zero;
                foreach(var action in new[]{"attack1","cover","vault","shoulder","reload","use"})Input.SetAction(action,false);
                if(!aimInjected){Input.SetAction("attack2",false);Input.SetLastAction("attack2",false);}
            }
            if(!aimInjected)return;
            bool down=aimTime>0;aimTime-=Time.Delta;
            Input.SetAction("attack2",down);Input.SetLastAction("attack2",aimPrevious);
            if(!down && !aimPrevious)aimInjected=false;
            aimPrevious=down;
        },nameof(NetworkReview),"Temporary owner aim press test");
    }
    protected override void OnDestroy()
    {
        walkHook?.Dispose();aimHook?.Dispose();carryAuditHook?.Dispose();
        Input.ReleaseActions();Input.AnalogMove=Vector3.Zero;Input.AnalogLook=Angles.Zero;
    }
    private void AuditCarry()
    {
        if(!controlled)return;
        foreach(var ball in Scene.GetAllComponents<OpponentPaintball>())
        {
            bool visible=ball.Components.GetAll<ModelRenderer>(FindMode.EverythingInSelfAndDescendants).Any();
            bool ownerEcho=!MultiplayerSession.Authority && ball.Shooter?.Components.Get<NetworkPawn>() is {IsProxy:false};
            if(ownerEcho){ownerEchoSamples++;if(visible)ownerEchoVisuals++;}
            else if(visible)remoteBallVisuals++;
        }
        localPredictionVisuals+=Scene.GetAllComponents<TrailRenderer>().Count(x=>x.GameObject.Parent?.Name=="Paintball");
        foreach(var shield in Scene.GetAllComponents<ArenaShield>())
        {
            if(!shield.Owner.IsValid() || shield.Stowed)continue;
            var body=shield.Owner.Components.Get<CitizenPlayerPresentation>()?.FootstepRenderer
                ?? shield.Owner.Components.Get<CitizenOpponentPresentation>()?.FootstepRenderer;
            if(!body.IsValid() || !body.TryGetBoneTransform("hand_L",out var hand))continue;
            CarrySamples++;
            var error=(hand.Position-shield.WorldTransform.PointToWorld(new Vector3(-5,8,0))).Length;
            if(error>CarryMaxError)
            {
                CarryMaxError=error;
                CarryWorst=Json.Serialize(new {Owner=shield.Owner.GameObject.Name,Hand=hand.Position.ToString(),
                    BoneObject=body.GetBoneObject("hand_L")?.WorldPosition.ToString(),Shield=shield.WorldPosition.ToString(),
                    shield.GripError,Model=body.Model?.Name});
            }
        }
    }
    protected override void OnUpdate()
    {
        UpdatePerformance();
        if(recoveryTaps>0)
        {
            nextTap-=Time.Delta;
            if(nextTap<=0){nextTap=.16f;recoveryTaps--;MultiplayerSession.LocalPlayer(Scene)?.Components.Get<CloseCombat>()?.RegisterRecoveryPress();}
        }
        nextReport-=Time.Delta;
        if(nextReport>0)return;
        nextReport=soak ? 2f : .2f;
        var local=MultiplayerSession.LocalPlayer(Scene);
        var session=MultiplayerSession.Find(Scene);
        Report(Json.Serialize(new {Revision="network-3",Host=Networking.IsHost,Local=Connection.Local.Id,LocalSeat=local?.Components.Get<NetworkPawn>()?.Seat,
            Menu=Scene.GetAllComponents<PaintballControls>().FirstOrDefault()?.Open,Scene.TimeScale,
            session?.Minutes,session?.InRound,session?.Status,session?.Remaining,session?.RoundId,
            Players=Scene.GetAllComponents<NetworkPawn>().Select(x=>new{x.Seat,x.ConnectionId,x.IsProxy,Position=x.WorldPosition.ToString(),
                CanCover=x.Components.Get<CoverController>()?.CanEnter,VaultReason=x.Components.Get<VaultController>()?.BlockReason,Airborne=x.Components.Get<PlayerController>()?.IsAirborne,
                x.AcceptedShots,x.RejectedShots,x.ContactState,x.RecoveryRequests,x.RecoveryAccepted,x.Covered,x.Peek,x.Vaulting,
                Input=x.Components.Get<PlayerController>().UseInputControls,Camera=x.Components.Get<PlayerController>().UseCameraControls,
                MotionEnabled=x.Components.Get<PlayerController>().Body.MotionEnabled,Velocity=x.Components.Get<PlayerController>().Velocity.ToString(),
                Ready=x.Components.Get<CitizenPlayerPresentation>()?.IsReady,
                Character=x.Components.Get<RosterSelection>()?.Selected,Name=x.Components.Get<PlayerProfile>()?.DisplayName,
                Hits=x.Components.Get<PaintballCombatant>().PaintHits,Landed=x.Components.Get<PaintballCombatant>().HitsLanded,
                Contacts=x.Components.Get<CloseCombat>().Contacts,Sounds=x.Components.Get<CloseCombat>().ImpactSounds,Attacking=x.Components.Get<CloseCombat>().Attacking,
                Paint=x.Components.Get<CharacterPaint>().Count,Down=x.Components.Get<CloseCombat>().Downed,Recovering=x.Components.Get<CloseCombat>().Recovering,Presses=x.Components.Get<CloseCombat>().RecoveryPresses,
                Muzzle=x.Components.Get<PaintballMarker>()?.Muzzle.ToString()}).ToArray(),
            Bots=Scene.GetAllComponents<NetworkBot>().Select(x=>new{x.Seat,x.IsProxy,Position=x.WorldPosition.ToString(),Shots=x.Components.Get<ArenaOpponent>().ShotsFired,
                Hits=x.Components.Get<PaintballCombatant>().PaintHits,Landed=x.Components.Get<PaintballCombatant>().HitsLanded}).ToArray(),
            Shields=Scene.GetAllComponents<ArenaShield>().Select(x=>new{x.Remaining,Owner=x.Owner?.GameObject.Name,x.PaintCount,x.GripError,x.Stowed,Position=x.WorldPosition.ToString()}).ToArray(),
            ShieldFragments=Scene.GetAllComponents<ShieldBreakEffect>().Sum(x=>x.Fragments),CarrySamples,CarryMaxError,CarryWorst,Performance=PerformanceReport(),
            MissingParticleModels=Scene.GetAllComponents<ParticleModelRenderer>().SelectMany(x=>x.Choices).Where(x=>x.Model is null || x.Model.IsError).Select(x=>x.Model?.Name ?? "null").Distinct().ToArray(),
            SoundResult,WorldPaint=PaintImpactSystem.Find(Scene)?.ActiveCount,MissingModels=Scene.GetAllComponents<ModelRenderer>().Where(r=>r.Model?.IsError==true).Select(r=>r.GameObject.Name+":"+r.Model.Name).Distinct().Take(12).ToArray(),
            Balls=Scene.GetAllComponents<OpponentPaintball>().Count(),Hud=Scene.GetAllComponents<TrainingHud>().Count(),
            ProjectilePresentation=new{OwnerEchoSamples=ownerEchoSamples,OwnerEchoVisuals=ownerEchoVisuals,RemoteBallVisuals=remoteBallVisuals,LocalPredictionVisuals=localPredictionVisuals}}));
    }
    [Rpc.Host]
    public async void Report(string json)
    {
        var caller=Rpc.Caller.Id;
        await GameTask.MainThread();
        if(this.IsValid())Reports[caller]=json;
    }
    [Rpc.Broadcast(NetFlags.HostOnly)]
    public async void Command(string action,int seat,Vector3 point)
    {
        await GameTask.MainThread();
        if(!this.IsValid())return;
        var player=MultiplayerSession.LocalPlayer(Game.ActiveScene);
        if(player?.Components.Get<NetworkPawn>()?.Seat!=seat)return;
        var controls=Scene.GetAllComponents<PaintballControls>().FirstOrDefault();
        if(action=="test_lock"){controlled=true;CarrySamples=0;CarryMaxError=0;}
        if(action=="test_unlock"){controlled=false;Input.ReleaseActions();}
        if(action=="soak_start"){controlled=true;soak=true;BeginPerformance();}
        if(action=="soak_stop"){soak=false;controlled=false;player.Components.Get<PaintballMarker>().ReviewAimOverride=null;Input.ReleaseActions();}
        if(action=="close" && controls?.Open==true)controls.Toggle();
        if(action=="open" && controls?.Open==false)controls.Toggle();
        if(action=="move"){player.WorldPosition=point;player.Body.Velocity=Vector3.Zero;player.Body.Sleeping=false;player.Network.ClearInterpolation();}
        if(action=="look")player.EyeAngles=Rotation.LookAt(point-player.EyePosition).Angles();
        if(action=="cover")player.Components.Get<CoverController>().Activate(false);
        if(action=="vault")player.Components.Get<VaultController>().TryBegin();
        if(action=="aim")player.Components.Get<PaintballMarker>().ReviewAimOverride=point.x>0;
        if(action=="sound"){var sound=Sound.Play(CharacterMarkerSound.For(seat),player.WorldPosition);SoundResult=$"{sound?.Name}: {sound?.IsPlaying}";}
        if(action=="attempt_round")MultiplayerSession.Find(Scene).StartRound();
        if(action=="attempt_minutes")MultiplayerSession.Find(Scene).SetMinutes(3);
        if(action=="reject")player.Components.Get<NetworkPawn>().RequestShot(new Vector3(999999,0,0),new Vector3(999999,10,0),int.MaxValue);
        if(action=="screenshot")Game.TakeHighResScreenshot(1280,720);
        if(action=="walk")walkTime=2;
        if(action=="fire")player.Components.Get<PaintballMarker>().FireAt(point);
        if(action=="elbow")player.Components.Get<CloseCombat>().TryAttack();
        if(action=="attack_press"){aimTime=.45f;aimPrevious=false;aimInjected=true;}
        if(action=="recover_burst"){recoveryTaps=5;nextTap=0;}
        if(action=="recover"){var c=player.Components.Get<CloseCombat>();SoundResult=$"ready:{c.ReadyToRecover} proxy:{c.IsProxy} sent:{c.RegisterRecoveryPress()}";}
        if(action=="name")player.Components.Get<PlayerProfile>().SaveName($"Test Player {seat+1}");
        if(action=="leave")Networking.Disconnect();
    }
}