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();
}
}