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;}
}
}
}