{"TotalCount":257,"Files":[{"Ident":"solovolt.paintball-baddies","Path":"AiMatchObservation.cs","FileName":"AiMatchObservation.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nusing System;\nnamespace PaintballBaddies;\n\n/// \u003Csummary\u003EOpt-in observation of the ordinary five-bot match; no injected hits.\u003C/summary\u003E\npublic sealed class AiMatchObservation : Component\n{\n    private float elapsed;\n    private double previous;\n    private PlayerController player;\n    private readonly List\u003Cdouble\u003E frames=new();\n    private readonly HashSet\u003Cstring\u003E seenTactics=new();\n    public bool Done { get; private set; }\n    public object Result { get; private set; }\n    protected override void OnStart()\n    {\n        Scene.GetAllComponents\u003CPaintballControls\u003E().First().StartMatch();\n        player=Scene.GetAllComponents\u003CPlayerController\u003E().First();\n        player.UseInputControls=player.UseLookControls=false;\n        previous=RealTime.NowDouble;\n    }\n    protected override void OnUpdate()\n    {\n        if(Done)return;\n        elapsed\u002B=Time.Delta;\n        var now=RealTime.NowDouble;\n        if(elapsed\u003E3)frames.Add((now-previous)*1000);\n        previous=now;\n        var bots=Scene.GetAllComponents\u003CArenaOpponent\u003E().ToArray();\n        foreach(var bot in bots)seenTactics.Add(bot.Tactic);\n        if(elapsed\u003C63)return;\n        var sorted=frames.OrderBy(x=\u003Ex).ToArray();\n        Result=new{Seconds=elapsed,BotCount=bots.Length,ShootingBots=bots.Count(x=\u003Ex.ShotsFired\u003E0),\n            Shots=bots.Sum(x=\u003Ex.ShotsFired),ShotsAtPlayers=bots.Sum(x=\u003Ex.ShotsAtPlayers),PlayerPaint=player.Components.Get\u003CPaintballCombatant\u003E()?.PaintHits,CoverShots=bots.Sum(x=\u003Ex.ShotsFromCover),\n            ShelteredShots=bots.Sum(x=\u003Ex.ShotsWhileSheltered),CoverChoices=bots.Sum(x=\u003Ex.CoverSelections),\n            Relocations=bots.Sum(x=\u003Ex.CoverRelocations),Flanks=bots.Sum(x=\u003Ex.FlankSelections),\n            Searches=bots.Sum(x=\u003Ex.SearchesStarted),HeardShots=bots.Sum(x=\u003Ex.HeardShots),\n            Recoveries=bots.Sum(x=\u003Ex.NavigationRecoveries),AvoidedBlockedShots=bots.Sum(x=\u003Ex.BlockedShotsAvoided),\n            Tactics=seenTactics.ToArray(),Frames=frames.Count,MeanMs=frames.Average(),P99Ms=sorted[(int)(sorted.Length*.99)],MaxMs=sorted.Last(),\n            Over33Ms=frames.Count(x=\u003Ex\u003E33.333),Standings=Scene.GetAllComponents\u003CArenaMatch\u003E().First().Standings};\n        Done=true;Log.Info(\u0022AI_MATCH_OBSERVATION \u0022\u002BJson.Serialize(Result));\n    }\n    protected override void OnDestroy()\n    {\n        if(player.IsValid())player.UseInputControls=player.UseLookControls=true;\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"AiTacticsReview.cs","FileName":"AiTacticsReview.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nnamespace PaintballBaddies;\r\npublic sealed class AiTacticsReview : Component\r\n{\r\n    PlayerController player;ArenaOpponent bot;float elapsed,next;int hiddenShots,peekShots,lastShots;bool sheltered,shifted;ArenaShield shield;bool stowed,exposed;float firstShot=-1;\r\n    protected override void OnStart()\r\n    {\r\n        player=Scene.GetAllComponents\u003CPlayerController\u003E().First();player.UseInputControls=player.UseLookControls=false;\r\n        player.WorldPosition=new(-630,100,1);\r\n        Scene.GetAllComponents\u003CTrainingRange\u003E().First().SetTargetsVisible(false);\r\n        Scene.NavMesh.IsEnabled=true;Scene.NavMesh.IncludeStaticBodies=true;Scene.NavMesh.IncludeKeyframedBodies=false;Scene.NavMesh.AgentHeight=67;Scene.NavMesh.AgentRadius=17;Scene.NavMesh.SetDirty();\r\n        var go=new GameObject(true,\u0022AI tactics review opponent\u0022);go.WorldPosition=new(-190,-40,1);go.WorldRotation=Rotation.LookAt(player.WorldPosition-go.WorldPosition);\r\n        go.Components.Create\u003CCitizenOpponentPresentation\u003E();bot=go.Components.Create\u003CArenaOpponent\u003E();bot.Character=\u0022imani\u0022;bot.CombatEnabled=true;bot.ContestMode=true;\r\n    }\r\n    protected override void OnUpdate()\r\n    {\r\n        elapsed\u002B=Time.Delta;\r\n        if(elapsed\u003Enext){next=elapsed\u002B1;Log.Info($\u0022AI_TRACE t={elapsed} pos={bot.WorldPosition} cover={bot.UsingCover} peek={bot.CoverPeeking} sees={bot.SeesPlayer} plan={bot.HasCoverPlan} destination={bot.CoverDestination} nav={bot.Components.Get\u003CNavMeshAgent\u003E()?.TargetPosition} block={bot.ShotBlockReason}\u0022);}\n        if(shield is null \u0026\u0026 elapsed\u003E1){shield=Scene.GetAllComponents\u003CArenaShield\u003E().First();shield.WorldPosition=bot.WorldPosition\u002Bnew Vector3(0,0,24);}\r\n        if(shield is not null \u0026\u0026 shield.Owner.IsValid()){stowed|=bot.UsingCover\u0026\u0026shield.Stowed;exposed|=bot.CoverPeeking\u0026\u0026!shield.Stowed;}\r\n        if(bot.ShotsFired\u003E0\u0026\u0026firstShot\u003C0)firstShot=elapsed;\r\n        var state=player.Components.Get\u003CPaintballCombatant\u003E();if(state.IsValid())state.StayInMatch=true;\r\n        if(bot.UsingCover){sheltered=true;hiddenShots\u002B=bot.ShotsFired-lastShots;}\r\n        if(bot.CoverPeeking)peekShots\u002B=bot.ShotsFired-lastShots;\r\n        lastShots=bot.ShotsFired;\r\n        if(elapsed\u003E17\u0026\u0026!shifted){shifted=true;player.WorldPosition=new(180,0,1);}\r\n        if(elapsed\u003E31)\r\n        {\r\n            Log.Info(\u0022AI_TACTICS \u0022\u002BJson.Serialize(new{bot.CoverSelections,firstShot,ShieldCollected=shield?.Owner?.GameObject==bot.GameObject,stowed,exposed,sheltered,bot.CoverBursts,bot.ShotsFromCover,bot.ShotsWhileSheltered,bot.FlankSelections,bot.ObstructedMoves,bot.ShotsFired,bot.DistanceTravelled,PlayerHits=state?.PaintHits}));Destroy();\r\n        }\r\n    }\r\n    protected override void OnDestroy(){if(bot.IsValid())bot.GameObject.Destroy();if(player.IsValid())player.UseInputControls=player.UseLookControls=true;}\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"ArenaOpponent.Overdrive.cs","FileName":"ArenaOpponent.Overdrive.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nnamespace PaintballBaddies;\r\n\r\npublic sealed partial class ArenaOpponent\r\n{\r\n    \r\n    private float overdriveThink,overdriveFire,overdriveWeaponTime,overdriveGrenadeThink;\n    private int overdriveWeaponRound=-1;\n    private int overdriveSpecialRound=-1,overdriveSpecialWave=-1;\n    private Vector3 overdriveAim;\r\n    private void UpdateOverdriveObjective()\r\n    {\r\n        var session=MultiplayerSession.Find(Scene);\r\n        if(!CombatEnabled || !session.IsValid() || !session.InRound || session.OverdriveReviewPaused || session.OverdrivePhase==\u0022READY\u0022){agent.Stop();return;}\r\n        UsingCover=CoverPeeking=holdingCover=seekingCover=false;flankRemaining=0;\r\n        var travel=WorldPosition-previous;DistanceTravelled\u002B=travel.Length;MovementSpeed=travel.WithZ(0).Length/MathF.Max(.001f,Time.Delta);previous=WorldPosition;\r\n        overdriveThink-=Time.Delta;overdriveFire-=Time.Delta;overdriveWeaponTime-=Time.Delta;overdriveGrenadeThink-=Time.Delta;\n        if(overdriveSpecialRound!=session.RoundId || overdriveSpecialWave!=session.OverdriveWave)\n        {\n            overdriveSpecialRound=session.RoundId;overdriveSpecialWave=session.OverdriveWave;\n            combatant.PrepareAISpecial(session.OverdriveWave,CharacterIndex);\n            overdriveGrenadeThink=2\u002BCharacterIndex*.45f;\n        }\n        if(overdriveWeaponRound!=session.RoundId || overdriveWeaponTime\u003C=0)\r\n        {\r\n            overdriveWeaponRound=session.RoundId;overdriveWeaponTime=10;\r\n            Components.Get\u003CPaintballCombatant\u003E()?.EquipWeapon(Game.Random.Float(0,1)\u003C.2f ? 1 : 0);\r\n        }\r\n        var candidates=session.OverdriveEnemies.Select(e=\u003Ee.AimPoint)\r\n            .Concat(session.RiotTargets.Select(t=\u003Et.WorldPosition)).OrderBy(p=\u003E(p-WorldPosition).Length).ToArray();\r\n        var sight=candidates.FirstOrDefault(p=\u003E!Scene.Trace.Ray(WorldPosition\u002BVector3.Up*58,p)\r\n            .IgnoreGameObjectHierarchy(GameObject).WithoutTags(\u0022paintball_actor\u0022,\u0022paintball_debris\u0022,\u0022overdrive_enemy\u0022,\u0022pop_target\u0022).Run().Hit);\r\n        bool visible=candidates.Length\u003E0 \u0026\u0026 sight!=Vector3.Zero;\r\n        overdriveAim=visible ? sight : candidates.FirstOrDefault();\r\n        if(overdriveAim==Vector3.Zero){agent.Stop();return;}\r\n        LastKnownPlayerPosition=overdriveAim-Vector3.Up*40;SeesPlayer=visible;observedAimPoint=overdriveAim;\r\n        var facing=(overdriveAim-WorldPosition).WithZ(0).Normal;\r\n        WorldRotation=Rotation.Slerp(WorldRotation,Rotation.LookAt(facing),1-MathF.Exp(-9*Time.Delta));\r\n        float distance=(overdriveAim-WorldPosition).WithZ(0).Length;\r\n        if(visible \u0026\u0026 distance\u003C440 \u0026\u0026 distance\u003E180)agent.Stop();\r\n        else if(overdriveThink\u003C=0)\r\n        {\r\n            overdriveThink=.55f;\r\n            var destination=Scene.NavMesh.GetClosestPoint(overdriveAim.WithZ(WorldPosition.z)-facing*240\u002BRotation.FromYaw(CharacterIndex*60).Forward*80,240);\r\n            if(destination.HasValue){agent.MaxSpeed=205;agent.MoveTo(destination.Value);}\r\n        }\r\n        if(!visible || overdriveFire\u003E0 || Vector3.Dot(WorldRotation.Forward,facing)\u003C.9f || !marker.IsValid())return;\r\n        var muzzle=marker.WorldTransform.PointToWorld(OverdriveLoadout.MuzzleFor(GameObject));\n        if(Scene.Trace.Ray(WorldPosition\u002BVector3.Up*58,muzzle).IgnoreGameObjectHierarchy(GameObject).WithoutTags(\u0022paintball_actor\u0022,\u0022paintball_debris\u0022).Run().Hit)return;\n        if(TryOverdriveGrenade(session,distance,overdriveAim))return;\n        // Teammates help without deleting every target before a human can react.\r\n        overdriveFire=2.2f\u002BCharacterIndex*.12f;\r\n        OverdriveShotgun.Fire(Scene,combatant,muzzle,overdriveAim\u002BRotation.FromYaw(shots*71\u002BCharacterIndex*23).Forward*(shots%3==0 ? 28 : 8),\u002B\u002Bshots);\n    }\n    private bool TryOverdriveGrenade(MultiplayerSession session,float distance,Vector3 aim)\n    {\n        // One roll per suitable attack opportunity, never per frame. Teammates\n        // consume their own queue through the same authoritative throw.\n        if(overdriveGrenadeThink\u003E0 || combatant.GrenadeCharges\u003C=0 || distance\u003C=190 || distance\u003E=720)return false;\n        bool useful=!combatant.FreezeGrenadeReady || session.OverdriveEnemies.Any(e=\u003Ee.FrozenRemaining\u003C=0 \u0026\u0026 (e.AimPoint-aim).Length\u003CSausageGrenade.BlastRadius);\n        if(!useful)return false;\n        overdriveGrenadeThink=4;\n        if(Game.Random.Float(0,1)\u003E=.2f || !combatant.TryThrowGrenade(aim))return false;\n        overdriveGrenadeThink=7;overdriveFire=2.4f;return true;\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"CharacterPaintReview.cs","FileName":"CharacterPaintReview.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nnamespace PaintballBaddies;\n\n/// \u003Csummary\u003EOpt-in physical front/back/side impacts above the arena; never takes player input.\u003C/summary\u003E\npublic sealed class CharacterPaintReview : Component\n{\n    private GameObject actorObject,shooterObject;\n    private CitizenOpponentPresentation presentation;\n    private ArenaOpponent bot;\n    private PaintballCombatant actor,attacker;\n    private float elapsed,next=1;\n    private int shot;\n    public bool Done { get; private set; }\n    public List\u003Cstring\u003E Results { get; }=new();\n    public bool Crouch { get; set; }\n    protected override void OnPreRender()\n    {\n        if(presentation.IsValid() \u0026\u0026 presentation.IsReady \u0026\u0026 !actor.Components.Get\u003CCloseCombat\u003E().Incapacitated)\n            presentation.FootstepRenderer.Components.Get\u003CCitizenLocomotion\u003E().Animation.DuckLevel=Crouch ? 1 : 0;\n    }\n    protected override void OnStart()\n    {\n        actorObject=new GameObject(GameObject){Name=\u0022Clothing hit review\u0022,WorldPosition=new Vector3(0,0,1500)};\n        presentation=actorObject.Components.Create\u003CCitizenOpponentPresentation\u003E();\n        bot=actorObject.Components.Create\u003CArenaOpponent\u003E();bot.Character=\u0022viper\u0022;bot.CombatEnabled=false;\n        actor=actorObject.Components.Create\u003CPaintballCombatant\u003E();actor.Team=91;actor.StayInMatch=true;\n        var box=actorObject.Components.Create\u003CBoxCollider\u003E();box.Scale=new(28,28,66);box.Center=new(0,0,33);\n        shooterObject=new GameObject(GameObject){Name=\u0022Clothing review shooter\u0022,WorldPosition=new Vector3(120,0,1500)};\n        attacker=shooterObject.Components.Create\u003CPaintballCombatant\u003E();attacker.Team=92;attacker.StayInMatch=true;\n    }\n    protected override void OnUpdate()\n    {\n        elapsed\u002B=Time.Delta;\n        if(!presentation.IsReady)return;\n        bot.Enabled=false;\n        bot.ReviewCrouch=Crouch;\n        var agent=actorObject.Components.Get\u003CNavMeshAgent\u003E();if(agent.IsValid()){agent.Stop();agent.UpdatePosition=false;}\n        actorObject.WorldPosition=new(0,0,1500);actorObject.WorldRotation=Rotation.Identity;\n        var body=presentation.FootstepRenderer;\n        body.Components.Get\u003CCitizenLocomotion\u003E().UpdateMotion(Vector3.Zero,Vector3.Zero,true,Crouch ? 1 : 0,0,Rotation.Identity,0,false,Time.Delta);\n        if(Done || elapsed\u003Cnext)return;\n        next=elapsed\u002B.45f;\n        if(shot\u003E0)\n        {\n            var paint=actor.Components.Get\u003CCharacterPaint\u003E();\n            bool pass=actor.PaintHits==shot \u0026\u0026 attacker.HitsLanded==shot \u0026\u0026 paint?.Count==System.Math.Min(shot,CharacterPaint.Capacity)\n                \u0026\u0026 !paint.LastBone.Contains(\u0022IK\u0022) \u0026\u0026 !paint.LastBone.Contains(\u0022ikrule\u0022);\n            var result=$\u0022{(pass ? \u0022PASS\u0022 : \u0022FAIL\u0022)} shot={shot} hits={actor.PaintHits} score={attacker.HitsLanded} paint={paint?.Count} bone={paint?.LastBone}\u0022;\n            Results.Add(result);Log.Info(\u0022CHARACTER_PAINT \u0022\u002Bresult);\n        }\n        if(shot\u003E=12){Done=true;return;}\n        var direction=new[]{Vector3.Forward,Vector3.Backward,Vector3.Left,Vector3.Right}[shot%4];\n        var height=new[]{34f,47f,61f}[shot/4];\n        var target=actorObject.WorldPosition\u002BVector3.Up*height;\n        var start=target\u002Bdirection*120;\n        var ray=PaintballFlight.Trace(Scene,start,target-direction*12,shooterObject);\n        Log.Info($\u0022CHARACTER_PAINT_TRACE shot={shot\u002B1} hit={ray.GameObject?.Name} hitbox={ray.Hitbox is not null} position={ray.HitPosition}\u0022);\n        var ballObject=new GameObject(GameObject){Name=\u0022Clothing test paintball\u0022,WorldPosition=start};\n        var ball=ballObject.Components.Create\u003COpponentPaintball\u003E();ball.Shooter=shooterObject;ball.Team=92;ball.Velocity=PaintballFlight.LaunchVelocity(start,target);\n        shot\u002B\u002B;\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"CharacterSeparation.cs","FileName":"CharacterSeparation.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nnamespace PaintballBaddies;\r\n\r\n/// \u003Csummary\u003ECharacters never physically collide with each other (see\r\n/// Collision.config: that caused launches and jitter). Instead this gently\r\n/// slides overlapping characters apart every frame, so the crew and the\r\n/// sausages never stand inside each other and nobody gets flung.\r\n/// Each object is only moved by whoever simulates it (the host for AI and\r\n/// sausages, each player for themselves), never through walls.\u003C/summary\u003E\r\npublic sealed class CharacterSeparation : Component\r\n{\r\n    const float ActorRadius=15,SausageRadius=16,MaxSpeed=140,Response=9;\r\n    public static void Ensure(Scene scene)\r\n    {\r\n        if(scene is null || scene.GetAllComponents\u003CCharacterSeparation\u003E().Any())return;\r\n        new GameObject(true,\u0022Character separation\u0022){NetworkMode=NetworkMode.Never}.Components.Create\u003CCharacterSeparation\u003E();\r\n    }\r\n    private readonly List\u003C(GameObject Go,float Radius,float Give,NavMeshAgent Agent)\u003E bodies=new();\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        bodies.Clear();\r\n        foreach(var c in Scene.GetAllComponents\u003CPaintballCombatant\u003E())\r\n        {\r\n            if(!c.IsValid() || !c.Active || c.Eliminated)continue;\r\n            // Humans give way less than AI, so the player never feels shoved.\r\n            bool human=c.Components.Get\u003CPlayerController\u003E().IsValid();\r\n            bool anchored=c.Components.Get\u003CCoverController\u003E() is {Attached:true} or {Sliding:true}\r\n                || c.Components.Get\u003CVaultController\u003E()?.IsVaulting==true\r\n                || c.Components.Get\u003CCloseCombat\u003E()?.Incapacitated==true\r\n                || c.Components.Get\u003CArenaOpponent\u003E()?.UsingCover==true;\r\n            bodies.Add((c.GameObject,ActorRadius,anchored ? 0 : human ? .25f : 1,c.Components.Get\u003CNavMeshAgent\u003E()));\r\n        }\r\n        foreach(var e in Scene.GetAllComponents\u003COverdriveEnemy\u003E())\r\n            if(e.IsValid() \u0026\u0026 e.Active \u0026\u0026 !e.Dead)bodies.Add((e.GameObject,SausageRadius,e.FrozenRemaining\u003E0 ? 0 : 1,e.Components.Get\u003CNavMeshAgent\u003E()));\r\n        if(bodies.Count\u003C2)return;\r\n        var push=new Vector3[bodies.Count];\r\n        for(int i=0;i\u003Cbodies.Count;i\u002B\u002B)\r\n            for(int j=i\u002B1;j\u003Cbodies.Count;j\u002B\u002B)\r\n            {\r\n                var a=bodies[i];var b=bodies[j];\r\n                var d=(a.Go.WorldPosition-b.Go.WorldPosition);\r\n                if(MathF.Abs(d.z)\u003E60)continue;\r\n                d=d.WithZ(0);float min=a.Radius\u002Bb.Radius;float len=d.Length;\r\n                if(len\u003E=min)continue;\r\n                // Exactly on top of each other: split along a stable direction.\r\n                var n=len\u003E.01f ? d/len : Rotation.FromYaw((i*73\u002Bj*151)%360).Forward;\r\n                float overlap=min-len,total=a.Give\u002Bb.Give;\r\n                if(total\u003C=0)continue;\r\n                push[i]\u002B=n*overlap*(a.Give/total);push[j]-=n*overlap*(b.Give/total);\r\n            }\r\n        for(int i=0;i\u003Cbodies.Count;i\u002B\u002B)\r\n        {\r\n            if(push[i].Length\u003C.05f)continue;\r\n            var b=bodies[i];\r\n            if(b.Go.IsProxy)continue;\r\n            var step=push[i]*MathF.Min(1,Response*Time.Delta);\r\n            float max=MaxSpeed*Time.Delta;if(step.Length\u003Emax)step=step.Normal*max;\r\n            var from=b.Go.WorldPosition\u002BVector3.Up*24;\r\n            var hit=Scene.Trace.Sphere(8,from,from\u002Bstep).WithoutTags(\u0022paintball_actor\u0022,\u0022overdrive_enemy\u0022,\u0022paintball_debris\u0022,\u0022trigger\u0022).Run();\r\n            if(hit.Hit || hit.StartedSolid)continue;\r\n            var to=b.Go.WorldPosition\u002Bstep;\r\n            if(b.Agent.IsValid() \u0026\u0026 b.Agent.UpdatePosition)b.Agent.SetAgentPosition(to);\r\n            else b.Go.WorldPosition=to;\r\n        }\r\n    }\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"CitizenLocomotion.cs","FileName":"CitizenLocomotion.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nusing Sandbox.Citizen;\n\nnamespace PaintballBaddies;\n\n/// \u003Csummary\u003ENative animation inputs shared by player and AI presentation.\u003C/summary\u003E\npublic sealed class CitizenLocomotion : Component\n{\n    public CitizenAnimationHelper Animation { get; private set; }\n    public float VaultWeight { get; private set; }\n    private SkinnedModelRenderer body;\n    private float aimPitch;\n    private float previousYaw;\n    private bool rotationReady;\n    private bool positionReady;\n    private Vector3 previousPosition;\n    private float stationaryTime;\n    private float duckLevel;\n    private bool postureReady;\n    private float visibleTurn;\n    public float AimPitch =\u003E aimPitch;\n    public float AimYaw { get; private set; }\n    public float RotationSpeed =\u003E Animation?.MoveRotationSpeed ?? 0;\n\n    public void Configure(SkinnedModelRenderer renderer, CitizenAnimationHelper.HoldTypes hold)\n    {\n        body = renderer;\n        Animation ??= Components.Create\u003CCitizenAnimationHelper\u003E();\n        Animation.Target = body;\n        Animation.HoldType = hold;\n        Animation.IsGrounded = true;\n        Animation.IsWeaponLowered = false;\n        aimPitch = 0;\n        VaultWeight = 0;\n        rotationReady=false;\n        positionReady=false;stationaryTime=0;postureReady=false;\n        visibleTurn=0;\n    }\n\n    public void UpdateMotion(Vector3 velocity, Vector3 wishVelocity, bool grounded,\n        float duck, float turnSpeed, Rotation lookYaw, float pitch, bool lowerWeapon, float delta, float bodyAimWeight=1, Vector3? rootPosition=null)\n    {\n        if (!body.IsValid() || !Animation.IsValid()) return;\n        if(rootPosition.HasValue)\n        {\n            var position=rootPosition.Value;\n            stationaryTime=positionReady \u0026\u0026 grounded \u0026\u0026 (position-previousPosition).WithZ(0).Length\u003C.02f\n                ? stationaryTime\u002Bdelta : 0;\n            previousPosition=position;positionReady=true;\n            // Allow ordinary network interpolation gaps, but don\u0027t keep a run\n            // loop alive from stale replicated velocity or a blocked nav agent.\n            if(stationaryTime\u003E.18f)\n            {\n                velocity=velocity.WithX(0).WithY(0);\n                wishVelocity=wishVelocity.WithX(0).WithY(0);\n            }\n        }\n        float bodyYaw=body.WorldRotation.Angles().yaw;\n        float measuredTurn=rotationReady\u0026\u0026delta\u003E.0001f\u0026\u0026delta\u003C.2f\n            ? System.MathF.IEEERemainder(bodyYaw-previousYaw,360)/delta : 0;\n        previousYaw=bodyYaw;rotationReady=true;\n        // Rapid camera reversals can exceed 1,000 degrees/sec. Feeding that\n        // into Citizen\u0027s additive turn poses crosses the legs dramatically.\n        // Keep a restrained stepping response while the root turns freely.\n        // Citizen expects clockwise-positive; engine yaw increases the other\n        // way. Match the native controller\u0027s sign and half-rate turn response.\n        float targetTurn=(turnSpeed!=0 ? turnSpeed : -measuredTurn*.5f).Clamp(-180,180);\n        // Filter only the additive stepping pose. Movement, root facing and aim\n        // remain immediate; replicated yaw corrections cannot jerk the hips.\n        visibleTurn=delta\u003E=.2f ? 0 : visibleTurn\u002B(targetTurn-visibleTurn)*(1-System.MathF.Exp(-16*delta));\n        Animation.MoveRotationSpeed=visibleTurn;\n        Animation.IsGrounded = grounded;\n        Animation.WithVelocity(velocity);\n        Animation.WithWishVelocity(wishVelocity);\n        // Keep physical clearance immediate; ease only the visible Citizen pose.\n        // Player and AI use the same transition, including remote presentations.\n        duckLevel=postureReady ? duckLevel\u002B(duck-duckLevel)*(1-System.MathF.Exp(-20*delta)) : duck;\n        postureReady=true;\n        Animation.DuckLevel = duckLevel;\n        Animation.IsWeaponLowered = lowerWeapon;\n        aimPitch \u002B= (pitch.Clamp(-45,45)-aimPitch)*(1-System.MathF.Exp(-12*delta));\n        // Human aim poses have a limited twist range. Targets farther behind\n        // require a root/body turn, not a 180-degree spine and pelvis override.\n        AimYaw=System.MathF.IEEERemainder(lookYaw.Angles().yaw-bodyYaw,360).Clamp(-45,45);\n        Animation.WithLook((Rotation.FromYaw(bodyYaw\u002BAimYaw)*Rotation.FromPitch(aimPitch)).Forward,1,1,bodyAimWeight);\n    }\n\n    public void UpdateVault(bool vaulting, float progress, float delta)\n    {\n        if (!body.IsValid()) return;\n        body.UseAnimGraph = true;\n        if (vaulting)\n        {\n            var entry = (progress/.25f).Clamp(0,1);\n            VaultWeight = entry*entry*(3-2*entry);\n            body.Set(\u0022pb_vault_phase\u0022, progress);\n        }\n        else VaultWeight *= System.MathF.Exp(-20*delta);\n        body.Set(\u0022pb_vault_weight\u0022, VaultWeight);\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"CitizenOpponentPresentation.cs","FileName":"CitizenOpponentPresentation.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing Sandbox.Citizen;\r\n\r\nnamespace PaintballBaddies;\r\n\r\n/// \u003Csummary\u003EOpt-in native opponent presentation driven by existing AI movement and magazine state.\u003C/summary\u003E\r\npublic sealed class CitizenOpponentPresentation : Component\r\n{\r\n    [Property] public string ModelPath { get; set; } = \u0022models/characters/citizen_out_signal_review/character.vmdl\u0022;\r\n    [Property] public string OutfitPath { get; set; } = \u0022models/gear/citizen_curvy_suit_review/suit.vmdl\u0022;\r\n    [Property] public bool UseRosterSuit { get; set; } = true;\r\n    [Property] public string GlovesPath { get; set; } = \u0022models/gear/citizen_female_gloves/gloves.vmdl\u0022;\r\n    [Property] public string BootsPath { get; set; } = \u0022models/citizen_clothes/shoes/boots/models/boots_m_human.vmdl\u0022;\r\n    private ArenaOpponent opponent;\r\n    private OpponentMagazine weapon;\r\n    [Property] public string HelmetPath { get; set; } = \u0022models/gear/citizen_closed_helmet_review/helmet.vmdl\u0022;\r\n    private GameObject subject, marker, support, previousAnchor, shoulderHand;\r\n    private SkinnedModelRenderer body, previousBody;\r\n    private bool previousVisible;\r\n    private CitizenLocomotion locomotion;\r\n    private CitizenMarkerGrip grip;\r\n    private CitizenPodReload pod;\r\n    private float shieldPoseWeight;\r\n    private CharacterEquipment equipment;\r\n    public string EquippedSuit =\u003E equipment?.VisibleOutfit?.Model?.Name;\r\n    public string EquippedHelmet =\u003E equipment?.Helmet?.Model?.Name;\r\n    public bool ProtectiveGearReady =\u003E equipment.IsValid() \u0026\u0026 (equipment.CompleteCharacterReady || (\r\n        GearReady(equipment.Outfit) \u0026\u0026 GearReady(equipment.Helmet)\r\n        \u0026\u0026 GearReady(equipment.Gloves) \u0026\u0026 GearReady(equipment.Boots)));\r\n    private bool GearReady(SkinnedModelRenderer piece) =\u003E piece.IsValid()\r\n        \u0026\u0026 piece.Model.IsValid() \u0026\u0026 piece.Enabled \u0026\u0026 piece.GameObject.Active\r\n        \u0026\u0026 piece.BoneMergeTarget==body;\r\n    public bool VisualActive =\u003E subject.IsValid() \u0026\u0026 subject.Active;\r\n    private readonly CitizenReloadClock clock = new();\r\n    private bool reloading, cancelled, gesture;\r\n    private float time, posture;\r\n    private float outTime;\r\n    private float stationaryAimTime;\r\n    public float HeadHeight =\u003E body.IsValid() \u0026\u0026 body.TryGetBoneTransform(\u0022head\u0022,out var head) ? head.Position.z-subject.WorldPosition.z : -1;\r\n    public float AnimationSpeed { get; private set; }\r\n    public float PeakAnimationSpeed { get; private set; }\r\n    public bool ShowingOutSignal =\u003E outTime \u003E 0 \u0026\u0026 outTime \u003C 1.5f;\r\n    public float OutSignalWeight { get; private set; }\r\n    public float SignalHandAboveHead =\u003E body.IsValid()\r\n        \u0026\u0026 body.TryGetBoneTransform(\u0022hand_R\u0022,out var hand)\r\n        \u0026\u0026 body.TryGetBoneTransform(\u0022head\u0022,out var head) ? hand.Position.z-head.Position.z : -999;\r\n    private Transform cancelHand;\r\n    private Rotation cancelPod;\r\n    public bool IsReady =\u003E body.IsValid() \u0026\u0026 body.Model.IsValid();\r\n    public SkinnedModelRenderer FootstepRenderer =\u003E body;\r\n    public bool PodHeld =\u003E pod.IsValid() \u0026\u0026 pod.podHeld;\r\n    public bool ReloadPropsVisible =\u003E pod.IsValid() \u0026\u0026 pod.HasActiveReloadProps;\r\n\r\n    protected override void OnStart()\r\n    {\r\n        TryInitialize();\r\n    }\r\n\r\n    private void TryInitialize()\r\n    {\r\n        if (subject.IsValid()) return;\r\n        opponent = Components.Get\u003CArenaOpponent\u003E();\r\n        if (!opponent.IsValid() || !opponent.Body.IsValid()) return;\r\n        weapon = opponent.Magazine;\r\n        previousBody = opponent.Body;\r\n        previousVisible = previousBody.IsValid() \u0026\u0026 previousBody.Enabled;\r\n        subject = new GameObject(GameObject) { Name = \u0022Citizen opponent presentation\u0022, NetworkMode=NetworkMode.Never, Enabled=false };\n        subject.WorldPosition = opponent.WorldPosition;\r\n        subject.WorldRotation = opponent.WorldRotation;\r\n        body = subject.Components.Create\u003CSkinnedModelRenderer\u003E();\r\n        body.Model = Model.Load(BaddieWardrobeCatalog.ModelPath(opponent.CharacterIndex.Clamp(0,5),true));\n        if(!body.Model.IsValid() || body.Model.IsError)body.Model=Model.Load(ModelPath);\n        body.UseAnimGraph = true;\r\n        equipment=subject.Components.Create\u003CCharacterEquipment\u003E();\r\n        equipment.Configure(body,\r\n            UseRosterSuit ? CitizenRosterAssets.Suit(opponent.CharacterIndex) : OutfitPath,\r\n            GlovesPath, BootsPath, UseRosterSuit ? CitizenRosterAssets.Helmet(opponent.CharacterIndex) : HelmetPath);\r\n        subject.Components.Create\u003CCitizenSuitFollowThrough\u003E().Configure(body,equipment.Outfit);\r\n        locomotion = subject.Components.Create\u003CCitizenLocomotion\u003E();\r\n        locomotion.Configure(body, CitizenAnimationHelper.HoldTypes.Rifle);\r\n        grip = subject.Components.Create\u003CCitizenMarkerGrip\u003E();\r\n        pod = subject.Components.Create\u003CCitizenPodReload\u003E();\r\n        pod.CreateGear();\r\n        // Transform anchor only: OverdriveLoadout owns the visible firearm.\n        marker = new GameObject(subject) { Name = \u0022Opponent weapon anchor\u0022, NetworkMode=NetworkMode.Never };\n        support = new GameObject(subject) { Name = \u0022Marker support hand\u0022 };\r\n        previousAnchor = opponent.PresentationAnchor;\r\n        opponent.PresentationAnchor = marker;\r\n        weapon.ReloadStarted \u002B= StartReload;\r\n        weapon.ReloadCancelled \u002B= CancelReload;\r\n        if (previousBody.IsValid()) previousBody.Enabled = false;\n        subject.Enabled=true;\n        if (weapon.ReloadRemaining \u003E 0) StartReload();\r\n    }\r\n\r\n    private void StartReload()\r\n    {\r\n        if(MultiplayerSession.Find(Scene)?.IsOverdrive==true)return;\r\n        reloading=true;cancelled=false;gesture=false;time=.8f;\r\n        clock.Reset();grip.ResetRecovery();pod.ResetCycle();\r\n    }\r\n\r\n    private void CancelReload()\r\n    {\r\n        if (!reloading || cancelled || !body.IsValid()) return;\r\n        cancelled=true;\r\n        if (body.TryGetBoneTransform(\u0022hand_L\u0022,out var hand)) cancelHand=subject.WorldTransform.ToLocal(hand);\r\n        if (pod.refillPod.IsValid()) cancelPod=subject.WorldRotation.Inverse*pod.refillPod.WorldRotation;\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        // Scene components can start before ArenaOpponent creates its renderer.\r\n        if (!subject.IsValid()) TryInitialize();\r\n        if (!IsReady || !opponent.IsValid() || weapon is null) return;\r\n        if (previousBody.IsValid()) previousBody.Enabled=false;\r\n        var heldShield=Scene.GetAllComponents\u003CArenaShield\u003E().FirstOrDefault(x=\u003Ex.Owner.IsValid()\u0026\u0026x.Owner.GameObject==GameObject);\r\n        bool shieldActive=heldShield is not null \u0026\u0026 !heldShield.Stowed;\r\n        shieldPoseWeight\u002B=((shieldActive ? 1 : 0)-shieldPoseWeight)*(1-System.MathF.Exp(-12*Time.Delta));\r\n        body.Set(\u0022pb_shield_carry\u0022,shieldPoseWeight);\r\n        locomotion.Animation.HoldType=shieldActive ? CitizenAnimationHelper.HoldTypes.Pistol : OverdriveLoadout.HoldFor(GameObject);\r\n        locomotion.Animation.Handedness=!shieldActive ? CitizenAnimationHelper.Hand.Both : CitizenAnimationHelper.Hand.Right;\n        var eliminated=opponent.Components.Get\u003CPaintballCombatant\u003E()?.Eliminated==true;\r\n        if(eliminated)\r\n        {\r\n            outTime\u002B=Time.Delta;\r\n            subject.Enabled=ShowingOutSignal;\r\n            marker.Enabled=false;\r\n            pod.DropHeldPod(opponent.NavigationVelocity*.5f);\r\n            pod.StopPresentation();\r\n            pod.Enabled=false;\r\n            locomotion.Animation.IkLeftHand=null;\r\n            locomotion.Animation.IkRightHand=null;\r\n            locomotion.UpdateMotion(Vector3.Zero,Vector3.Zero,true,opponent.CoverCrouched ? 1 : 0,\r\n                0,subject.WorldRotation,0,true,Time.Delta);\r\n            grip.UpdateGrip(body,0,true,Time.Delta);\r\n            var progress=(outTime/.45f).Clamp(0,1);\r\n            OutSignalWeight=progress*progress*(3-2*progress);\r\n            body.Set(\u0022pb_out_weight\u0022,OutSignalWeight);\r\n            return;\r\n        }\r\n        var sheltered=opponent.UsingCover;\r\n        var duck=opponent.CoverCrouched ? 1f : 0f;\r\n        var facing=opponent.WorldRotation;\r\n        subject.WorldRotation=Rotation.Slerp(subject.WorldRotation,facing,1-System.MathF.Exp(-9*Time.Delta));\r\n        if (reloading) time=clock.Sample(time,true,cancelled,grip.Returning,pod.podHeld,weapon.ReloadRemaining,(1-weapon.ReloadRemaining/weapon.ActiveReloadDuration),Time.Delta);\r\n        var release=reloading \u0026\u0026 time\u003E=.8f \u0026\u0026 time\u003C3.2f;\r\n        // Native agent velocity excludes position corrections and avoids measuring\r\n        // movement against a child that already follows the parent this frame.\r\n        var velocity=opponent.NavigationVelocity;\r\n        AnimationSpeed=velocity.WithZ(0).Length;\r\n        PeakAnimationSpeed=System.MathF.Max(PeakAnimationSpeed,AnimationSpeed);\r\n        var look=Rotation.LookAt(opponent.CombatAimPoint-(subject.WorldPosition\u002BVector3.Up*(sheltered ? 35 : 60)));\r\n        // While following a route, look with the body instead of craning all\r\n        // the way back toward a threat that is no longer visible.\r\n        if(velocity.WithZ(0).Length\u003E30 || (!opponent.SeesPlayer\u0026\u0026!opponent.CoverPeeking))look=facing;\r\n        stationaryAimTime=velocity.WithZ(0).Length\u003E20 || System.MathF.Abs(locomotion.RotationSpeed)\u003E12 ? 0 : stationaryAimTime\u002BTime.Delta;\r\n        var torsoAim=((stationaryAimTime-.12f)/.18f).Clamp(0,1);\r\n        locomotion.UpdateMotion(velocity,opponent.NavigationWishVelocity,true,duck,0,Rotation.FromYaw(look.Angles().yaw),\r\n            release || sheltered ? 0 : look.Angles().pitch,sheltered,Time.Delta,torsoAim,rootPosition:opponent.WorldPosition);\r\n        var recovery=((time-3.2f)/.6f).Clamp(0,1);\r\n        recovery=recovery*recovery*(3-2*recovery);\r\n        posture\u002B=((reloading ? duck*(1-recovery) : 0)-posture)*(1-System.MathF.Exp(-24*Time.Delta));\r\n        body.Set(\u0022pb_reload_posture\u0022,posture);\r\n        grip.UpdateGrip(body,1,release,Time.Delta);\r\n        if (reloading \u0026\u0026 !cancelled \u0026\u0026 !gesture \u0026\u0026 time\u003E=1) { body.Set(\u0022b_reload\u0022,true);gesture=true; }\r\n        if (grip.Attach(body,marker,CitizenMarkerGrip.MarkerOffset,Angles.Zero))\r\n        {\r\n            grip.UpdateSupport(body,locomotion.Animation,marker,support,MultiplayerSession.Find(Scene)?.IsOverdrive==true ? OverdriveLoadout.SupportFor(GameObject) : new Vector3(1.5f,1,0),release,\r\n                reloading \u0026\u0026 time\u003E=3.2f ? (time-3.2f)/.4f : -1);\r\n            pod.Apply(body,locomotion.Animation,marker,support,grip.SupportRotation,grip.Grip,time,\r\n                duck,release,cancelled,clock.CancellationElapsed,cancelHand,cancelPod,Time.Delta);\r\n            // Dual pistols: the left hand mirrors the right, holding the second gun.\r\n            // Sausage Survival: shoulder the shotgun like a real shooter.\r\n            shoulderHand??=new GameObject(subject,true,\u0022Shotgun shoulder hand\u0022){NetworkMode=NetworkMode.Never};\r\n            bool shoulder=!reloading \u0026\u0026 !release \u0026\u0026 !shieldActive\r\n                \u0026\u0026 ShotgunShoulder.Apply(GameObject,body,locomotion.Animation,shoulderHand,Rotation.FromYaw(subject.WorldRotation.Angles().yaw\u002Blocomotion.AimYaw)*Rotation.FromPitch(locomotion.AimPitch));\r\n            if(!shoulder \u0026\u0026 locomotion.Animation.IkRightHand==shoulderHand)locomotion.Animation.IkRightHand=null;\r\n            // Dual pistols: each hand straight out in front of its own shoulder.\r\n            if(!reloading \u0026\u0026 !release \u0026\u0026 !shieldActive \u0026\u0026 !sheltered \u0026\u0026 OverdriveLoadout.DualFor(GameObject))DualPistols.BothHands(body,locomotion.Animation,shoulderHand,support,subject.WorldRotation.Angles().yaw\u002Blocomotion.AimYaw,locomotion.AimPitch);\r\n            else if(OverdriveLoadout.DualFor(GameObject))locomotion.Animation.IkLeftHand=null;\r\n        }\r\n        subject.WorldPosition=opponent.WorldPosition;\r\n        Components.Get\u003CCloseCombat\u003E()?.ApplyPresentation(body,locomotion.Animation,marker);\r\n        pod.SetCarryVisible(!weapon.UnlimitedAmmo || reloading);\r\n        heldShield?.ApplyGrip(body,locomotion.Animation);\r\n        if (reloading)\r\n        {\r\n            time\u002B=Time.Delta;\r\n            if (time\u003E=4.5f \u0026\u0026 !pod.podHeld) reloading=false;\r\n        }\r\n    }\r\n\r\n    protected override void OnDestroy()\r\n    {\r\n        if (weapon is not null)\r\n        {\r\n            weapon.ReloadStarted-=StartReload;weapon.ReloadCancelled-=CancelReload;\r\n            if (opponent.IsValid() \u0026\u0026 opponent.PresentationAnchor==marker) opponent.PresentationAnchor=previousAnchor;\r\n        }\r\n        if (GameObject.IsValid() \u0026\u0026 GameObject.Active \u0026\u0026 Scene.IsValid() \u0026\u0026 previousBody.IsValid()) previousBody.Enabled=previousVisible\n            \u0026\u0026 opponent.IsValid() \u0026\u0026 opponent.Components.Get\u003CPaintballCombatant\u003E()?.Eliminated!=true;\r\n        if (subject.IsValid()) subject.Destroy();\r\n    }\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"ConcreteCoverChecks.cs","FileName":"ConcreteCoverChecks.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nusing System.Linq;\nnamespace PaintballBaddies;\n\n/// \u003Csummary\u003EOpt-in checks against the actual placed arena barriers.\u003C/summary\u003E\npublic sealed class ConcreteCoverChecks : Component\n{\n    private PlayerController player;\n    private CoverController cover;\n    private CoverSurface[] barriers;\n    private int index;\n    private float elapsed;\n    private void Place()\n    {\n        var surface=barriers[index];\n        var collider=surface.Components.Get\u003CModelCollider\u003E();\n        var depth=collider.Model.Bounds.Size.x*surface.WorldScale.x;\n        player.WorldPosition=surface.WorldPosition-surface.WorldRotation.Forward*(depth/2\u002B30)\u002BVector3.Up*2;\n        player.EyeAngles=surface.WorldRotation.Angles();\n        player.Body.Velocity=Vector3.Zero;\n        elapsed=0;\n    }\n    protected override void OnStart()\n    {\n        player=Components.Get\u003CPlayerController\u003E();cover=Components.Get\u003CCoverController\u003E();\n        player.UseInputControls=player.UseLookControls=false;\n        Components.Get\u003CPaintballMarker\u003E().AcceptInput=false;cover.TestInput=true;\n        barriers=Scene.GetAllComponents\u003CCoverSurface\u003E().Where(c=\u003Ec.GameObject.Name.StartsWith(\u0022Concrete cover \u0022)).ToArray();\n        if(barriers.Length==0) { Log.Error(\u0022CONCRETE_COVER FAIL no placed barriers\u0022);Destroy();return; }\n        Place();\n    }\n    protected override void OnUpdate()\n    {\n        elapsed\u002B=Time.Delta;\n        if(elapsed\u003C.6f)return;\n        var surface=barriers[index];\n        var trace=Scene.Trace.Ray(player.WorldPosition\u002BVector3.Up*28,player.WorldPosition\u002BVector3.Up*28\u002Bsurface.WorldRotation.Forward*70).IgnoreGameObjectHierarchy(GameObject).Run();\n        bool attached=cover.TryEnter();\n        Log.Info($\u0022CONCRETE_COVER {(attached ? \u0022PASS\u0022 : \u0022FAIL\u0022)} {surface.GameObject.Name} attachment; normal={trace.Normal}, airborne={player.IsAirborne}\u0022);\n        bool route=VaultPlanner.TryPlan(player,out var plan,out var reason);\n        bool expected=surface.Top-player.WorldPosition.z\u003C=58;\n        Log.Info($\u0022CONCRETE_COVER {(route==expected ? \u0022PASS\u0022 : \u0022FAIL\u0022)} {surface.GameObject.Name} vault route; expected={expected}, actual={route}, reason={reason}\u0022);\n        cover.Leave();index\u002B\u002B;\n        if(index\u003Cbarriers.Length) { Place();return; }\n        cover.TestInput=false;player.UseInputControls=player.UseLookControls=true;\n        Components.Get\u003CPaintballMarker\u003E().AcceptInput=true;\n        Log.Info($\u0022CONCRETE_COVER COMPLETE barriers={barriers.Length}\u0022);Destroy();\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"CoverController.cs","FileName":"CoverController.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\n\r\nnamespace PaintballBaddies;\r\n\r\n/// \u003Csummary\u003EPhysical cover attachment; native player collision remains responsible for movement.\u003C/summary\u003E\r\npublic sealed class CoverController : Component\r\n{\r\n    private bool remoteAttached;\r\n    public bool Attached =\u003E IsProxy \u0026\u0026 Components.Get\u003CNetworkPawn\u003E().IsValid() ? remoteAttached : surface is not null;\r\n    public CoverSurface ReservedSurface =\u003E Attached ? surface : Sliding ? slideSurface : null;\r\n    internal void ApplyNetwork(NetworkPawn state)\r\n    {\r\n        remoteAttached=state.Covered;LowCover=state.Low;Peeking=state.Peek;Sliding=state.Sliding;\r\n        SlideDirection=state.SlideDirection;Normal=state.CoverNormal;\r\n        surface=Scene.GetAllComponents\u003CCoverSurface\u003E().FirstOrDefault(x=\u003Ex.IsValid() \u0026\u0026 x.GameObject.IsValid() \u0026\u0026 x.GameObject.Name==state.CoverName);\r\n    }\r\n    public bool Sliding { get; private set; }\r\n    public bool FreeCrouched { get; private set; }\r\n    public Vector3 SlideDirection { get; private set; }\r\n    private CoverSurface slideSurface;\r\n    private Vector3 slideTarget;\r\n    private float slideElapsed;\r\n    private bool slideInput;\r\n    private float pendingCover;\r\n    private bool pendingForward;\r\n    private Vector3 pendingDirection;\r\n    private CoverSurface preferredCandidate;\r\n    public const float InputBuffer=.15f;\r\n    public const float MaxPeekStep=56;\r\n    public bool TrySlide()\r\n    {\r\n        if(!Enabled || Attached || Sliding || !MovementAllowsEntry || !player.UseInputControls || weapon?.AcceptInput!=true || player.Velocity.WithZ(0).Length\u003C135)return false;\r\n        var found=FindCandidate(out var hit,190);\r\n        if(!found.IsValid() || !found.Enabled || MathF.Abs(hit.Normal.z)\u003E.35f || hit.Distance\u003C=70)return false;\r\n        var destination=(hit.EndPosition\u002Bhit.Normal.WithZ(0).Normal*StandOff).WithZ(WorldPosition.z);\r\n        if(!CornerClear(destination))return false;\r\n        slideSurface=found;slideTarget=destination;SlideDirection=(destination-WorldPosition).WithZ(0).Normal;\r\n        FreeCrouched=false;\r\n        slideInput=player.UseInputControls;slideElapsed=0;Sliding=true;\r\n        player.UseInputControls=false;weapon.CancelReload();player.UpdateDucking(true);\r\n        return true;\r\n    }\r\n    public void CancelSlide(bool keepCrouched=false,bool preserveMomentum=false)\r\n    {\r\n        if(!Sliding)return;\r\n        Sliding=false;slideSurface=null;\r\n        if(!player.IsValid())return;\r\n        player.UseInputControls=slideInput;player.WishVelocity=Vector3.Zero;if(!keepCrouched)player.UpdateDucking(false);\r\n        if(player.Body.IsValid() \u0026\u0026 !preserveMomentum)player.Body.Velocity=Vector3.Zero;\r\n    }\r\n    private void UpdateSlide()\r\n    {\r\n        slideElapsed\u002B=Time.Delta;\r\n        var requested=TestInput ? TestMovement : Rotation.FromYaw(player.EyeAngles.yaw)*Input.AnalogMove;\r\n        if(slideElapsed\u003E.08f \u0026\u0026 requested.Length\u003E.5f \u0026\u0026 Vector3.Dot(requested.Normal,SlideDirection)\u003C-.45f)\r\n        {CancelSlide(preserveMomentum:true);return;}\r\n        if(!slideSurface.IsValid() || !slideSurface.Enabled || !slideSurface.GameObject.Active || slideElapsed\u003E1.5f || player.IsAirborne){CancelSlide();return;}\r\n        var offset=(slideTarget-WorldPosition).WithZ(0);\r\n        if(offset.Length\u003C28)\r\n        {\r\n            CancelSlide(true);\r\n            if(TryEnter(SlideDirection))player.UpdateDucking(LowCover);\r\n            else player.UpdateDucking(false);\r\n            return;\r\n        }\r\n        var step=WorldPosition\u002Boffset.Normal*MathF.Min(offset.Length,240*Time.Delta\u002B2);\r\n        if(!CornerClear(step)){CancelSlide();return;}\r\n        player.UpdateDucking(true);\r\n        player.WishVelocity=offset.Normal*MathF.Min(240,80\u002Boffset.Length*1.4f);\r\n    }\r\n    public bool LowCover { get; private set; }\r\n    public bool Peeking { get; private set; }\r\n    private bool candidateAvailable;\r\n    private bool MovementAllowsEntry =\u003E player is not null \u0026\u0026 !player.IsAirborne \u0026\u0026 Components.Get\u003CCloseCombat\u003E() is not {Incapacitated:true} and not {Attacking:true} \u0026\u0026 Components.Get\u003CVaultController\u003E()?.IsVaulting != true;\r\n    public bool CanEnter =\u003E Enabled \u0026\u0026 candidateAvailable \u0026\u0026 !Attached \u0026\u0026 MovementAllowsEntry \u0026\u0026 (TestInput || weapon?.AcceptInput == true);\r\n    public bool CornerAvailable { get; private set; }\r\n    public bool TestInput { get; set; }\r\n    public Vector3 TestMovement { get; set; }\r\n    public bool TestAim { get; set; }\r\n    public Vector3 Normal { get; private set; }\r\n    public string Hint =\u003E Sliding ? \u0022SLIDING INTO COVER\u0022 : Attached ? (LowCover\r\n        ? $\u0022{Key(\u0022attack1\u0022)} RISE \u0026 FIRE \u00B7 RELEASE TO HIDE \u00B7 {Key(\u0022cover\u0022)} LEAVE \u00B7 {Key(\u0022vault\u0022)} VAULT\u0022\r\n        : CornerAvailable ? $\u0022{Key(\u0022attack1\u0022)} PEEK \u0026 FIRE \u00B7 RELEASE TO HIDE \u00B7 {Key(\u0022cover\u0022)} LEAVE\u0022\r\n        : $\u0022{Key(\u0022cover\u0022)} LEAVE COVER \u00B7 {Key(\u0022left\u0022)}/{Key(\u0022right\u0022)} SLIDE \u00B7 {Key(\u0022shoulder\u0022)} SHOULDER\u0022) : CanEnter ? $\u0022{Key(\u0022cover\u0022)} TAKE COVER\u0022 : \u0022\u0022;\r\n    private string Key(string action)=\u003EPaintballControls.Display(Scene,action);\r\n    private PlayerController player;\r\n    private PaintballMarker weapon;\r\n    private CoverSurface surface;\r\n    private Vector3 facePoint;\r\n    private float originalDuckHeight;\r\n    private bool originalInput;\r\n    private float probeTime;\r\n    private Vector3 edgeDirection;\r\n    private Vector3? cornerOrigin;\r\n    private Vector3 cornerTarget;\r\n    private Vector3 cachedPeek;\r\n    private float cornerProbeTime;\r\n    private const float StandOff = 20;\r\n    // Camera follows the exposed edge; the marker always remains right-handed.\r\n    public float PeekCameraSide =\u003E !LowCover \u0026\u0026 cornerOrigin.HasValue \u0026\u0026 Peeking\r\n        ? (Vector3.Dot(edgeDirection,Rotation.FromYaw(player.EyeAngles.yaw).Right)\u003E=0 ? 1 : -1) : 0;\r\n\r\n    protected override void OnStart()\r\n    {\r\n        player = Components.Get\u003CPlayerController\u003E();\r\n        // A lobby transition can destroy the practice pawn before this queued\r\n        // Start callback runs. It no longer has a controller to initialize.\r\n        if ( !player.IsValid() ) { Enabled=false; return; }\r\n        player.JumpSpeed = 0;\r\n        weapon = Components.Get\u003CPaintballMarker\u003E();\r\n    }\r\n\r\n    private SceneTraceResult Probe( Vector3 from, Vector3 direction, float distance ) =\u003E Scene.Trace.Ray( from, from \u002B direction * distance ).IgnoreGameObjectHierarchy( GameObject ).Run();\r\n\r\n    private Vector3 IntentDirection\r\n    {\r\n        get\r\n        {\r\n            var view=Rotation.FromYaw(player.EyeAngles.yaw).Forward;\r\n            var move=TestInput ? TestMovement : Rotation.FromYaw(player.EyeAngles.yaw)*Input.AnalogMove;\r\n            return move.Length\u003E.5f \u0026\u0026 Vector3.Dot(move.Normal,view)\u003E.45f ? (view\u002Bmove.Normal*.5f).Normal : view;\r\n        }\r\n    }\r\n    private CoverSurface Candidate(out SceneTraceResult hit,Vector3? direction=null)=\u003EFindCandidate(out hit,70,direction);\r\n    private CoverSurface FindCandidate(out SceneTraceResult hit,float range,Vector3? direction=null)\r\n    {\r\n        hit=default;CoverSurface best=null;float bestScore=float.MaxValue;\r\n        var intent=direction ?? IntentDirection;\r\n        foreach(float angle in new[]{0f,-9,9,-18,18,-28,28})\r\n        {\r\n            var ray=Rotation.FromYaw(angle)*intent;\r\n            var sample=Probe(WorldPosition\u002BVector3.Up*28,ray,range);\r\n            if(!sample.Hit || MathF.Abs(sample.Normal.z)\u003E.35f || Vector3.Dot(-ray,sample.Normal)\u003C.4f)continue;\r\n            var found=CoverSurface.Resolve(Scene,sample);\n            if(!found.IsValid() || !found.Enabled || !found.GameObject.Active || found.Top-WorldPosition.z\u003C30)continue;\r\n            // Attachment corrects the distance to the wall, not the sideways\r\n            // position of an angled probe. Test the same correction we apply.\r\n            var normal=sample.Normal.WithZ(0).Normal;\r\n            var gap=Vector3.Dot(WorldPosition-sample.EndPosition,normal);\r\n            var destination=WorldPosition\u002Bnormal*(StandOff-gap);\r\n            if(!CornerClear(destination))continue;\r\n            float score=sample.Distance\u002BMathF.Abs(angle)*.65f-(found==preferredCandidate ? 5 : 0);\r\n            if(score\u003E=bestScore)continue;\r\n            bestScore=score;best=found;hit=sample;\r\n        }\r\n        preferredCandidate=best;\r\n        return best;\r\n    }\r\n\r\n    public bool TryEnter(Vector3? direction=null)\r\n    {\r\n        if ( !Enabled || Attached || Sliding || !MovementAllowsEntry ) return false;\r\n        var candidate = Candidate( out var hit, direction );\r\n        if ( candidate is null ) return false;\r\n        surface = candidate;\r\n        FreeCrouched=false;\r\n        CornerAvailable = false;\r\n        cornerProbeTime=0;\r\n        edgeDirection = Vector3.Zero;\r\n        cornerOrigin = null;\r\n        Normal = hit.Normal.WithZ( 0 ).Normal;\r\n        facePoint = hit.EndPosition;\r\n        LowCover = !surface.StandingOnly \u0026\u0026 surface.Top - WorldPosition.z \u003C player.BodyHeight - 3;\r\n        originalInput = player.UseInputControls;\r\n        originalDuckHeight = player.DuckedHeight;\r\n        player.UseInputControls = false;\r\n        if ( LowCover ) player.DuckedHeight = MathF.Min( originalDuckHeight, MathF.Max( 30, surface.Top - WorldPosition.z - 5 ) );\r\n        return true;\r\n    }\r\n\r\n    public void Leave()\r\n    {\r\n        if(IsProxy)return;\r\n        FreeCrouched=false;\r\n        pendingCover=0;\r\n        CancelSlide();\r\n        if ( !Attached ) return;\r\n        surface = null;\r\n        LowCover = false;\r\n        Peeking = false;\r\n        CornerAvailable = false;\r\n        cornerOrigin = null;\r\n        if ( !player.IsValid() ) return;\r\n        player.DuckedHeight = originalDuckHeight;\r\n        player.UpdateDucking( false );\r\n        player.WishVelocity = Vector3.Zero;\r\n        player.UseInputControls = originalInput;\r\n    }\r\n\r\n    // Cover and vault are separate deliberate actions. Forward intent only\r\n    // chooses the running slide approach; it never starts a vault.\r\n    public bool Activate(bool forward)\r\n    {\r\n        if(!Enabled || player is null || (!TestInput \u0026\u0026 weapon?.AcceptInput!=true))return false;\r\n        if(Sliding){CancelSlide(preserveMomentum:true);return true;}\r\n        if(Attached)\r\n        {\r\n            Leave();return true;\r\n        }\r\n        if(!MovementAllowsEntry)return false;\r\n        if(TryEnter())return true;\r\n        return forward \u0026\u0026 TrySlide();\r\n    }\r\n\r\n    public bool RequestCover(bool forward)\r\n    {\r\n        pendingCover=0;\r\n        if(Activate(forward))return true;\r\n        if(MovementAllowsEntry \u0026\u0026 !Attached \u0026\u0026 !Sliding \u0026\u0026 (TestInput || weapon?.AcceptInput==true))\r\n        {pendingCover=InputBuffer;pendingForward=forward;pendingDirection=IntentDirection;}\r\n        return false;\r\n    }\r\n\r\n    public bool RequestCoverOrCrouch(bool forward)\r\n    {\r\n        if(RequestCover(forward))return true;\r\n        if(!MovementAllowsEntry || (!TestInput \u0026\u0026 weapon?.AcceptInput!=true))return false;\r\n        FreeCrouched=!FreeCrouched;\r\n        player.UpdateDucking(FreeCrouched);\r\n        return true;\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        if(IsProxy)return;\r\n        if ( player is null ) return;\r\n        if ( weapon?.AcceptInput != true \u0026\u0026 !TestInput ) { Leave(); return; }\r\n        if ( !TestInput \u0026\u0026 Input.Pressed( \u0022cover\u0022 ) )\r\n        {\r\n            RequestCoverOrCrouch(Input.AnalogMove.x\u003E.5f \u0026\u0026 !Input.Down(\u0022attack2\u0022));\r\n        }\r\n        if(pendingCover\u003E0)\r\n        {\r\n            pendingCover=MathF.Max(0,pendingCover-Time.Delta);\r\n            if(!MovementAllowsEntry || Vector3.Dot(IntentDirection,pendingDirection)\u003C.65f)pendingCover=0;\r\n            else if(Activate(pendingForward))pendingCover=0;\r\n        }\r\n        probeTime -= Time.Delta;\r\n        if ( probeTime \u003C= 0 )\r\n        {\r\n            candidateAvailable = !Attached \u0026\u0026 MovementAllowsEntry \u0026\u0026 Candidate( out _ ) is not null;\r\n            probeTime = .1f;\r\n        }\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        if(IsProxy)return;\r\n        if(Sliding){UpdateSlide();return;}\r\n        if ( !Attached || player is null ) return;\r\n        if ( !surface.IsValid() || !surface.Enabled || !surface.GameObject.Active )\r\n        {\r\n            Leave(); return;\r\n        }\r\n        if ( surface.Curved \u0026\u0026 !cornerOrigin.HasValue )\r\n        {\r\n            // Follow the actual hull instead of retaining the tangent plane at entry.\r\n            var inward = (surface.WorldPosition - WorldPosition).WithZ( 0 ).Normal;\r\n            var face = Probe( WorldPosition \u002B Vector3.Up * 28, inward, 85 );\r\n            if ( !surface.Matches(face) || MathF.Abs( face.Normal.z ) \u003E .15f )\n            {\r\n                Leave(); return;\r\n            }\r\n            Normal = face.Normal.WithZ( 0 ).Normal;\r\n            facePoint = face.EndPosition;\r\n            edgeDirection = Vector3.Zero;\r\n        }\r\n        var movement = TestInput ? TestMovement : Rotation.FromYaw( player.EyeAngles.yaw ) * Input.AnalogMove;\r\n        // Moving away cancels immediately; movement into the wall never clips the body through it.\r\n        if ( Vector3.Dot( movement, Normal ) \u003E .55f ) { Leave(); return; }\r\n        var aim = (TestInput ? TestAim : Input.Down( \u0022attack2\u0022 )) || weapon?.CoverFireRequested==true;\r\n        player.UpdateDucking( LowCover \u0026\u0026 !aim );\r\n        Peeking = LowCover \u0026\u0026 aim \u0026\u0026 !player.IsDucking;\r\n        var tangent = Vector3.Cross( Vector3.Up, Normal ).Normal;\r\n        var slide = tangent * Vector3.Dot( movement, tangent ) * 55;\r\n        if ( !LowCover \u0026\u0026 cornerOrigin.HasValue )\r\n        {\r\n            // Recheck after an aim change, moving farther along this edge only.\r\n            // Never switch to the other side of the obstacle while holding aim.\r\n            cornerProbeTime-=Time.Delta;\r\n            if(aim \u0026\u0026 cornerProbeTime\u003C=0)\r\n            {\r\n                cornerProbeTime=.12f;\r\n                bool settled=(cornerTarget-WorldPosition).WithZ(0).Length\u003C6 \u0026\u0026 weapon.Aiming\r\n                    \u0026\u0026 weapon.PresentationAnchor.IsValid() \u0026\u0026 Vector3.Dot(weapon.PresentationAnchor.WorldRotation.Forward,player.EyeAngles.ToRotation().Forward)\u003E.9f;\r\n                // Once the rifle pose has settled, the actual muzzle is the\r\n                // clearance authority. Combining it with the approximate grip\r\n                // corridor can reject a safe short step at the left-hand edge.\r\n                if(!(settled ? StandingMarkerClear(cornerTarget) : StandingLaneClear(cornerTarget)))\r\n                    for(float extra=4;extra\u003C=32;extra\u002B=4)\r\n                    {\r\n                        var next=cornerTarget\u002BedgeDirection*extra;\r\n                        if((next-cornerOrigin.Value).Length\u003EMaxPeekStep)break;\r\n                        if(CornerClear(next)\u0026\u0026(settled ? StandingMarkerClear(next) : StandingLaneClear(next))){cornerTarget=next;break;}\r\n                    }\r\n            }\r\n            var destination = aim ? cornerTarget : cornerOrigin.Value;\r\n            var offset = (destination - WorldPosition).WithZ( 0 );\r\n            Peeking = aim \u0026\u0026 offset.Length \u003C 4;\r\n            // Repositioning is always player-controlled. Commit the current\r\n            // spot when A/D is used rather than dragging back to an old anchor.\r\n            if(slide.Length\u003E5)\r\n            {\r\n                cornerOrigin=null;cornerProbeTime=0;CornerAvailable=false;Peeking=false;\r\n                player.WishVelocity=slide;return;\r\n            }\r\n            player.WishVelocity = offset.Normal * MathF.Min( 180, offset.Length * 16 );\r\n            if ( !aim \u0026\u0026 offset.Length \u003C 1 ) cornerOrigin = null;\r\n            return;\r\n        }\r\n        // Probe beyond the body radius so an unprompted slide stops before the end of the face.\r\n        if ( slide.Length \u003E 1 \u0026\u0026 !surface.Curved )\r\n        {\r\n            var edge = Probe( WorldPosition \u002B Vector3.Up * 28 \u002B slide.Normal * 22, -Normal, 80 );\r\n            if ( !surface.Matches(edge) || Vector3.Dot( edge.Normal, Normal ) \u003C .9f )\n            {\r\n                edgeDirection = slide.Normal;\r\n                slide = Vector3.Zero;\r\n            }\r\n            else edgeDirection = Vector3.Zero;\r\n        }\r\n        // Detect reachable exposure even when entering directly beside an edge.\r\n        // Rounded bunkers have no hard edge, so find a clear tangent position.\r\n        cornerProbeTime-=Time.Delta;\r\n        if(cornerProbeTime\u003C=0)\r\n        {\r\n            CornerAvailable = !LowCover \u0026\u0026 FindStandingPeek(tangent, out cachedPeek);\r\n            cornerProbeTime=.08f;\r\n        }\r\n        if ( aim \u0026\u0026 CornerAvailable )\r\n        {\r\n            cornerOrigin = WorldPosition;\r\n            cornerTarget = cachedPeek;\r\n        }\r\n        var gap = Vector3.Dot( WorldPosition - facePoint, Normal );\r\n        if ( gap \u003E 85 || gap \u003C 0 ) { Leave(); return; }\r\n        var correction = Normal * ((StandOff - gap) * 14).Clamp( -180, 180 );\r\n        player.WishVelocity = slide \u002B correction;\r\n    }\r\n\r\n    private bool FindStandingPeek(Vector3 tangent, out Vector3 destination)\r\n    {\r\n        destination=WorldPosition;\r\n        var preferred=edgeDirection.Length\u003E.5f ? edgeDirection : tangent*(weapon?.LeftShoulder==true ? 1 : -1);\r\n        for(float distance=8;distance\u003C=MaxPeekStep;distance\u002B=4)\r\n        {\r\n            // Choose the nearest reachable edge, using shoulder preference only\r\n            // as a tie breaker. A long wall still cannot force a distant excursion.\r\n            foreach(var side in new[]{preferred,-preferred})\r\n            {\r\n                var target=WorldPosition\u002Bside*distance;\r\n                if(!CornerClear(target))continue;\r\n                if(!StandingLaneClear(target))continue;\r\n                destination=target;edgeDirection=side;return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n    private bool StandingLaneClear(Vector3 position)\r\n    {\r\n        var facing=player.EyeAngles.ToRotation();\r\n        // Test the right-hand grip and complete barrel corridor, rather than\r\n        // a ray from the character\u0027s centre. This matters at a left corner.\r\n        foreach(float height in new[]{46f,58f})\r\n        {\r\n            var eye=position\u002BVector3.Up*height;\r\n            var grip=eye\u002Bfacing.Right*16;\r\n            var muzzle=grip\u002Bfacing.Forward*36;\r\n            foreach(var segment in new[]{(eye,grip),(grip,muzzle),(muzzle,muzzle\u002Bfacing.Forward*160)})\r\n                if(Scene.Trace.Sphere(2,segment.Item1,segment.Item2).IgnoreGameObjectHierarchy(GameObject)\r\n                    .WithoutTags(\u0022paintball_debris\u0022,\u0022paintball_actor\u0022).WithSurfaceMeshes().Run().Hit)return false;\r\n        }\r\n        return true;\r\n    }\r\n\r\n    private bool StandingMarkerClear(Vector3 position)\r\n    {\r\n        var offset=position-WorldPosition;\r\n        var muzzle=weapon.Muzzle\u002Boffset;\r\n        foreach(var segment in new[]{(player.EyePosition\u002Boffset,muzzle),(muzzle,muzzle\u002Bplayer.EyeAngles.ToRotation().Forward*160)})\r\n            if(Scene.Trace.Sphere(1.4f,segment.Item1,segment.Item2).IgnoreGameObjectHierarchy(GameObject)\r\n                .WithoutTags(\u0022paintball_debris\u0022,\u0022paintball_actor\u0022).WithSurfaceMeshes().Run().Hit)return false;\r\n        return true;\r\n    }\r\n\r\n    private bool CornerClear( Vector3 destination )\r\n    {\r\n        // Sweep a body-sized volume at both torso heights before committing to exposure.\r\n        // The native capsule can rest a fraction inside its contact margin.\r\n        // A full-radius sweep then reports StartedSolid even while backing away\r\n        // from the wall. Use a tiny skin; native body collision stays unchanged.\r\n        var radius=MathF.Max(1,player.BodyRadius-.5f);\r\n        foreach ( var height in new[] { 18f, 50f } )\r\n        {\r\n            var hit = Scene.Trace.Sphere( radius, WorldPosition \u002B Vector3.Up * height, destination \u002B Vector3.Up * height )\r\n                .IgnoreGameObjectHierarchy( GameObject ).Run();\r\n            if ( hit.Hit ) return false;\r\n        }\r\n        return Probe( destination \u002B Vector3.Up * 12, Vector3.Down, 24 ).Hit;\r\n    }\r\n\r\n    protected override void OnDisabled() =\u003E Leave();\r\n    protected override void OnDestroy() =\u003E Leave();\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"CoveredAimPose.cs","FileName":"CoveredAimPose.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\n\nnamespace PaintballBaddies;\n\n/// \u003Csummary\u003EApplies pitch to an authored two-handed pose and its marker as one assembly.\u003C/summary\u003E\npublic static class CoveredAimPose\n{\n    public static bool Apply( SkinnedModelRenderer body, float degrees, Vector3 markerOffset, out Transform markerPose, bool writeBones = true )\n    {\n        markerPose = default;\n        if ( !body.IsValid() || !body.Model.IsValid() ) return false;\n        var spine = body.Model.Bones.GetBone( \u0022Spine\u0022 );\n        var handBone = body.Model.Bones.GetBone( \u0022RightHand\u0022 );\n        if ( spine is null || handBone is null\n            || !body.TryGetBoneTransformAnimation( spine, out var pivot )\n            || !body.TryGetBoneTransformAnimation( handBone, out var hand ) ) return false;\n        var pitch = body.WorldRotation * Rotation.FromPitch( degrees.Clamp( -45, 45 ) ) * body.WorldRotation.Inverse;\n        if ( writeBones ) foreach ( var bone in body.Model.Bones.AllBones )\n        {\n            var ancestor = bone;\n            while ( ancestor.Name != \u0022Spine\u0022 \u0026\u0026 ancestor.Parent is not null ) ancestor = ancestor.Parent;\n            if ( ancestor.Name != \u0022Spine\u0022 || !body.TryGetBoneTransformAnimation( bone, out var pose ) ) continue;\n            pose.Position = pivot.Position \u002B pitch * (pose.Position - pivot.Position);\n            pose.Rotation = pitch * pose.Rotation;\n            body.SetBoneTransform( bone, body.WorldTransform.ToLocal( pose ) );\n        }\n        var origin = hand.Position \u002B body.WorldRotation * markerOffset;\n        markerPose = new Transform( pivot.Position \u002B pitch * (origin - pivot.Position), pitch * body.WorldRotation );\n        return true;\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"FeelAudio.cs","FileName":"FeelAudio.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nusing System;\nnamespace PaintballBaddies;\n\n// Extra audio layers: a shooter-only low-end thump under the shotgun, a\n// spatial pump rack after each blast, and a low-health heartbeat.\n// Sources are synthesized project WAVs under sounds/feel/.\npublic static class FeelAudio\n{\n    private static readonly Dictionary\u003Cstring,SoundEvent[]\u003E banks=new();\n    private static readonly Dictionary\u003Cstring,int\u003E previous=new();\n    public static int Plays {get;private set;}\n    private static SoundEvent[] Bank(string name,int count,float distance)\n    {\n        if(banks.TryGetValue(name,out var bank))return bank;\n        return banks[name]=Enumerable.Range(0,count).Select(i=\u003Enew SoundEvent\n        {Sounds=[SoundFile.Load(count==1 ? $\u0022sounds/feel/{name}.vsnd\u0022 : $\u0022sounds/feel/{name}_{i}.vsnd\u0022)],Volume=1,Pitch=1,\n         Distance=distance,DistanceAttenuation=distance\u003E0,OcclusionEnabled=false,AirAbsorption=false,ReverbEnabled=false}).ToArray();\n    }\n    public static SoundHandle Play(string name,int count,Vector3 point,float volume,float distance=900,float pitchSpread=.03f)\n    {\n        var bank=Bank(name,count,distance);\n        int take=count==1 ? 0 : previous.TryGetValue(name,out var old) ? (old\u002BGame.Random.Int(1,count-1))%count : Game.Random.Int(0,count-1);\n        previous[name]=take;\n        var handle=Sound.Play(bank[take],point);\n        if(handle is not null){handle.Volume=volume;handle.Pitch=1\u002BGame.Random.Float(-pitchSpread,pitchSpread);Plays\u002B\u002B;}\n        return handle;\n    }\n    public static void ShotgunLayers(Scene scene,GameObject shooter,Vector3 origin)\n    {\n        var local=MultiplayerSession.LocalPlayer(scene);\n        if(local.IsValid() \u0026\u0026 local.GameObject==shooter \u0026\u0026 scene.Camera.IsValid())\n            Play(\u0022shotgun_thump\u0022,3,scene.Camera.WorldPosition,.6f,0);\n        DelayedSound.Schedule(scene,\u0022shotgun_pump\u0022,2,origin,.34f,.5f);\n    }\n}\n\n/// \u003Csummary\u003EPlays one cue after a short delay, e.g. the pump rack after a blast.\u003C/summary\u003E\npublic sealed class DelayedSound : Component\n{\n    private string cue;private int count;private float delay,volume;\n    public static void Schedule(Scene scene,string cue,int count,Vector3 at,float delay,float volume)\n    {\n        var go=new GameObject(true,\u0022Delayed sound\u0022){NetworkMode=NetworkMode.Never,WorldPosition=at};go.Tags.Add(\u0022paintball_debris\u0022);\n        var d=go.Components.Create\u003CDelayedSound\u003E();d.cue=cue;d.count=count;d.delay=delay;d.volume=volume;\n    }\n    protected override void OnUpdate()\n    {\n        delay-=Time.Delta;if(delay\u003E0)return;\n        FeelAudio.Play(cue,count,WorldPosition,volume);GameObject.Destroy();\n    }\n}\n\n/// \u003Csummary\u003EDiner room tone was removed: its mains hum read as an unwanted buzz.\n/// This stub only stops any tone an already-running scene is still playing.\u003C/summary\u003E\npublic sealed class ArenaAmbience : Component\n{\n    protected override void OnStart(){Destroy();}\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"FoundryFixtureLighting.cs","FileName":"FoundryFixtureLighting.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nnamespace PaintballBaddies;\r\n/// \u003Csummary\u003EDownward fixture light with a restrained unshadowed ceiling fill.\u003C/summary\u003E\r\npublic sealed class FoundryFixtureLighting : Component\r\n{\r\n    private readonly List\u003C(PointLight light, Color color, GameObject down)\u003E fixtures = new();\r\n    protected override void OnStart()\r\n    {\r\n        foreach (var light in Scene.GetAllComponents\u003CPointLight\u003E().Where(x=\u003Ex.Shadows \u0026\u0026 (x.GameObject.Name.StartsWith(\u0022Foundry overhead key\u0022) || x.GameObject.Name.StartsWith(\u0022Service annex luminaire\u0022))).ToArray())\r\n        {\r\n            var down = new GameObject(light.GameObject) { NetworkMode=NetworkMode.Never, Name = \u0022Downward fixture light\u0022 };\r\n            down.WorldRotation = Rotation.LookAt(Vector3.Down, Vector3.Forward);\r\n            var spot = down.Components.Create\u003CSpotLight\u003E();\r\n            spot.LightColor = light.LightColor * .45f;\r\n            spot.Radius = light.Radius;\r\n            spot.ConeInner = 65;\r\n            spot.ConeOuter = 85;\r\n            spot.Shadows = true;\r\n            spot.Attenuation = light.Attenuation;\r\n            fixtures.Add((light,light.LightColor,down));\r\n            light.LightColor *= .3f;\r\n            light.Shadows = false;\r\n        }\r\n    }\r\n    protected override void OnDestroy()\r\n    {\r\n        foreach (var fixture in fixtures)\r\n        {\r\n            if(fixture.light.IsValid()){fixture.light.LightColor=fixture.color;fixture.light.Shadows=true;}\r\n            if(fixture.down.IsValid())fixture.down.Destroy();\r\n        }\r\n        fixtures.Clear();\r\n    }\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"HitDirectionChecks.cs","FileName":"HitDirectionChecks.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nnamespace PaintballBaddies;\npublic sealed class HitDirectionChecks : Component\n{\n    private float elapsed;\n    private int stage;\n    private PlayerController player;\n    private PaintballCombatant state;\n    private void Check(string name,bool pass)=\u003ELog.Info($\u0022HIT_DIRECTION {(pass?\u0022PASS\u0022:\u0022FAIL\u0022)} {name}\u0022);\n    protected override void OnUpdate()\n    {\n        elapsed\u002B=Time.Delta;\n        if(stage==0 \u0026\u0026 elapsed\u003E.5f)\n        {\n            player=Scene.GetAllComponents\u003CPlayerController\u003E().First();\n            var match=player.Components.Get\u003CArenaMatch\u003E();match.StartRound();state=match.PlayerState;\n            player.UseLookControls=false;player.EyeAngles=new Angles(0,0,0);\n            foreach(var entry in new[]{(Vector3.Forward,\u0022AHEAD\u0022),(Vector3.Backward,\u0022BEHIND\u0022),(Rotation.Identity.Right,\u0022RIGHT\u0022),(-Rotation.Identity.Right,\u0022LEFT\u0022)})\n            {\n                state.ResetPaintHits();state.RegisterHit(1,player.WorldPosition\u002Bentry.Item1*100);\n                Check(entry.Item2,state.HitDirectionLabel(Rotation.Identity)==entry.Item2);\n            }\n            var before=state.LastHitDirection;\n            Check(\u0022friendly hit preserves feedback\u0022,!state.RegisterHit(0,player.WorldPosition\u002BVector3.Forward*100) \u0026\u0026 state.LastHitDirection==before);\n            state.ResetPaintHits();Check(\u0022reset clears direction\u0022,state.LastHitDirection==Vector3.Zero \u0026\u0026 state.HitDirectionLabel(Rotation.Identity)==\u0022\u0022);\n            stage\u002B\u002B;\n        }\n        if(stage==1 \u0026\u0026 elapsed\u003E1)\n        {\n            var shot=new GameObject(true,\u0022Directional projectile test\u0022);shot.WorldPosition=player.WorldPosition\u002BVector3.Backward*70\u002BVector3.Up*40;\n            var ball=shot.Components.Create\u003COpponentPaintball\u003E();ball.Shooter=shot;ball.Velocity=Vector3.Forward*2283;\n            stage\u002B\u002B;\n        }\n        if(stage==2 \u0026\u0026 elapsed\u003E1.15f)\n        {\n            Check(\u0022physical shot reports behind\u0022,state.PaintHits==1 \u0026\u0026 state.HitDirectionLabel(Rotation.Identity)==\u0022BEHIND\u0022);\n            Check(\u0022view rotation updates direction\u0022,state.HitDirectionLabel(Rotation.FromYaw(180))==\u0022AHEAD\u0022);stage\u002B\u002B;\n        }\n        if(stage==3 \u0026\u0026 elapsed\u003E1.8f){Check(\u0022feedback expires\u0022,state.HitDirectionLabel(Rotation.Identity)==\u0022\u0022);stage\u002B\u002B;}\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"MultiplayerSession.Lobbies.cs","FileName":"MultiplayerSession.Lobbies.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing Sandbox.Network;\r\nusing System;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nnamespace PaintballBaddies;\r\n\r\npublic sealed partial class MultiplayerSession\r\n{\r\n    public bool Searching {get;private set;}\r\n    public bool QuickPlaying {get;private set;}\r\n    public bool CanQuickPlay=\u003E!Online || (Networking.IsHost \u0026\u0026 RoundId==0 \u0026\u0026 Players.Count()\u003C=1);\r\n    public int LobbyRevision {get;private set;}\r\n    private CancellationTokenSource lobbySearch;\r\n    private int searchVersion;\r\n    private int playVersion;\r\n\r\n    public static bool CompatibleLobby(LobbyInformation lobby)=\u003Elobby.Get(\u0022pb_protocol\u0022,\u0022\u0022)==Protocol;\r\n    public static string LobbyLabel(LobbyInformation lobby)\n    {\n        string mode=lobby.Get(\u0022pb_mode\u0022,\u0022\u0022);\n        string title=mode==\u0022overdrive\u0022 ? \u0022SAUSAGE SURVIVAL\u0022 : mode==\u0022pop_riot\u0022 ? \u0022POP RIOT\u0022 : \u0022PAINT RUSH\u0022;\n        string length=mode==\u0022overdrive\u0022 ? \u00223 WAVES\u0022 : $\u0022{lobby.Get(\u0022pb_minutes\u0022,\u00223\u0022)} MIN\u0022;\n        return $\u0022{lobby.Name} / {title} / {lobby.Members}/{lobby.MaxMembers} PEOPLE / {length}\u0022\n            \u002B(lobby.IsFull ? \u0022 / FULL\u0022 : !CompatibleLobby(lobby) ? \u0022 / DIFFERENT BUILD\u0022 : \u0022\u0022);\n    }\n\r\n    public async void RefreshLobbies()=\u003Eawait RefreshLobbiesAsync();\r\n    public async Task\u003Cbool\u003E RefreshLobbiesAsync()\r\n    {\r\n        if(Searching || !CanQuickPlay || Networking.IsConnecting)return false;\r\n        int version=\u002B\u002BsearchVersion;\r\n        Searching=true;LobbyRevision\u002B\u002B;Notice=\u0022Looking for games...\u0022;\r\n        using var search=new CancellationTokenSource(TimeSpan.FromSeconds(12));\r\n        lobbySearch=search;\r\n        try\r\n        {\r\n            var found=await Networking.QueryLobbies(search.Token);\r\n            await GameTask.MainThread();\r\n            if(!IsValid || search.IsCancellationRequested || version!=searchVersion)return false;\r\n            Lobbies=found.Where(x=\u003E!x.IsHidden \u0026\u0026 x.OwnerId!=Connection.Local.SteamId.ValueUnsigned)\r\n                .OrderBy(x=\u003Ex.IsFull || !CompatibleLobby(x)).ThenByDescending(x=\u003Ex.Members).ToList();\r\n            Notice=Lobbies.Count==0 ? \u0022No matches found. Play starts one with AI, ready for people to join.\u0022\r\n                : \u0022Choose a game. Full lobbies and different builds cannot be joined.\u0022;\r\n            return true;\r\n        }\r\n        catch(OperationCanceledException){await GameTask.MainThread();if(IsValid \u0026\u0026 version==searchVersion)Notice=\u0022Search timed out. Check Steam and try Play again.\u0022;}\r\n        catch(Exception){await GameTask.MainThread();if(IsValid \u0026\u0026 version==searchVersion)Notice=\u0022Cannot reach matches. Check Steam and try Play again.\u0022;}\r\n        finally {if(IsValid \u0026\u0026 version==searchVersion){Searching=false;LobbyRevision\u002B\u002B;lobbySearch=null;}}\r\n        return false;\r\n    }\r\n    // Use the engine\u0027s lobby directory and connection transport, with game-specific\r\n    // compatibility, public-access and self-host filters before joining.\r\n    public static IEnumerable\u003CLobbyInformation\u003E PlayCandidates(IEnumerable\u003CLobbyInformation\u003E lobbies,ulong localOwner)\r\n        =\u003Elobbies.Where(x=\u003E!x.IsHidden \u0026\u0026 !x.IsFull \u0026\u0026 x.Members\u003E0 \u0026\u0026 CompatibleLobby(x)\r\n            \u0026\u0026 x.Get(\u0022pb_access\u0022,\u0022\u0022)==\u0022public\u0022 \u0026\u0026 x.OwnerId!=localOwner \u0026\u0026 x.LobbyId!=0)\r\n            .OrderByDescending(x=\u003Ex.Members).ThenBy(x=\u003Ex.LobbyId);\r\n    public async void QuickPlay()\r\n    {\r\n        if(QuickPlaying || Searching || Networking.IsConnecting)return;\r\n        var controls=Scene.GetAllComponents\u003CPaintballControls\u003E().FirstOrDefault();\r\n        if(!CanQuickPlay){controls?.ResumeGame();return;}\r\n        if(controls.IsValid() \u0026\u0026 !controls.Open)controls.Toggle();\r\n        int request=\u002B\u002BplayVersion;\r\n        QuickPlaying=true;\r\n        bool success=await RefreshLobbiesAsync();\r\n        await GameTask.MainThread();\r\n        if(!IsValid || !QuickPlaying || request!=playVersion)return;\r\n        QuickPlaying=false;LobbyRevision\u002B\u002B;\r\n        if(!success)return;\r\n        // A person may have joined our platform-created lobby while searching.\r\n        // Keep their match rather than moving its host to a different lobby.\r\n        if(!CanQuickPlay){controls?.ResumeGame();return;}\r\n        var candidates=PlayCandidates(Lobbies,Connection.Local.SteamId.ValueUnsigned)\n            .Where(x=\u003Ex.Get(\u0022pb_mode\u0022,\u0022paint_rush\u0022)==ModeKey(Mode) \u0026\u0026 x.Get(\u0022pb_arena\u0022,ArenaSelection.Foundry)==ArenaSelection.Current(Scene)).ToArray();\n        if(candidates.Length\u003E0)JoinLobby(candidates[0]);\r\n        else if(Online)controls?.ResumeGame();\r\n        else Host();\r\n    }\r\n    public void CancelPlaySearch()\n    {\r\n        lobbySearch?.Cancel();lobbySearch=null;searchVersion\u002B\u002B;playVersion\u002B\u002B;\r\n        Searching=false;QuickPlaying=false;LobbyRevision\u002B\u002B;Notice=\u0022Search cancelled.\u0022;\r\n    }\n    public void CancelMenuConnection()\n    {\n        CancelPlaySearch();\n        if(!Networking.IsConnecting)return;\n        Networking.Disconnect();\n        Notice=\u0022Connection cancelled. Choose another match or start your own.\u0022;\n    }\n    public void JoinLobby(LobbyInformation lobby)\r\n    {\r\n        if(lobby.IsFull){Notice=\u0022That lobby is full. Refresh to find another game.\u0022;return;}\r\n        if(!CompatibleLobby(lobby)){Notice=\u0022That lobby uses a different build. Update the game and refresh.\u0022;return;}\r\n        Join(lobby.LobbyId);\r\n    }\r\n    protected override void OnDestroy()=\u003ECancelPlaySearch();\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"OpponentCoverPlanner.cs","FileName":"OpponentCoverPlanner.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing Sandbox.Navigation;\r\nnamespace PaintballBaddies;\r\n\r\n/// \u003Csummary\u003EFind a physically sheltered destination relative to an observed threat.\u003C/summary\u003E\r\npublic static class OpponentCoverPlanner\r\n{\r\n    public static CoverSurface ShelteringSurface(GameObject actor,GameObject threat,Vector3 origin,Vector3 position)\r\n    {\r\n        var hit=actor.Scene.Trace.Ray(origin\u002BVector3.Up*55,position\u002BVector3.Up*30)\r\n            .IgnoreGameObjectHierarchy(actor).IgnoreGameObjectHierarchy(threat).Run();\r\n        return CoverSurface.Resolve(actor.Scene,hit);\n    }\r\n    public static bool IsOccupied(GameObject actor,CoverSurface surface)\r\n    {\r\n        if(!surface.IsValid())return false;\r\n        return actor.Scene.GetAllComponents\u003CCoverController\u003E().Any(x=\u003Ex.GameObject!=actor \u0026\u0026 x.ReservedSurface==surface)\r\n            || actor.Scene.GetAllComponents\u003CArenaOpponent\u003E().Any(x=\u003Ex.GameObject!=actor \u0026\u0026 x.GameObject.Active\r\n                \u0026\u0026 x.Components.Get\u003CPaintballCombatant\u003E()?.Eliminated!=true \u0026\u0026 x.HasCoverPlan \u0026\u0026 x.ReservedCover==surface);\r\n    }\r\n    public static bool IsSheltered(GameObject actor, GameObject threatActor, Vector3 observedThreat, Vector3 position)\r\n    {\r\n        var shield=actor.Scene.Trace.Ray(observedThreat\u002BVector3.Up*55,position\u002BVector3.Up*30)\r\n            .IgnoreGameObjectHierarchy(actor).IgnoreGameObjectHierarchy(threatActor).Run();\r\n        return CoverSurface.Resolve(actor.Scene,shield).IsValid();\n    }\r\n    public static bool TryPeek(GameObject actor,GameObject threatActor,Vector3 threat,Vector3 shelter,out Vector3 peek,out bool low)\r\n    {\r\n        peek=shelter;low=false;\r\n        var shield=actor.Scene.Trace.Ray(threat\u002BVector3.Up*55,shelter\u002BVector3.Up*30)\r\n            .IgnoreGameObjectHierarchy(actor).IgnoreGameObjectHierarchy(threatActor).Run();\r\n        var surface=CoverSurface.Resolve(actor.Scene,shield);\n        if(!surface.IsValid())return false;\r\n        low=!surface.StandingOnly \u0026\u0026 surface.Top-shelter.z\u003C64;\r\n        var away=(shelter-threat).WithZ(0).Normal;\r\n        var side=Vector3.Cross(Vector3.Up,away);\r\n        var candidates=low ? new[]{shelter,shelter\u002Bside*44,shelter-side*44} : new[]{shelter\u002Bside*55,shelter-side*55,shelter\u002Bside*88,shelter-side*88};\r\n        foreach(var candidate in candidates)\r\n        {\r\n            // Match the destination to the engine\u0027s walkable surface before\r\n            // checking the firing lane; MoveTo otherwise silently stops short.\r\n            var grounded=actor.Scene.NavMesh.GetClosestPoint(candidate,32);\r\n            if(grounded is not Vector3 point || System.MathF.Abs(point.z-shelter.z)\u003E8)continue;\r\n            var route=actor.Scene.Trace.Sphere(16,shelter\u002BVector3.Up*50,point\u002BVector3.Up*50).IgnoreGameObjectHierarchy(actor).Run();\r\n            if(route.Hit)continue;\r\n            var floor=actor.Scene.Trace.Ray(point\u002BVector3.Up*12,point-Vector3.Up*24).IgnoreGameObjectHierarchy(actor).Run();\r\n            if(!floor.Hit||floor.Normal.z\u003C.8f)continue;\r\n            var line=actor.Scene.Trace.Sphere(2,point\u002BVector3.Up*46,threat\u002BVector3.Up*40)\r\n                .IgnoreGameObjectHierarchy(actor).IgnoreGameObjectHierarchy(threatActor).WithoutTags(\u0022paintball_debris\u0022).Run();\r\n            if(line.Hit)continue;\r\n            peek=point;return true;\r\n        }\r\n        return false;\r\n    }\r\n    public static bool TryFind(GameObject actor,GameObject threatActor,Vector3 observedThreat,out Vector3 destination,bool requirePeek=true,bool relocating=false)\r\n    {\r\n        destination=default;float best=float.MaxValue;\r\n        var bot=actor.Components.Get\u003CArenaOpponent\u003E();\r\n        if(actor.Scene.NavMesh.IsGenerating)return false;\r\n        foreach(var surface in actor.Scene.GetAllComponents\u003CCoverSurface\u003E())\r\n        {\r\n            if(!surface.Enabled || !surface.GameObject.Enabled || IsOccupied(actor,surface))continue;\r\n            if(bot?.RecentlyUsed(surface)==true || (relocating \u0026\u0026 bot?.ReservedCover==surface))continue;\r\n            var away=(surface.WorldPosition-observedThreat).WithZ(0).Normal;\r\n            if(away.Length\u003C.9f)continue;\r\n            var centre=surface.WorldPosition.WithZ(actor.WorldPosition.z\u002B28);\r\n            var face=actor.Scene.Trace.Ray(centre\u002Baway*160,centre).IgnoreGameObjectHierarchy(actor).Run();\r\n            if(!surface.Matches(face))continue;\n            var point=(face.EndPosition\u002Baway*25).WithZ(actor.WorldPosition.z);\r\n            var floor=actor.Scene.Trace.Ray(point\u002BVector3.Up*20,point-Vector3.Up*40).IgnoreGameObjectHierarchy(actor).Run();\r\n            if(!floor.Hit || floor.Normal.z\u003C.8f)continue;\r\n            point=floor.EndPosition\u002BVector3.Up;\r\n            var grounded=actor.Scene.NavMesh.GetClosestPoint(point,32);\r\n            if(grounded is not Vector3 reachable || System.MathF.Abs(reachable.z-point.z)\u003E8)continue;\r\n            point=reachable;\r\n            if(surface.Top-point.z\u003C37)continue;\r\n            var body=actor.Scene.Trace.Sphere(15,point\u002BVector3.Up*20,point\u002BVector3.Up*21).IgnoreGameObjectHierarchy(actor).Run();\r\n            if(body.Hit)continue;\r\n            var shield=actor.Scene.Trace.Ray(observedThreat\u002BVector3.Up*55,point\u002BVector3.Up*30)\r\n                .IgnoreGameObjectHierarchy(actor).IgnoreGameObjectHierarchy(threatActor).Run();\r\n            if(!surface.Matches(shield))continue;\n            // Choose cover that also offers a reachable firing position, avoiding\r\n            // repeated trips into a blind pocket followed by immediate retreat.\r\n            if(requirePeek \u0026\u0026 !TryPeek(actor,threatActor,observedThreat,point,out _,out _))continue;\r\n            bool occupied=false;\r\n            foreach(var other in actor.Scene.GetAllComponents\u003CArenaOpponent\u003E())\r\n            {\r\n                if(other.Components.Get\u003CPaintballCombatant\u003E()?.Eliminated==true)continue;\r\n                if(other.GameObject!=actor \u0026\u0026 ((other.WorldPosition-point).WithZ(0).Length\u003C65\r\n                    || (other.HasCoverPlan \u0026\u0026 (other.CoverDestination-point).WithZ(0).Length\u003C65)))occupied=true;\r\n            }\r\n            float distance=(point-actor.WorldPosition).WithZ(0).Length;\r\n            if(occupied || distance\u003E(relocating ? 720 : 550) || (relocating \u0026\u0026 distance\u003C140))continue;\r\n            var path=actor.Scene.NavMesh.CalculatePath(new CalculatePathRequest{\r\n                Start=actor.WorldPosition,Target=point,Agent=actor.Components.Get\u003CNavMeshAgent\u003E()});\r\n            if(path.Status!=NavMeshPathStatus.Complete || path.Points.Count\u003C1)continue;\r\n            float routeLength=0,exposure=0;\r\n            var previous=actor.WorldPosition;\r\n            foreach(var step in path.Points)\r\n            {\r\n                routeLength\u002B=(step.Position-previous).Length;\r\n                if(relocating || bot?.EvadingAfterHit==true)\r\n                {\r\n                    var sample=(step.Position\u002Bprevious)*.5f\u002BVector3.Up*38;\r\n                    var sight=actor.Scene.Trace.Ray(observedThreat\u002BVector3.Up*55,sample)\r\n                        .IgnoreGameObjectHierarchy(actor).IgnoreGameObjectHierarchy(threatActor).Run();\r\n                    if(!sight.Hit)exposure\u002B=(step.Position-previous).Length;\r\n                }\r\n                previous=step.Position;\r\n            }\r\n            if(routeLength\u003E(relocating ? 1000 : 650))continue;\r\n            // Prefer shorter protected routes, but vary comparable choices and\r\n            // reward a new firing angle rather than another spot on one bunker.\r\n            float angleChange=1-Vector3.Dot((actor.WorldPosition-observedThreat).WithZ(0).Normal,\r\n                (point-observedThreat).WithZ(0).Normal);\r\n            float cost=distance\u002BrouteLength*.35f\u002Bexposure*.55f-(relocating ? angleChange*90 : 0)\u002BGame.Random.Float(0,65);\r\n            // Avoid sheltering from one observed opponent while exposing our\r\n            // back to the other visible contestants. No unseen positions used.\r\n            if(bot is not null)\r\n            foreach(var threat in bot.VisibleThreats)\r\n            {\r\n                if((threat-observedThreat).Length\u003C60)continue;\r\n                var danger=actor.Scene.Trace.Ray(threat\u002BVector3.Up*55,point\u002BVector3.Up*30)\r\n                    .IgnoreGameObjectHierarchy(actor).WithoutTags(\u0022paintball_debris\u0022,\u0022paintball_actor\u0022).Run();\r\n                if(!danger.Hit)cost\u002B=180;\r\n            }\r\n            if(cost\u003E=best)continue;\r\n            best=cost;destination=point;\r\n        }\r\n        return best\u003Cfloat.MaxValue;\r\n    }\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"OverdriveFragment.cs","FileName":"OverdriveFragment.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nnamespace PaintballBaddies;\r\n\r\npublic sealed class OverdriveFragment : Component\r\n{\r\n    private SkinnedModelRenderer renderer;\r\n    private readonly Dictionary\u003Cstring,Transform\u003E bones=new();\r\n    private float age;\n    private Vector3 launchVelocity;\n    protected override void OnStart()\n    {\n        var physics=Components.Get\u003CRigidbody\u003E();physics.MotionEnabled=true;\n        physics.Velocity=launchVelocity;physics.AngularVelocity=new Vector3(3,6,4);\n    }\n    public static void Spawn(SkinnedModelRenderer source,int part,Vector3 impulse)\r\n    {\r\n        if(!source.IsValid())return;\r\n        foreach(var old in source.Scene.GetAllComponents\u003COverdriveFragment\u003E().OrderBy(x=\u003Ex.age).Skip(11).ToArray())old.GameObject.Destroy();\r\n        string joint=part switch {1=\u003E\u0022head\u0022,2=\u003E\u0022arm_upper_L\u0022,4=\u003E\u0022arm_upper_R\u0022,8=\u003E\u0022leg_upper_L\u0022,_=\u003E\u0022leg_upper_R\u0022};\r\n        var rig=source.GameObject.Parent.Components.Get\u003COverdriveRobotRig\u003E();\r\n        var pivot=rig.IsValid() ? rig.IntactPose(joint) : source.WorldTransform;\r\n        var go=new GameObject(false,\u0022Tumbling sausage part\u0022){NetworkMode=NetworkMode.Never,WorldPosition=pivot.Position};go.Tags.Add(\u0022paintball_debris\u0022);\n        var piece=go.Components.Create\u003COverdriveFragment\u003E();piece.renderer=go.Components.Create\u003CSkinnedModelRenderer\u003E();\r\n        piece.renderer.Model=source.Model;piece.renderer.Tint=source.Tint;piece.renderer.UseAnimGraph=false;\r\n        OverdriveRobotRig.ApplyParts(piece.renderer,0,part);\r\n        foreach(var bone in source.Model.Bones.AllBones)\r\n        {\r\n            var pose=rig.IsValid() ? rig.IntactPose(bone.Name) : source.WorldTransform;pose=go.WorldTransform.ToLocal(pose);\r\n            piece.bones[bone.Name]=pose;\r\n        }\r\n        var collider=go.Components.Create\u003CSphereCollider\u003E();collider.Radius=part==1 ? 6 : 4;collider.Static=false;\n        var physics=go.Components.Create\u003CRigidbody\u003E();physics.MassOverride=2;\r\n        piece.launchVelocity=impulse.Normal*220\u002BVector3.Up*155;go.Enabled=true;\n    }\r\n    protected override void OnPreRender()\r\n    {foreach(var bone in renderer.Model.Bones.AllBones)renderer.SetBoneTransform(bone,bones[bone.Name]);}\r\n    protected override void OnUpdate(){age\u002B=Time.Delta;if(age\u003E3)GameObject.Destroy();}\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"OverdriveHeadBurst.cs","FileName":"OverdriveHeadBurst.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nnamespace PaintballBaddies;\r\n\r\n// Pieces cut from Terry\u0027s actual head, with preserved skin UVs and filling.\n// Native particles collide visually without becoming networked gameplay bodies.\r\npublic sealed class OverdriveHeadBurst : Component\r\n{\r\n    public float Age {get;private set;}\r\n    public int Shards=\u003EComponents.Get\u003CParticleEffect\u003E()?.ParticleCount ?? 0;\r\n    private Vector3 impulse;\r\n    private int seed;\n    private int variant;\n    public static void Spawn(Scene scene,Vector3 point,Vector3 direction,int variation,int headVariant=0)\n    {\r\n        foreach(var old in scene.GetAllComponents\u003COverdriveHeadBurst\u003E().OrderByDescending(x=\u003Ex.Age).SkipLast(3).ToArray())old.GameObject.Destroy();\r\n        var go=new GameObject(false,\u0022Terry sausage burst\u0022){WorldPosition=point,NetworkMode=NetworkMode.Never};\r\n        go.Tags.Add(\u0022paintball_debris\u0022);\r\n        var burst=go.Components.Create\u003COverdriveHeadBurst\u003E();burst.impulse=direction;burst.seed=variation;burst.variant=headVariant.Clamp(0,2);go.Enabled=true;\n        OverdriveAudio.Play(\u0022burst\u0022,point);\r\n    }\r\n    protected override void OnStart()\r\n    {\r\n        var particles=Components.Create\u003CParticleEffect\u003E();\r\n        particles.MaxParticles=14;particles.Lifetime=1.35f;particles.LocalSpace=0;\n        particles.ApplyRotation=false;particles.ApplyShape=false;particles.ApplyAlpha=false;particles.ApplyColor=false;\r\n        particles.Force=true;particles.ForceDirection=Vector3.Down*650;particles.ForceScale=1;\r\n        particles.ForceSpace=ParticleEffect.SimulationSpace.World;particles.Damping=.35f;\r\n        particles.Collision=true;particles.CollisionRadius=.7f;particles.Bounce=.28f;particles.Friction=.7f;particles.PushStrength=0;\r\n        particles.CollisionIgnore=new TagSet();particles.CollisionIgnore.Add(\u0022paintball_actor\u0022);particles.CollisionIgnore.Add(\u0022paintball_debris\u0022);\r\n        var render=Components.Create\u003CParticleModelRenderer\u003E();\r\n        render.Choices=Enumerable.Range(0,3).Select(i=\u003Enew ParticleModelRenderer.ModelEntry{Model=Model.Load($\u0022models/sausage/grumpy/head_piece_{variant}_{i}.vmdl\u0022)}).ToList();\n        render.Scale=1;render.CastShadows=false;render.RotateWithGameObject=false;\r\n        particles.OnStep=(p,dt)=\u003E{p.Angles\u002B=new Angles(170,260,130)*dt;p.Alpha=((1.35f-p.Age)/.35f).Clamp(0,1);};\n        var random=new Random(seed);\r\n        for(int i=0;i\u003C14;i\u002B\u002B)\r\n        {\r\n            var direction=new Vector3(MathF.Cos(i*2.399f\u002Bseed),MathF.Sin(i*2.399f\u002Bseed),-.3f\u002B(float)random.NextDouble()*1.3f).Normal;\r\n            var p=particles.Emit(WorldPosition\u002Bdirection*2,0);float size=.055f\u002B(float)random.NextDouble()*.13f;p.Size=new Vector3(size);\n            p.Angles=new Angles(i*27,i*47,i*19);p.StartAngles=p.Angles;p.Color=Color.White;\r\n            // Most material exits along the shot, smaller crumbs spread wider.\n            p.Velocity=direction*(65\u002B(float)random.NextDouble()*130)\u002Bimpulse.Normal*170\u002BVector3.Up*50;\n        }\n        for(int i=0;i\u003C3;i\u002B\u002B)SausageHeavyChunk.Spawn(WorldPosition,impulse.Normal,seed\u002Bi,i,variant);\n    }\r\n    protected override void OnUpdate(){Age\u002B=Time.Delta;if(Age\u003E2.4f)GameObject.Destroy();}\r\n}\n\npublic sealed class SausageHeavyChunk : Component,Component.ICollisionListener\n{\n    private float age;\n    private bool landed;\n    public static int Landings {get;private set;}\n    public static void Spawn(Vector3 point,Vector3 impulse,int seed,int size,int variant=0)\n    {\n        var scene=Game.ActiveScene;\n        foreach(var old in scene.GetAllComponents\u003CSausageHeavyChunk\u003E().OrderByDescending(c=\u003Ec.age).SkipLast(17).ToArray())old.GameObject.Destroy();\n        var go=new GameObject(false,\u0022Sausage chunk\u0022){NetworkMode=NetworkMode.Never,WorldPosition=point};go.Tags.Add(\u0022paintball_debris\u0022);\n        var mesh=new GameObject(go,true,\u0022Textured Terry fragment\u0022){NetworkMode=NetworkMode.Never,LocalScale=new Vector3(size==0 ? .65f : size==1 ? .85f : .55f)};\n        mesh.Components.Create\u003CModelRenderer\u003E().Model=Model.Load($\u0022models/sausage/grumpy/head_piece_{variant}_{size}.vmdl\u0022);\n        var collider=go.Components.Create\u003CSphereCollider\u003E();collider.Radius=1.2f\u002Bsize*.65f;collider.Static=false;\n        var body=go.Components.Create\u003CRigidbody\u003E();body.MassOverride=.5f\u002Bsize*.8f;body.LinearDamping=.2f;\n        go.Components.Create\u003CSausageHeavyChunk\u003E();go.Enabled=true;\n        body.Velocity=Rotation.FromYaw(seed*137).Forward*(75-size*12)\u002Bimpulse*175\u002BVector3.Up*(100\u002Bsize*20);\n        body.AngularVelocity=new Vector3(4\u002Bsize,8-size,seed%6\u002B2);\n    }\n    void Component.ICollisionListener.OnCollisionStart(Collision collision)\n    {\n        if(landed || age\u003C.12f)return;landed=true;Landings\u002B\u002B;\n        var sound=SausageAudio.Play(\u0022landing\u0022,WorldPosition);if(sound is not null){sound.Volume=.42f;sound.Pitch=.82f;}\n    }\n    protected override void OnUpdate(){age\u002B=Time.Delta;if(age\u003E3.8f)GameObject.Destroy();}\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"PaintRushReview.cs","FileName":"PaintRushReview.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\nnamespace PaintballBaddies;\n\n/// \u003Csummary\u003EOpt-in scoring, unlimited ammunition and actual one-minute match check.\u003C/summary\u003E\npublic sealed class PaintRushReview : Component\n{\n    [Property] public string CaptureId { get; set; } = \u0022\u0022;\n    private ArenaMatch match;\n    private PlayerController player;\n    private PaintballMarker weapon;\n    private float elapsed,fireClock;\n    private int stage;\n    private void Check(string name,bool passed) =\u003E Log.Info(\u0022PAINT_RUSH_CHECK \u0022\u002BJson.Serialize(new{capture_id=CaptureId,name,passed}));\n    protected override void OnUpdate()\n    {\n        elapsed\u002B=Time.Delta;\n        if(stage==0 \u0026\u0026 elapsed\u003E1)\n        {\n            match=Scene.GetAllComponents\u003CArenaMatch\u003E().First();\n            player=match.Components.Get\u003CPlayerController\u003E();weapon=match.Components.Get\u003CPaintballMarker\u003E();\n            Check(\u0022duration settings\u0022,match.SetRoundMinutes(3) \u0026\u0026 match.RoundDuration==180 \u0026\u0026 match.SetRoundMinutes(2) \u0026\u0026 match.RoundDuration==120 \u0026\u0026 !match.SetRoundMinutes(4) \u0026\u0026 match.SetRoundMinutes(1));\n            match.StartRound();player.UseInputControls=false;player.UseLookControls=false;stage=1;\n        }\n        if(stage==1 \u0026\u0026 elapsed\u003E2)\n        {\n            var actors=Scene.GetAllComponents\u003CPaintballCombatant\u003E().Where(x=\u003Ex.StayInMatch).ToArray();\n            Check(\u0022six distinct competitors\u0022,actors.Length==6 \u0026\u0026 actors.Select(x=\u003Ex.Team).Distinct().Count()==6);\n            Check(\u0022duration locked during round\u0022,!match.SetRoundMinutes(3));\n            var receiver=actors.First(x=\u003Ex!=match.PlayerState);\n            var before=match.PlayerState.HitsLanded;\n            for(int i=0;i\u003C50;i\u002B\u002B)receiver.RegisterHit(match.PlayerState.Team,player.WorldPosition,match.PlayerState);\n            Check(\u0022hits score without elimination\u0022,receiver.PaintHits\u003E=50 \u0026\u0026 !receiver.Eliminated \u0026\u0026 match.PlayerState.HitsLanded==before\u002B50 \u0026\u0026 match.InRound);\n            var current=receiver.PaintHits;\n            Check(\u0022self hits rejected\u0022,!receiver.RegisterHit(receiver.Team,receiver.WorldPosition,receiver) \u0026\u0026 receiver.PaintHits==current);\n            weapon.Reload();Check(\u0022no forced player reload\u0022,weapon.UnlimitedAmmo \u0026\u0026 weapon.ReloadRemaining==0);\n            stage=2;\n        }\n        if(stage==2 \u0026\u0026 match.InRound)\n        {\n            fireClock-=Time.Delta;\n            if(fireClock\u003C=0){weapon.FireAt(weapon.Muzzle\u002BVector3.Up*1000);fireClock=.12f;}\n        }\n        if(stage==2 \u0026\u0026 !match.InRound)\n        {\n            var bots=Scene.GetAllComponents\u003CArenaOpponent\u003E().Where(x=\u003Ex.ContestMode).ToArray();\n            Check(\u0022one minute ends on timer\u0022,match.Status==\u0022TIME UP\u0022 \u0026\u0026 match.Remaining==0 \u0026\u0026 elapsed\u003E=61 \u0026\u0026 elapsed\u003C65);\n            Check(\u0022unlimited player firing\u0022,weapon.Shots\u003E80 \u0026\u0026 weapon.Ammo==40 \u0026\u0026 weapon.Reserve==160 \u0026\u0026 weapon.ReloadRemaining==0);\n            Check(\u0022unlimited AI ammunition\u0022,bots.Length==5 \u0026\u0026 bots.All(x=\u003Ex.Magazine.Ammo==30 \u0026\u0026 x.Magazine.Reserve==120 \u0026\u0026 x.Magazine.Reloads==0));\n            Check(\u0022everyone remains visible\u0022,bots.All(x=\u003Ex.GameObject.Active \u0026\u0026 !x.Components.Get\u003CPaintballCombatant\u003E().Eliminated));\n            Check(\u0022score conservation\u0022,match.Standings.Sum(x=\u003Ex.Points)==0);\n            Check(\u0022rank ordering\u0022,match.Standings.Zip(match.Standings.Skip(1),(a,b)=\u003Ea.Points\u003Eb.Points || a.Points==b.Points \u0026\u0026 a.Received\u003C=b.Received).All(x=\u003Ex));\n            var hits=match.PlayerState.PaintHits;\n            Check(\u0022score freezes at horn\u0022,!match.PlayerState.RegisterHit(99) \u0026\u0026 match.PlayerState.PaintHits==hits);\n            Log.Info(\u0022PAINT_RUSH_RESULT \u0022\u002BJson.Serialize(new{capture_id=CaptureId,standings=match.Standings,shots=weapon.Shots,bot_shots=bots.Sum(x=\u003Ex.ShotsFired)}));\n            stage=3;\n        }\n    }\n}\n"},{"Ident":"solovolt.paintball-baddies","Path":"PopRiotTarget.Visuals.cs","FileName":"PopRiotTarget.Visuals.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nnamespace PaintballBaddies;\r\n\r\npublic sealed partial class PopRiotTarget\r\n{\r\n    private const string IndustrialPath=\u0022models/pop_riot/industrial/\u0022;\r\n    private GameObject shell,launcher;\r\n    private ModelRenderer skin;\r\n    private SkinnedModelRenderer drone;\r\n    private LineRenderer warningRing;\r\n    private readonly List\u003CModelRenderer\u003E liquid=new(),lamps=new(),glass=new(),shutters=new();\r\n    private float shutterOpen=1;\r\n    private bool wasCharging;\r\n    private ModelRenderer Part(string name,GameObject parent,Vector3 position,float scale=1,Color? tint=null)\r\n    {\r\n        var go=new GameObject(parent,true,name){NetworkMode=NetworkMode.Never,LocalPosition=position,LocalScale=new Vector3(scale),LocalRotation=Rotation.FromYaw(-90)};\r\n        var renderer=go.Components.Create\u003CModelRenderer\u003E();renderer.Model=Model.Load(IndustrialPath\u002Bname\u002B\u0022.vmdl\u0022);\r\n        renderer.Tint=tint ?? Color.White;return renderer;\r\n    }\r\n    private void Capsule(GameObject parent,Vector3 position,float scale,int index,bool armored)\r\n    {\r\n        var assembly=new GameObject(parent,true,\u0022Pressure chamber\u0022){NetworkMode=NetworkMode.Never,LocalPosition=position,LocalScale=new Vector3(scale)};\r\n        if(Kind==PopKind.Giant)assembly.LocalRotation=Rotation.FromYaw(index*120);\r\n        Part(\u0022capsule_frame\u0022,assembly,Vector3.Zero);\r\n        glass.Add(Part(\u0022capsule_glass\u0022,assembly,Vector3.Zero));\r\n        liquid.Add(Part(\u0022capsule_paint\u0022,assembly,Vector3.Zero,1,PaintTint));\r\n        lamps.Add(Part(\u0022capsule_light\u0022,assembly,Vector3.Zero,1,PaintTint));\r\n        if(armored)shutters.Add(Part(\u0022shutter\u0022,assembly,Vector3.Zero));\r\n        if(Kind==PopKind.Giant)\r\n        {\r\n            var hit=assembly.Components.Create\u003CBoxCollider\u003E();hit.Scale=new Vector3(25,25,34);hit.Static=false;\r\n        }\r\n    }\r\n    private void BuildIndustrialVisuals()\r\n    {\r\n        shell=new GameObject(GameObject,true,\u0022Industrial pressure target\u0022){NetworkMode=NetworkMode.Never};\r\n        if(Kind==PopKind.Glob)\r\n        {\r\n            skin=Sphere(shell,Vector3.Zero,Radius,PaintTint,false);\r\n            Sphere(shell,Vector3.Forward*(Radius*.25f),Radius*.55f,PaintTint*1.35f,true);\r\n            return;\r\n        }\r\n        if(Kind==PopKind.Giant)\r\n        {\r\n            Part(\u0022machine_base\u0022,shell,Vector3.Zero);\r\n            for(int i=0;i\u003C3;i\u002B\u002B)Capsule(shell,SpotOffset(i),1.6f,i,true);\r\n        }\r\n        else if(Kind==PopKind.Blob)\r\n        {\r\n            var airframe=new GameObject(shell,true,\u0022Rust quadcopter\u0022){NetworkMode=NetworkMode.Never,LocalScale=new Vector3(.88f)};\r\n            drone=airframe.Components.Create\u003CSkinnedModelRenderer\u003E();drone.Model=RiotDroneAssets.Drone;drone.UseAnimGraph=false;\r\n            if(drone.Model.IsError)LoadAirframe();\r\n            Capsule(shell,Vector3.Down*15,.65f,0,false);\r\n            launcher=new GameObject(GameObject,true,\u0022Drone paint launcher\u0022){NetworkMode=NetworkMode.Never,LocalPosition=Vector3.Down*12};\r\n            var barrel=Part(\u0022capsule_frame\u0022,launcher,Vector3.Forward*12,.48f);\r\n            barrel.LocalRotation=Rotation.FromPitch(90)*Rotation.FromYaw(-90);\r\n            var bore=Sphere(launcher,Vector3.Forward*20,4.8f,new Color(.018f,.02f,.022f),false);\r\n            bore.GameObject.LocalScale=new Vector3(.012f,.15f,.15f);\r\n            warningRing=ArenaGlow.Ring(GameObject,\u0022Drone attack warning\u0022,43,1.4f,new Color(1,.38f,.055f),32);\r\n            warningRing.LocalRotation=Rotation.FromPitch(90);warningRing.Enabled=false;\r\n        }\r\n        else\r\n        {\r\n            Capsule(shell,Vector3.Zero,Kind==PopKind.Chain ? .92f : .8f,0,false);\r\n            if(Kind==PopKind.Chain)\r\n            {\r\n                // Twin cartridges distinguish chain reactors by shape as well as colour.\r\n                Capsule(shell,new Vector3(0,-15,-2),.5f,1,false);\r\n                Capsule(shell,new Vector3(0,15,-2),.5f,2,false);\r\n            }\r\n        }\r\n    }\r\n    private async void LoadAirframe()\r\n    {\r\n        var model=await RiotDroneAssets.LoadAsync();\r\n        if(drone.IsValid() \u0026\u0026 model.IsValid() \u0026\u0026 !model.IsError)drone.Model=model;\r\n    }\r\n    protected override void OnPreRender()\r\n    {\r\n        if(!drone.IsValid())return;\r\n        int i=0;\r\n        foreach(var bone in drone.Model.Bones.AllBones)\r\n        {\r\n            if(!drone.TryGetBoneTransformAnimation(bone,out var pose))continue;\r\n            var local=drone.WorldTransform.ToLocal(pose);\r\n            local.Rotation*=Rotation.FromYaw(age*(Crashing ? 450 : 1800)*(i\u002B\u002B%2==0 ? 1 : -1));\r\n            drone.SetBoneTransform(bone,local);\r\n        }\r\n    }\r\n    private void UpdateIndustrialVisuals()\r\n    {\r\n        if(!shell.IsValid())return;\r\n        if(Charging \u0026\u0026 !wasCharging)DroneAudio.Play(\u0022charge\u0022,WorldPosition);\r\n        wasCharging=Charging;\r\n        if(Kind==PopKind.Glob)\r\n        {\r\n            shell.LocalRotation=Rotation.LookAt(GlobVelocity.Length\u003E1 ? GlobVelocity.Normal : Vector3.Forward);\r\n            shell.LocalScale=new Vector3(1.12f,.94f,.94f);return;\r\n        }\r\n        if(Kind==PopKind.Blob)\r\n        {\r\n            var facing=Charging ? AttackAim-WorldPosition : FlightVelocity;\r\n            if(launcher.IsValid())\r\n            {\r\n                launcher.LocalRotation=Charging ? Rotation.LookAt(AttackAim-launcher.WorldPosition) : shell.LocalRotation;\r\n                launcher.Enabled=!Crashing;\r\n            }\r\n            float yaw=facing.WithZ(0).Length\u003E1 ? Rotation.LookAt(facing.WithZ(0)).Yaw() : shell.LocalRotation.Yaw();\r\n            float damage=1-(float)Health/MaximumHealth;\r\n            var bank=Rotation.FromRoll(MathF.Sin(age*1.6f\u002BSeed)*4\u002BMathF.Sin(age*9)*damage*13\u002BMathF.Sin(age*31)*hitPulse*70);\r\n            if(Crashing)bank=Rotation.FromRoll(18\u002BCrashSeconds*280)*Rotation.FromPitch(CrashSeconds*110);\r\n            shell.LocalRotation=Rotation.Lerp(shell.LocalRotation,Rotation.FromYaw(yaw)*bank,1-MathF.Exp(-6*Time.Delta));\r\n            if(warningRing.IsValid())\r\n            {\r\n                warningRing.Enabled=!Crashing \u0026\u0026 (Charging || Entering);\r\n                if(Scene.Camera.IsValid())warningRing.WorldRotation=Rotation.LookAt(Scene.Camera.WorldPosition-WorldPosition)*Rotation.FromPitch(90);\r\n                warningRing.LocalScale=Vector3.One*(Charging ? .9f\u002B.35f*ChargeRemaining/ChargeDuration : 1);\r\n                var warning=(Entering ? new Color(.15f,.85f,.8f) : new Color(1,.25f,.04f))*1.5f;\r\n                warningRing.Color=Gradient.FromColors(warning,warning);\r\n            }\r\n        }\r\n        else if(Kind!=PopKind.Giant)shell.LocalRotation=Rotation.FromYaw(Seed*47\u002Bage*12)*Rotation.FromRoll(MathF.Sin(age*1.2f)*2);\r\n        float goal=Kind==PopKind.Giant ? (WeakSpotsExposed ? 1 : 0) : (3-Health)*.38f;\r\n        shutterOpen=MathX.Lerp(shutterOpen,goal,1-MathF.Exp(-12*Time.Delta));\r\n        for(int i=0;i\u003Cliquid.Count;i\u002B\u002B)\r\n        {\r\n            bool intact=Kind!=PopKind.Giant || ChamberHealth(i)\u003E0;\r\n            liquid[i].Enabled=glass[i].Enabled=intact;\r\n            float pulse=Charging ? 1.7f\u002BMathF.Sin(age*19)*.5f : 1;\r\n            lamps[i].Tint=intact ? (Kind==PopKind.Blob \u0026\u0026 (Charging || Health\u003C=2) ? new Color(1,.32f,.06f) : PaintTint)*pulse : new Color(.025f,.025f,.025f);\r\n            liquid[i].Tint=Color.Lerp(PaintTint,Color.White,hitPulse*.9f);\r\n            float fill=Kind==PopKind.Giant ? (float)ChamberHealth(i)/(MaximumHealth/3) : 1;\r\n            liquid[i].GameObject.LocalScale=new Vector3(1,1,.5f\u002Bfill*.5f);\r\n            liquid[i].GameObject.LocalPosition=Vector3.Down*(1-fill)*7;\r\n        }\r\n        foreach(var shutter in shutters)\r\n        {\r\n            shutter.GameObject.LocalRotation=Rotation.FromYaw(-90);\r\n            shutter.GameObject.LocalPosition=Kind==PopKind.Giant ? Vector3.Down*(38*shutterOpen) : Vector3.Up*(5-22*shutterOpen);\r\n        }\r\n    }\r\n}\r\n"},{"Ident":"solovolt.paintball-baddies","Path":"PopRiotTarget.cs","FileName":"PopRiotTarget.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":396491,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nnamespace PaintballBaddies;\r\n\r\n// Enum values stay stable for the existing score and network effect paths.\r\npublic enum PopKind { Bubble, Blob, Chain, Giant, Glob }\r\n\r\npublic sealed partial class PopRiotTarget : Component\r\n{\r\n    [Sync(SyncFlags.FromHost)] public PopKind Kind {get;set;}\r\n    [Sync(SyncFlags.FromHost)] public int Health {get;set;}\r\n    [Sync(SyncFlags.FromHost)] public int Round {get;set;}\r\n    [Sync(SyncFlags.FromHost)] public int Seed {get;set;}\r\n    [Sync(SyncFlags.FromHost)] public bool Popped {get;private set;}\r\n    [Sync(SyncFlags.FromHost)] public Vector3 Anchor {get;set;}\r\n    [Sync(SyncFlags.FromHost)] public int ChamberA {get;private set;}\r\n    [Sync(SyncFlags.FromHost)] public int ChamberB {get;private set;}\r\n    [Sync(SyncFlags.FromHost)] public int ChamberC {get;private set;}\r\n    public static float SizeFor(PopKind kind)=\u003Ekind switch {PopKind.Glob=\u003E4.2f,PopKind.Blob=\u003E38,PopKind.Giant=\u003E84,PopKind.Chain=\u003E25,_=\u003E22};\r\n    public float Radius=\u003ESizeFor(Kind);\r\n    public int MaximumHealth=\u003EKind switch {PopKind.Blob=\u003E8,PopKind.Giant=\u003EMultiplayerSession.Find(Scene)?.ActiveMinutes==1 ? 30 : 60,_=\u003E1};\r\n    public Color PaintTint=\u003EKind switch {PopKind.Chain=\u003Enew Color(1,.68f,.08f),PopKind.Giant=\u003Enew Color(.94f,.58f,.08f),PopKind.Blob=\u003Enew Color(.85f,.12f,.49f),_=\u003E(Seed%3) switch {0=\u003Enew Color(.96f,.23f,.55f),1=\u003Enew Color(.08f,.7f,.87f),_=\u003Enew Color(.62f,.9f,.16f)}};\r\n    public int ActiveChambers=\u003EKind==PopKind.Giant ? Enumerable.Range(0,3).Count(i=\u003EChamberHealth(i)\u003E0) : 1;\r\n    public bool CanBeDamaged=\u003EKind!=PopKind.Giant || WeakSpotsExposed;\r\n    private float age,hitPulse;\r\n    private int shownHealth=-1;\r\n    internal bool ReviewStill {get;set;}\r\n    public static PopRiotTarget Find(GameObject go)\r\n    {\r\n        while(go.IsValid()){var t=go.Components.Get\u003CPopRiotTarget\u003E();if(t.IsValid())return t;go=go.Parent;}return null;\r\n    }\r\n    internal void InitializeHealth()\r\n    {\r\n        Health=MaximumHealth;\r\n        if(Kind==PopKind.Giant)ChamberA=ChamberB=ChamberC=Health/3;\r\n    }\r\n    public int ChamberHealth(int index)=\u003Eindex switch {0=\u003EChamberA,1=\u003EChamberB,_=\u003EChamberC};\r\n    private void DamageChamber(int index)\r\n    {\r\n        if(index==0)ChamberA=Math.Max(0,ChamberA-1);\r\n        else if(index==1)ChamberB=Math.Max(0,ChamberB-1);\r\n        else ChamberC=Math.Max(0,ChamberC-1);\r\n        Health=ChamberA\u002BChamberB\u002BChamberC;\r\n    }\r\n    protected override void OnStart()\r\n    {\r\n        GameObject.Tags.Add(\u0022pop_target\u0022);\r\n        if(Kind==PopKind.Giant)\r\n        {\r\n            var hull=Components.Create\u003CSphereCollider\u003E();hull.Radius=28;hull.Static=false;\r\n            var baseHull=Components.Create\u003CBoxCollider\u003E();baseHull.Center=Vector3.Down*72;baseHull.Scale=new Vector3(118,118,20);baseHull.Static=false;\r\n        }\r\n        else {var hull=Components.Create\u003CSphereCollider\u003E();hull.Radius=Kind==PopKind.Glob ? GlobInterceptRadius : Radius;hull.Static=false;}\r\n        BuildIndustrialVisuals();shownHealth=Health;SetupThreat();\r\n    }\r\n    internal static ModelRenderer Sphere(GameObject parent,Vector3 position,float radius,Color tint,bool glow)\r\n    {\r\n        var go=new GameObject(parent,true,\u0022Paint liquid\u0022){NetworkMode=NetworkMode.Never,LocalPosition=position,LocalScale=new Vector3(radius/32)};\r\n        var model=go.Components.Create\u003CModelRenderer\u003E();model.Model=Model.Load(\u0022models/dev/sphere.vmdl\u0022);\r\n        model.MaterialOverride=Material.Load(glow ? \u0022materials/dev/primary_white_emissive.vmat\u0022 : \u0022models/pop_riot/industrial/paint.vmat\u0022);\r\n        model.Tint=tint;return model;\r\n    }\r\n    private Vector3 SpotOffset(int i)=\u003ERotation.FromYaw(i*120).Forward*50\u002BVector3.Up*5;\r\n    public Vector3 ChamberPoint(int i)=\u003EWorldTransform.PointToWorld(SpotOffset(i));\r\n    public Vector3 AimPoint(Vector3 from)=\u003EKind!=PopKind.Giant ? WorldPosition : Enumerable.Range(0,3)\r\n        .Where(i=\u003EChamberHealth(i)\u003E0).Select(ChamberPoint).OrderBy(p=\u003E(p-from).Length).DefaultIfEmpty(WorldPosition).First();\r\n    protected override void OnUpdate()\r\n    {\r\n        age\u002B=Time.Delta;hitPulse=MathF.Max(0,hitPulse-Time.Delta);\r\n        if(Health!=shownHealth){shownHealth=Health;hitPulse=.2f;}\r\n        UpdateIndustrialVisuals();\r\n        if(Crashing){UpdateCrash();return;}\r\n        if(!MultiplayerSession.Authority || ReviewStill || Popped)return;\r\n        var session=MultiplayerSession.Find(Scene);\r\n        if(!session.IsValid() || !session.IsCooperative || !session.InRound || session.RoundId!=Round){GameObject.Destroy();return;}\r\n        if(Kind==PopKind.Glob){AdvanceGlob(session);return;}\r\n        AdvanceThreat(session);\r\n        if(Kind==PopKind.Giant || session.RiotReviewPaused)return;\r\n        AdvanceHover(session);\r\n    }\r\n    public bool Hit(Vector3 impact,PaintballCombatant shooter)\r\n    {\r\n        var session=MultiplayerSession.Find(Scene);\r\n        if(!MultiplayerSession.Authority || (Popped \u0026\u0026 !Crashing) || !session.IsValid() || !session.InRound || !session.IsCooperative || session.RoundId!=Round || !shooter.IsValid() || !shooter.AcceptHits)return false;\r\n        if(Crashing){FinishCrash(WorldPosition);return true;}\r\n        if(Kind==PopKind.Giant)\r\n        {\r\n            if(!WeakSpotsExposed)return false;\r\n            int index=Enumerable.Range(0,3).Where(i=\u003EChamberHealth(i)\u003E0).FirstOrDefault(i=\u003E\r\n            {\r\n                var delta=WorldTransform.PointToLocal(impact)-SpotOffset(i);\r\n                return delta.WithZ(0).Length\u003C23 \u0026\u0026 MathF.Abs(delta.z)\u003C30;\r\n            },-1);\r\n            if(index\u003C0)return false;\r\n            DamageChamber(index);\r\n            if(ChamberHealth(index)==0)\r\n                NetworkEffects.RiotBurst(ChamberPoint(index),30,PaintTint,false,false,Round);\r\n        }\r\n        else Health=Math.Max(0,Health-1);\r\n        session.RiotHit(shooter);\r\n        if(Health==0)Pop(shooter,false);\r\n        else\r\n        {\r\n            NetworkEffects.RiotCue(\u0022collect\u0022);\r\n            if(Kind==PopKind.Blob)NetworkEffects.RiotDroneFlare(WorldPosition,PaintTint,false,Round,Seed);\r\n        }\r\n        return true;\r\n    }\r\n    internal void Pop(PaintballCombatant shooter,bool chained)\r\n    {\r\n        if(!MultiplayerSession.Authority || Popped)return;\r\n        var session=MultiplayerSession.Find(Scene);\r\n        if(!session.IsValid() || !session.InRound || !session.IsCooperative || Round!=session.RoundId)return;\r\n        Popped=true;Health=0;\r\n        session.RiotPop(this,shooter,chained);\r\n        if(Kind==PopKind.Blob)BeginCrash();\r\n        else GameObject.Destroy();\r\n    }\r\n}\r\n"}]}