SausageEggSlick.cs
using Sandbox;
using System;
namespace PaintballBaddies;

// A visible spill, with authoritative and deliberately occasional slapstick.
// Only sausages slip; this never steals the player's controls.
public sealed class SausageEggSlick : Component
{
    [Sync(SyncFlags.FromHost)] public int Round {get;set;}
    private float age,nextSlip;
    // Each sausage gets one roll per pass; a slick can floor up to three.
    private readonly Dictionary<Guid,float> attempted=new();
    private int uses;
    const int MaxUses=3;const float Life=25;
    public static int Spills {get;private set;}
    public static void Spawn(Scene scene,Vector3 point,int round)
    {
        if(!MultiplayerSession.Authority)return;
        var hit=scene.Trace.Ray(point+Vector3.Up*3,point-Vector3.Up*220).WithSurfaceMeshes()
            .WithoutTags("paintball_actor","overdrive_enemy","paintball_debris").Run();
        if(!hit.Hit || hit.Normal.z<.75f || scene.NavMesh.GetClosestPoint(hit.HitPosition,32) is not {} floor || MathF.Abs(floor.z-hit.HitPosition.z)>8)return;
        foreach(var old in scene.GetAllComponents<SausageEggSlick>().OrderByDescending(s=>s.age).SkipLast(13).ToArray())old.GameObject.Destroy();
        var go=new GameObject(false,"Slippery egg yolk"){WorldPosition=hit.HitPosition+hit.Normal*.03f};go.Tags.Add("paintball_debris");
        go.Components.Create<SausageEggSlick>().Round=round;go.Enabled=true;Spills++;
        if(MultiplayerSession.Online){go.NetworkMode=NetworkMode.Object;go.Network.SetOwnerTransfer(OwnerTransfer.Fixed);go.NetworkSpawn();}
    }
    protected override void OnStart()
    {
        // Runny white spread, an intact yolk, and the two shell halves.
        Mark("Egg white spill",Vector3.Zero,new Vector2(42,34),new Color(1,.98f,.9f,.72f),Game.Random.Float(0,360));
        var yolkAt=new Vector3(Game.Random.Float(-5,5),Game.Random.Float(-5,5),0);
        Mark("Egg yolk",yolkAt,new Vector2(14,13),YolkBurst.Yellow.WithAlpha(.97f),Game.Random.Float(0,360));
        var shell=Model.Load(OverdriveAssets.EggShellHalfPath);
        if(!shell.IsError)
        {
            float scale=4.4f/MathF.Max(.01f,shell.Bounds.Size.x);
            for(int i=0;i<2;i++)
            {
                var yaw=Game.Random.Float(0,360);var at=Rotation.FromYaw(yaw).Forward*Game.Random.Float(9,16)+Vector3.Up*1.6f;
                var piece=new GameObject(GameObject,true,"Egg shell half"){NetworkMode=NetworkMode.Never,LocalPosition=at,
                    LocalRotation=Rotation.FromYaw(yaw)*Rotation.FromPitch(i==0 ? 95 : 180+Game.Random.Float(-20,20)),LocalScale=new Vector3(scale)};
                var r=piece.Components.Create<ModelRenderer>();r.Model=shell;r.Tint=new Color(.99f,.94f,.86f);
            }
        }
    }
    private void Mark(string name,Vector3 offset,Vector2 size,Color tint,float spin)
    {
        var mark=new GameObject(GameObject,true,name){NetworkMode=NetworkMode.Never,LocalPosition=offset,LocalRotation=Rotation.LookAt(Vector3.Down)};
        var decal=mark.Components.Create<Decal>();decal.Decals=[new(){ColorTexture=PaintImpactSystem.SplashTexture,Width=1,Height=1,ColorMix=1}];
        decal.SortLayer=name=="Egg yolk" ? 102u : 100u;
        // Project only onto the top finish, not the near-coplanar workshop floor beneath it.
        decal.Size=size;decal.Depth=.12f;decal.ColorTint=tint;decal.Rotation=spin;decal.LifeTime=0;decal.Transient=false;decal.AttenuationAngle=.5f;decal.Parallax=0;
    }
    protected override void OnUpdate()
    {
        age+=Time.Delta;
        if(!MultiplayerSession.Authority)return;
        var s=MultiplayerSession.Find(Scene);
        if(s?.IsOverdrive!=true || !s.InRound || s.RoundId!=Round || age>Life){GameObject.Destroy();return;}
        if(uses>=MaxUses || age<.3f || age<nextSlip || s.OverdriveReviewPaused)return;
        foreach(var enemy in s.OverdriveEnemies)
        {
            if(MathF.Abs(enemy.WorldPosition.z-WorldPosition.z)>15 || (enemy.WorldPosition-WorldPosition).WithZ(0).Length>34 || enemy.Agent?.Velocity.Length<40)continue;
            // One roll per pass, not every frame until it succeeds; a sausage
            // walking back through a few seconds later gets a fresh roll.
            if(attempted.TryGetValue(enemy.GameObject.Id,out var when) && age-when<4)continue;
            attempted[enemy.GameObject.Id]=age;
            if(Game.Random.Float(0,1)<.7f && enemy.TrySlip()){uses++;nextSlip=age+3;break;}
        }
    }
}