CoverFireReview.cs
using Sandbox;
using System;
namespace PaintballBaddies;
/// <summary>Opt-in native input/pose/geometry check; never saved into the arena.</summary>
public sealed class CoverFireReview : Component
{
public List<string> Results {get;}=new();
public bool Done {get;private set;}
public int Stage {get;private set;}
public int Layout {get;private set;}
PlayerController player; PaintballMarker marker; CoverController cover;
GameObject obstacle; IDisposable hook,fixedHook;
Vector3 oldPosition; Angles oldAngles; bool oldLook;
bool primary,focus,previousPrimary,previousFocus;
float elapsed; int shots;
void Check(bool ok,string label)=>Results.Add((ok?"PASS ":"FAIL ")+Layout+" "+label);
void Next(){Stage++;elapsed=0;}
protected override void OnStart()
{
Scene.GetAllComponents<PaintballControls>().First().StartPractice();
player=MultiplayerSession.LocalPlayer(Scene);marker=player.Components.Get<PaintballMarker>();cover=player.Components.Get<CoverController>();
oldPosition=player.WorldPosition;oldAngles=player.EyeAngles;oldLook=player.UseLookControls;player.UseLookControls=false;
TrainingRange.Box(GameObject,"Cover firing platform",new(0,0,1198),new(900,900,4),Color.Gray,true);
obstacle=TrainingRange.Box(GameObject,"Cover firing obstacle",new(60,0,1222),new(20,160,44),Color.Gray,true);
obstacle.Components.Create<CoverSurface>();
hook=Scene.AddHook(GameObjectSystem.Stage.StartUpdate,-35,()=>
{
Input.AnalogMove=Vector3.Zero;Input.AnalogLook=Angles.Zero;
Input.SetAction("attack1",primary);Input.SetLastAction("attack1",previousPrimary);previousPrimary=primary;
Input.SetAction("attack2",focus);Input.SetLastAction("attack2",previousFocus);previousFocus=focus;
},nameof(CoverFireReview),"Temporary cover fire action edges");
fixedHook=Scene.AddHook(GameObjectSystem.Stage.StartFixedUpdate,-35,()=>
{Input.AnalogMove=Vector3.Zero;Input.SetAction("attack1",primary);Input.SetAction("attack2",focus);},nameof(CoverFireReview),"Temporary held cover actions");
Place();
}
void Place()
{
primary=focus=false;cover.Leave();cover.TestInput=false;marker.ReviewAimOverride=null;marker.AcceptInput=true;
player.Components.Get<CloseCombat>().ResetState();
obstacle.WorldPosition=new(60,0,Layout==0?1222:1248);
obstacle.LocalScale=new Vector3(20,Layout==3?400:160,Layout==0?44:96)/50;
player.WorldPosition=new(24,Layout==1?-62:Layout==2?62:0,1202);player.EyeAngles=Angles.Zero;
player.Body.Velocity=Vector3.Zero;player.WishVelocity=Vector3.Zero;elapsed=0;Stage=0;
}
protected override void OnUpdate()
{
if(Done)return;elapsed+=Time.Delta;
switch(Stage)
{
case 0:
if(elapsed<.5f)return;
Check(cover.TryEnter()&&cover.Attached,"stationary attachment");Next();break;
case 1:
if(elapsed<.6f)return;
Check(!cover.Peeking,"starts sheltered");shots=marker.Shots;primary=true;Next();break;
case 2:
if(elapsed<.04f)return;primary=false;Next();break;
case 3:
if(elapsed<1.65f)return;
Check(marker.Shots-shots==(Layout==3?0:1),$"quick left click emits {(Layout==3?0:1)} shot (actual {marker.Shots-shots})");
Check(cover.Attached&&!cover.Peeking&&!marker.CoverFireRequested,"returns to shelter after tap / timeout");
if(Layout==3){Done=true;return;}
shots=marker.Shots;primary=true;Next();break;
case 4:
if(elapsed<1.5f)return;
Check(marker.Shots-shots>=2,$"held left fires without right mouse ({marker.Shots-shots})");
Check(cover.Peeking&&marker.Aiming,"held attack exposes rifle pose");
Check(!PaintballFlight.Trace(Scene,player.EyePosition,marker.Muzzle,player.GameObject).Hit,"barrel clears eye segment");
Check(PaintballFlight.Trace(Scene,marker.Muzzle,marker.Muzzle+Vector3.Forward*96,player.GameObject).GameObject!=obstacle,"barrel clears cover");
primary=false;shots=marker.Shots;Next();break;
case 5:
if(elapsed<.8f)return;
Check(marker.Shots==shots&&cover.Attached&&!cover.Peeking,"release stops fire and hides without leaving");
focus=true;Next();break;
case 6:
if(elapsed<.9f)return;
Check(cover.Peeking&&marker.Aiming&&marker.Shots==shots,"optional right focus peeks without firing");
focus=false;Next();break;
case 7:
if(elapsed<.7f)return;
primary=true;shots=marker.Shots;Next();break;
case 8:
if(elapsed<.025f)return;
primary=false;marker.AcceptInput=false;Next();break;
case 9:
if(elapsed<1.3f)return;
Check(marker.Shots==shots&&!marker.CoverFireRequested,"input lock cancels queued shot");
marker.AcceptInput=true;Layout++;Place();break;
}
}
protected override void OnDestroy()
{
hook?.Dispose();fixedHook?.Dispose();Input.ReleaseActions();
if(cover.IsValid()){cover.Leave();cover.TestInput=false;cover.TestAim=false;}
if(marker.IsValid()){marker.AcceptInput=true;marker.ReviewAimOverride=null;}
if(player.IsValid()){player.WorldPosition=oldPosition;player.EyeAngles=oldAngles;player.UseLookControls=oldLook;player.Body.Velocity=Vector3.Zero;player.WishVelocity=Vector3.Zero;}
}
}