CitizenLineupReview.cs
using Sandbox;
using System.Collections.Generic;
namespace PaintballBaddies;
/// <summary>Opt-in six-character comparison using real Citizen opponent equipment.</summary>
public sealed class CitizenLineupReview : Component
{
[Property] public bool ReviewNativeVest { get; set; }
[Property] public bool ReviewNativeBoots { get; set; }
[Property] public bool SingleCharacter { get; set; }
[Property] public int CharacterIndex { get; set; }
[Property] public string CaptureId { get; set; } = "";
[Property] public bool PreviewReload { get; set; }
[Property] public bool ReviewCrouch { get; set; }
[Property] public float ReviewRightGripWeight { get; set; } = 1;
private float elapsed;
private bool reloadStarted;
private bool reloadReported;
private readonly HashSet<GameObject> fitted = new();
private readonly List<GameObject> actors=new();
private GameObject floor;
private PlayerController player;
private Vector3 previousPlayerPosition;
protected override void OnStart()
{
player=Scene.GetAllComponents<PlayerController>().First();previousPlayerPosition=player.WorldPosition;
floor=TrainingRange.Box(null,"Roster comparison floor",new Vector3(0,3000,997),new Vector3(500,600,6),new Color(.35f,.35f,.35f),false);
for(int i=0;i<(SingleCharacter ? 1 : 6);i++)
{
var character=SingleCharacter ? CharacterIndex.Clamp(0,5) : i;
var actor=new GameObject {Name="Roster comparison "+RosterSelection.Names[character]};
actor.WorldPosition=new Vector3(0,2800+i*80,1000);
var presentation=actor.Components.Create<CitizenOpponentPresentation>();
if(ReviewNativeBoots && i % 2 == 0)
presentation.BootsPath="models/citizen_clothes/shoes/boots/models/boots_m_human.vmdl";
var bot=actor.Components.Create<ArenaOpponent>();bot.Character=RosterSelection.Names[character];bot.CombatEnabled=false;
actors.Add(actor);
}
}
protected override void OnUpdate()
{
elapsed+=Time.Delta;
if(PreviewReload && !reloadReported && elapsed>5)
{
foreach(var pod in Scene.GetAllComponents<CitizenPodReload>())
Log.Info("MARKER_SIDE_RELOAD "+Json.Serialize(new {capture_id=CaptureId,character=CharacterIndex,subject=pod.GameObject.Name,pickup=pod.pickupError,closest=pod.MinimumPickupGap,returned=pod.returnError,return_gap=pod.minimumReturnGap,pour_samples=pod.pourSamples,aligned=pod.pourAlignedSamples}));
reloadReported=true;
}
player.WorldPosition=new Vector3(-700,3000,1000);
for(int i=0;i<actors.Count;i++)
{
var actor=actors[i];
actor.Components.Get<ArenaOpponent>().ReviewCrouch=ReviewCrouch;
foreach(var grip in actor.Components.GetAll<CitizenMarkerGrip>(FindMode.EverythingInSelfAndDescendants))
grip.RightGripStrength=ReviewRightGripWeight;
if (ReviewNativeVest && i % 2 == 0 && !fitted.Contains(actor))
{
var presentation=actor.Components.Get<CitizenOpponentPresentation>();
if (presentation.IsReady)
{
var target=presentation.FootstepRenderer;
var gear=new GameObject(target.GameObject) { Name="Native tactical vest fit review" };
var renderer=gear.Components.Create<SkinnedModelRenderer>();
renderer.Model=Model.Load("models/citizen_clothes/vest/tactical_vest/models/tactical_vest_m_human.vmdl");
renderer.BoneMergeTarget=target;
fitted.Add(actor);
}
}
if(actor.Components.Get<NavMeshAgent>() is {} agent){agent.UpdatePosition=false;agent.MaxSpeed=0;}
actor.WorldPosition=new Vector3(0,2800+i*80,1000);
actor.WorldRotation=Rotation.FromYaw(180);
if(PreviewReload && !reloadStarted && elapsed>2 && actor.Components.Get<ArenaOpponent>()?.Magazine is {} magazine)
{
magazine.TryFire();magazine.BeginReload();reloadStarted=true;
}
}
}
protected override void OnDestroy()
{
if(player.IsValid())player.WorldPosition=previousPlayerPosition;
foreach(var actor in actors)if(actor.IsValid())actor.Destroy();
if(floor.IsValid())floor.Destroy();
}
}