PaintRushReview.cs
using Sandbox;
namespace PaintballBaddies;

/// <summary>Opt-in scoring, unlimited ammunition and actual one-minute match check.</summary>
public sealed class PaintRushReview : Component
{
    [Property] public string CaptureId { get; set; } = "";
    private ArenaMatch match;
    private PlayerController player;
    private PaintballMarker weapon;
    private float elapsed,fireClock;
    private int stage;
    private void Check(string name,bool passed) => Log.Info("PAINT_RUSH_CHECK "+Json.Serialize(new{capture_id=CaptureId,name,passed}));
    protected override void OnUpdate()
    {
        elapsed+=Time.Delta;
        if(stage==0 && elapsed>1)
        {
            match=Scene.GetAllComponents<ArenaMatch>().First();
            player=match.Components.Get<PlayerController>();weapon=match.Components.Get<PaintballMarker>();
            Check("duration settings",match.SetRoundMinutes(3) && match.RoundDuration==180 && match.SetRoundMinutes(2) && match.RoundDuration==120 && !match.SetRoundMinutes(4) && match.SetRoundMinutes(1));
            match.StartRound();player.UseInputControls=false;player.UseLookControls=false;stage=1;
        }
        if(stage==1 && elapsed>2)
        {
            var actors=Scene.GetAllComponents<PaintballCombatant>().Where(x=>x.StayInMatch).ToArray();
            Check("six distinct competitors",actors.Length==6 && actors.Select(x=>x.Team).Distinct().Count()==6);
            Check("duration locked during round",!match.SetRoundMinutes(3));
            var receiver=actors.First(x=>x!=match.PlayerState);
            var before=match.PlayerState.HitsLanded;
            for(int i=0;i<50;i++)receiver.RegisterHit(match.PlayerState.Team,player.WorldPosition,match.PlayerState);
            Check("hits score without elimination",receiver.PaintHits>=50 && !receiver.Eliminated && match.PlayerState.HitsLanded==before+50 && match.InRound);
            var current=receiver.PaintHits;
            Check("self hits rejected",!receiver.RegisterHit(receiver.Team,receiver.WorldPosition,receiver) && receiver.PaintHits==current);
            weapon.Reload();Check("no forced player reload",weapon.UnlimitedAmmo && weapon.ReloadRemaining==0);
            stage=2;
        }
        if(stage==2 && match.InRound)
        {
            fireClock-=Time.Delta;
            if(fireClock<=0){weapon.FireAt(weapon.Muzzle+Vector3.Up*1000);fireClock=.12f;}
        }
        if(stage==2 && !match.InRound)
        {
            var bots=Scene.GetAllComponents<ArenaOpponent>().Where(x=>x.ContestMode).ToArray();
            Check("one minute ends on timer",match.Status=="TIME UP" && match.Remaining==0 && elapsed>=61 && elapsed<65);
            Check("unlimited player firing",weapon.Shots>80 && weapon.Ammo==40 && weapon.Reserve==160 && weapon.ReloadRemaining==0);
            Check("unlimited AI ammunition",bots.Length==5 && bots.All(x=>x.Magazine.Ammo==30 && x.Magazine.Reserve==120 && x.Magazine.Reloads==0));
            Check("everyone remains visible",bots.All(x=>x.GameObject.Active && !x.Components.Get<PaintballCombatant>().Eliminated));
            Check("score conservation",match.Standings.Sum(x=>x.Points)==0);
            Check("rank ordering",match.Standings.Zip(match.Standings.Skip(1),(a,b)=>a.Points>b.Points || a.Points==b.Points && a.Received<=b.Received).All(x=>x));
            var hits=match.PlayerState.PaintHits;
            Check("score freezes at horn",!match.PlayerState.RegisterHit(99) && match.PlayerState.PaintHits==hits);
            Log.Info("PAINT_RUSH_RESULT "+Json.Serialize(new{capture_id=CaptureId,standings=match.Standings,shots=weapon.Shots,bot_shots=bots.Sum(x=>x.ShotsFired)}));
            stage=3;
        }
    }
}