PopRiotBallisticsReview.cs
using Sandbox;
using System;
namespace PaintballBaddies;
// Temporary private-match fixture. Never added to a saved scene.
public sealed class PopRiotBallisticsReview : Component
{
public List<string> Checks {get;}=new();
public bool Done {get;private set;}
private MultiplayerSession session;
private PaintballCombatant shooter;
private PopRiotTarget target;
private GameObject wall,floor;
private PlayerController player;
private Vector3 savedPosition;
private Angles savedAngles;
private bool savedInput;
private int lifeBefore,paintBefore,shieldBefore;
private Vector3 crashBefore;
private int reactionBefore;
private ArenaShield shield;
private Vector3 point=new(0,5000,6200);
private float clock;
private int stage,before,combo;
private void Check(bool value,string message)=>Checks.Add((value ? "PASS " : "FAIL ")+message);
protected override void OnStart()
{
session=MultiplayerSession.Find(Scene);shooter=MultiplayerSession.LocalPlayer(Scene)?.Components.Get<PaintballCombatant>();
if(!session.IsValid() || session.PublicLobby || !Networking.IsHost || !session.IsPopRiot || !session.InRound || !shooter.IsValid())
{Check(false,"private cooperative host required");Done=true;return;}
player=MultiplayerSession.LocalPlayer(Scene);savedPosition=player.WorldPosition;savedAngles=player.EyeAngles;savedInput=player.UseInputControls;
floor=new GameObject(true,"Temporary drone physics platform"){WorldPosition=point+new Vector3(0,600,-210)};
var collider=floor.Components.Create<BoxCollider>();collider.Scale=new Vector3(800,800,20);collider.Static=true;
player.WorldPosition=floor.WorldPosition+Vector3.Up*12;player.EyeAngles=Angles.Zero;player.UseInputControls=false;
target=session.SpawnRiotTarget(point,PopKind.Bubble);target.ReviewStill=true;
}
protected override void OnUpdate()
{
if(Done)return;
// Keep the opt-in fixture stationary even if live keyboard input remains held.
player.WorldPosition=floor.WorldPosition+Vector3.Up*12;player.EyeAngles=Angles.Zero;
player.UseInputControls=false;if(player.Body.IsValid())player.Body.Velocity=Vector3.Zero;
Scene.GetAllComponents<PaintballControls>().FirstOrDefault()?.ResumeGame();
clock+=Time.Delta;if(clock<.65f)return;clock=0;
if(stage==0 || stage==2)
{
before=session.TeamScore;combo=session.RiotCombo;
var from=point-Vector3.Forward*300;
NetworkEffects.SpawnBall(shooter.GameObject,shooter.Team,from,PaintballFlight.LaunchVelocity(from,point));
}
if(stage==1)
{
Check(target.Popped && session.TeamScore>before,"physical paintball bursts bubble and awards score");
Check(session.RiotCombo==combo+1,"successful physical pop does not also count as a miss");
target=session.SpawnRiotTarget(point,PopKind.Glob);target.ReviewStill=true;
}
if(stage==3)
{
Check(target.Popped && session.TeamScore>before,"physical paintball intercepts oversized enemy glob");
wall=new GameObject(true,"Temporary glob cover collider"){WorldPosition=point};
wall.Components.Create<BoxCollider>().Scale=new Vector3(30,160,160);
target=session.SpawnRiotTarget(point-Vector3.Forward*150,PopKind.Glob);target.GlobVelocity=Vector3.Forward*320;
before=session.TeamScore;
}
if(stage==4)
{
Check(!target.IsValid() && session.TeamScore==before,"solid cover stops hostile glob without awarding a pop");
wall.Destroy();
reactionBefore=shooter.Components.Get<PaintHitReaction>()?.Impulses ?? 0;
int paintHits=shooter.PaintHits;int lives=session.RiotLives;shooter.ReceiveRiotGlob(shooter.WorldPosition+Vector3.Right*150);
Check(shooter.HitMoveScale<1 && shooter.DirectionIndicatorRemaining>0 && shooter.PaintHits==paintHits && session.RiotLives==lives-1,"hostile splash removes one shared life, gives direction cue and no PvP penalty");
shooter.ReceiveRiotGlob(shooter.WorldPosition+Vector3.Left*150);
Check(session.RiotLives==lives-1,"brief hit grace prevents stacked same-frame life loss");
var friend=Scene.GetAllComponents<PaintballCombatant>().First(a=>a!=shooter);
var through=PaintballFlight.Trace(Scene,friend.EyePosition-Vector3.Forward*8,friend.EyePosition+Vector3.Forward*8,shooter.GameObject);
Check(PaintballCombatant.Find(through.GameObject)!=friend,"cooperative paintballs pass through teammate collision");
}
if(stage==5)
{
Check(shooter.Components.Get<PaintHitReaction>()?.Impulses==reactionBefore+1,"confirmed owner hit plays one native camera impulse");
Check(shooter.Components.Get<PaintHitReaction>()?.Weight>0 && Scene.TimeScale==1,"personal hit grading is active without slowing shared time");
}
if(stage==6)Check(shooter.HitMoveScale==1,"movement fully recovers after splash");
if(stage==7 || stage==10 || stage==12)
{
lifeBefore=session.RiotLives;paintBefore=shooter.Components.Get<CharacterPaint>()?.Count ?? 0;
var direction=stage==12 ? Vector3.Backward : Vector3.Forward;
var destination=player.WorldPosition+Vector3.Up*45;
target=session.SpawnRiotTarget(destination+direction*180,PopKind.Glob);
target.GlobVelocity=-direction*420;
}
if(stage==8)
{
Check(session.RiotLives==lifeBefore-1,"physical enemy projectile takes one shared life from a human");
Check(shooter.Components.Get<CharacterPaint>()?.Count>paintBefore,"physical enemy projectile paints the fitted suit");
}
if(stage==9)
{
shield=Scene.GetAllComponents<ArenaShield>().FirstOrDefault(s=>!s.Owner.IsValid() && s.RespawnDelay<=0);
Check(shield.IsValid() && shield.Collect(shooter),"collect a shield for actual incoming-projectile test");
shieldBefore=shield?.Remaining ?? 0;
}
if(stage==11)
{
Check(session.RiotLives==lifeBefore && shield?.Remaining==shieldBefore-1,"frontal shield hit damages shield without losing a team life");
Check(shield?.PaintCount>0,"incoming drone paint sticks to shield");
}
if(stage==13)
{
Check(session.RiotLives==lifeBefore-1,"rear hit bypasses frontal shield and costs a life");
}
if(stage==14)
{
target=session.SpawnRiotTarget(point,PopKind.Blob);target.ReviewStill=true;
for(int i=0;i<8;i++)target.Hit(target.WorldPosition,shooter);
crashBefore=target.WorldPosition;before=session.TeamScore;
}
if(stage==15)
{
Check(target.IsValid() && target.Crashing && (target.WorldPosition-crashBefore).Length>20,"disabled drone spirals and falls instead of freezing");
var aim=target.WorldPosition+target.CrashVelocity*.09f;
var from=aim-Vector3.Forward*160;
NetworkEffects.SpawnBall(shooter.GameObject,shooter.Team,from,PaintballFlight.LaunchVelocity(from,aim));
}
if(stage==16)
{
Check(!target.IsValid() && session.TeamScore==before,"physical extra shot clears falling wreck without duplicate score");
lifeBefore=session.RiotLives;shooter.ReceiveRiotCrash(shooter.WorldPosition+Vector3.Right*50);
Check(shooter.HitMoveScale<.6f && session.RiotLives==lifeBefore,"wreck contact gives personal slowdown without taking an extra team life");
}
if(stage==18)
{
Check(shooter.HitMoveScale==1 && shooter.Components.Get<PaintHitReaction>()?.Weight==0 && Scene.TimeScale==1,"hit presentation and movement fully restore");
}
if(stage is 19 or 21 or 23)
{
float speed=stage==19 ? 180 : stage==21 ? 360 : 480;
target=session.SpawnRiotTarget(point,PopKind.Glob);target.GlobVelocity=Vector3.Forward*speed;
var from=point-Vector3.Right*300;
var aim=point+target.GlobVelocity*(300/PaintballFlight.Speed);
NetworkEffects.SpawnBall(shooter.GameObject,shooter.Team,from,PaintballFlight.LaunchVelocity(from,aim));
}
if(stage is 20 or 22 or 24)
Check(!target.IsValid() || target.Popped,$"physical crossing-shot interception at {(stage==20 ? 180 : stage==22 ? 360 : 480)} units/second");
if(stage==24)Done=true;
stage++;
}
protected override void OnDestroy()
{
if(player.IsValid()){player.WorldPosition=savedPosition;player.EyeAngles=savedAngles;player.UseInputControls=savedInput;if(player.Body.IsValid())player.Body.Velocity=Vector3.Zero;}
if(floor.IsValid())floor.Destroy();
if(wall.IsValid())wall.Destroy();
if(target.IsValid())target.GameObject.Destroy();
}
}