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

/// <summary>Reuses the existing splatter mask and native shard particles for eggs.</summary>
public sealed class YolkBurst : Component
{
    public static readonly Color Yellow=new(1,.61f,.035f);
    public static int Bursts {get;private set;}
    private float age;
    public static void Spawn(Vector3 point)
    {
        var go=new GameObject(true,"Broken egg and yolk"){NetworkMode=NetworkMode.Never,WorldPosition=point};
        go.Tags.Add("paintball_debris");go.Components.Create<YolkBurst>();Bursts++;
    }
    protected override void OnStart()
    {
        foreach(var old in Scene.GetAllComponents<YolkBurst>().OrderByDescending(x=>x.age).SkipLast(11).ToArray())old.GameObject.Destroy();
        var p=Components.Create<ParticleEffect>();p.MaxParticles=14;p.Lifetime=1.25f;p.LocalSpace=0;
        p.ApplyShape=p.ApplyRotation=p.ApplyColor=p.ApplyAlpha=false;p.Force=true;p.ForceDirection=Vector3.Down*450;p.ForceScale=1;p.ForceSpace=ParticleEffect.SimulationSpace.World;
        p.Collision=true;p.CollisionRadius=.4f;p.Bounce=.12f;p.PushStrength=0;
        var render=Components.Create<ParticleModelRenderer>();render.Choices=[new(){Model=Model.Load("models/gear/clear_shield/fracture/piece_03.vmdl")}];
        render.MaterialOverride=Material.Load("materials/dev/primary_white.vmat");render.CastShadows=false;render.RotateWithGameObject=false;
        p.OnStep=(part,dt)=>{part.Angles+=new Angles(210,350,130)*dt;part.Alpha=((1.25f-part.Age)/.25f).Clamp(0,1);};
        for(int i=0;i<14;i++){var part=p.Emit(WorldPosition,0);part.Size=new Vector3(i<8 ? .045f : .065f);part.Color=i<8 ? new Color(.96f,.91f,.8f) : Yellow;part.Velocity=Rotation.FromYaw(i*137).Forward*(40+i*3)+Vector3.Up*(45+i*4);}
        // Near surfaces receive yolk; an air interception also drops a splash
        // onto the floor below it. No full-screen obstruction or paintball FX.
        foreach(var direction in new[]{Vector3.Down,Vector3.Up,Vector3.Forward,Vector3.Backward,Vector3.Left,Vector3.Right})
        {
            var hit=Scene.Trace.Ray(WorldPosition,WorldPosition+direction*(direction==Vector3.Down ? 180 : 24)).WithSurfaceMeshes().WithoutTags("paintball_debris").Run();
            if(!hit.Hit)continue;
            var mark=new GameObject(GameObject,true,"Yolk splatter"){NetworkMode=NetworkMode.Never,WorldPosition=hit.HitPosition+hit.Normal*(hit.Normal.z>.75f ? .03f : .3f),WorldRotation=Rotation.LookAt(-hit.Normal)};
            var enemy=OverdriveEnemy.Find(hit.GameObject);var rig=enemy?.Components.Get<OverdriveRobotRig>();
            if(rig?.Body.IsValid()==true)
            {
                string bone=enemy.Zone(hit.HitPosition) switch {1=>"head",8=>"leg_lower_L",16=>"leg_lower_R",_=>"spine_1"};
                var parent=rig.Body.GetBoneObject(bone);if(parent.IsValid())mark.Parent=parent;
                mark.Components.Create<YolkLifetime>();
            }
            var decal=mark.Components.Create<Decal>();decal.Decals=[new(){ColorTexture=PaintImpactSystem.SplashTexture,Width=1,Height=1,ColorMix=1}];
            decal.SortLayer=101;decal.Parallax=0;
            decal.Size=new Vector2(11,11);decal.Depth=hit.Normal.z>.75f ? .12f : 6;decal.ColorTint=Yellow;decal.Rotation=Game.Random.Float(0,360);decal.LifeTime=0;decal.Transient=false;decal.AttenuationAngle=.35f;
        }
    }
    protected override void OnUpdate(){age+=Time.Delta;if(age>18)GameObject.Destroy();}
}
public sealed class YolkLifetime : Component
{
    private float age;
    protected override void OnUpdate(){age+=Time.Delta;if(age>18)GameObject.Destroy();}
}