OverdriveHeadBurst.cs
using Sandbox;
using System;
namespace PaintballBaddies;
// Pieces cut from Terry's actual head, with preserved skin UVs and filling.
// Native particles collide visually without becoming networked gameplay bodies.
public sealed class OverdriveHeadBurst : Component
{
public float Age {get;private set;}
public int Shards=>Components.Get<ParticleEffect>()?.ParticleCount ?? 0;
private Vector3 impulse;
private int seed;
private int variant;
public static void Spawn(Scene scene,Vector3 point,Vector3 direction,int variation,int headVariant=0)
{
foreach(var old in scene.GetAllComponents<OverdriveHeadBurst>().OrderByDescending(x=>x.Age).SkipLast(3).ToArray())old.GameObject.Destroy();
var go=new GameObject(false,"Terry sausage burst"){WorldPosition=point,NetworkMode=NetworkMode.Never};
go.Tags.Add("paintball_debris");
var burst=go.Components.Create<OverdriveHeadBurst>();burst.impulse=direction;burst.seed=variation;burst.variant=headVariant.Clamp(0,2);go.Enabled=true;
OverdriveAudio.Play("burst",point);
}
protected override void OnStart()
{
var particles=Components.Create<ParticleEffect>();
particles.MaxParticles=14;particles.Lifetime=1.35f;particles.LocalSpace=0;
particles.ApplyRotation=false;particles.ApplyShape=false;particles.ApplyAlpha=false;particles.ApplyColor=false;
particles.Force=true;particles.ForceDirection=Vector3.Down*650;particles.ForceScale=1;
particles.ForceSpace=ParticleEffect.SimulationSpace.World;particles.Damping=.35f;
particles.Collision=true;particles.CollisionRadius=.7f;particles.Bounce=.28f;particles.Friction=.7f;particles.PushStrength=0;
particles.CollisionIgnore=new TagSet();particles.CollisionIgnore.Add("paintball_actor");particles.CollisionIgnore.Add("paintball_debris");
var render=Components.Create<ParticleModelRenderer>();
render.Choices=Enumerable.Range(0,3).Select(i=>new ParticleModelRenderer.ModelEntry{Model=Model.Load($"models/sausage/grumpy/head_piece_{variant}_{i}.vmdl")}).ToList();
render.Scale=1;render.CastShadows=false;render.RotateWithGameObject=false;
particles.OnStep=(p,dt)=>{p.Angles+=new Angles(170,260,130)*dt;p.Alpha=((1.35f-p.Age)/.35f).Clamp(0,1);};
var random=new Random(seed);
for(int i=0;i<14;i++)
{
var direction=new Vector3(MathF.Cos(i*2.399f+seed),MathF.Sin(i*2.399f+seed),-.3f+(float)random.NextDouble()*1.3f).Normal;
var p=particles.Emit(WorldPosition+direction*2,0);float size=.055f+(float)random.NextDouble()*.13f;p.Size=new Vector3(size);
p.Angles=new Angles(i*27,i*47,i*19);p.StartAngles=p.Angles;p.Color=Color.White;
// Most material exits along the shot, smaller crumbs spread wider.
p.Velocity=direction*(65+(float)random.NextDouble()*130)+impulse.Normal*170+Vector3.Up*50;
}
for(int i=0;i<3;i++)SausageHeavyChunk.Spawn(WorldPosition,impulse.Normal,seed+i,i,variant);
}
protected override void OnUpdate(){Age+=Time.Delta;if(Age>2.4f)GameObject.Destroy();}
}
public sealed class SausageHeavyChunk : Component,Component.ICollisionListener
{
private float age;
private bool landed;
public static int Landings {get;private set;}
public static void Spawn(Vector3 point,Vector3 impulse,int seed,int size,int variant=0)
{
var scene=Game.ActiveScene;
foreach(var old in scene.GetAllComponents<SausageHeavyChunk>().OrderByDescending(c=>c.age).SkipLast(17).ToArray())old.GameObject.Destroy();
var go=new GameObject(false,"Sausage chunk"){NetworkMode=NetworkMode.Never,WorldPosition=point};go.Tags.Add("paintball_debris");
var mesh=new GameObject(go,true,"Textured Terry fragment"){NetworkMode=NetworkMode.Never,LocalScale=new Vector3(size==0 ? .65f : size==1 ? .85f : .55f)};
mesh.Components.Create<ModelRenderer>().Model=Model.Load($"models/sausage/grumpy/head_piece_{variant}_{size}.vmdl");
var collider=go.Components.Create<SphereCollider>();collider.Radius=1.2f+size*.65f;collider.Static=false;
var body=go.Components.Create<Rigidbody>();body.MassOverride=.5f+size*.8f;body.LinearDamping=.2f;
go.Components.Create<SausageHeavyChunk>();go.Enabled=true;
body.Velocity=Rotation.FromYaw(seed*137).Forward*(75-size*12)+impulse*175+Vector3.Up*(100+size*20);
body.AngularVelocity=new Vector3(4+size,8-size,seed%6+2);
}
void Component.ICollisionListener.OnCollisionStart(Collision collision)
{
if(landed || age<.12f)return;landed=true;Landings++;
var sound=SausageAudio.Play("landing",WorldPosition);if(sound is not null){sound.Volume=.42f;sound.Pitch=.82f;}
}
protected override void OnUpdate(){age+=Time.Delta;if(age>3.8f)GameObject.Destroy();}
}