CryoBurst.cs
using Sandbox;
using System;
using System.Threading.Tasks;
namespace PaintballBaddies;
// Reuses native particles, the existing cut sausage meshes and library frost.
// Cosmetic pieces never apply damage or become networked physics objects.
public sealed class CryoBurst : Component
{
public static Material Frost {get;private set;}
private static Task loading;
public static Task Load()=>loading ??= LoadFrost();
private static async Task LoadFrost()
{
try{Frost=await Cloud.Load<Material>("tiled.frozen_ice");}
catch(Exception e){Log.Warning($"Frost material unavailable: {e.Message}");}
if(Frost is null || Frost.IsError)Frost=Material.Load("materials/glass/glass_a.vmat");
Frost=Frost.CreateCopy();Frost.Set("g_vTexCoordScale",new Vector4(4,4,0,0));Frost.Set("g_vColorTint",new Vector4(1.1f,1.25f,1.4f,1));
}
private float age;
private bool shatter,small;
public static int Shatters {get;private set;}
public static void Spawn(Scene scene,Vector3 at,bool shatter,bool small=false)
{
if(!scene.IsValid() || scene!=Game.ActiveScene)return;
_=Load();
foreach(var old in scene.GetAllComponents<CryoBurst>().OrderByDescending(x=>x.age).SkipLast(7).ToArray())old.GameObject.Destroy();
var go=new GameObject(false,shatter ? "Ice shatter" : "Flash freeze mist"){NetworkMode=NetworkMode.Never,WorldPosition=at};
go.Tags.Add("paintball_debris");var c=go.Components.Create<CryoBurst>();c.shatter=shatter;c.small=small;go.Enabled=true;
if(shatter)
{
Shatters++;Sound.Play("sounds/paintball/shield_break.sound",at);
if(scene.Camera.IsValid())scene.Camera.AddShake((1-(scene.Camera.WorldPosition-at).Length/650).Clamp(0,1)*.55f,24,.12f);
}
else if(!small)FeelAudio.Play("freeze_blast",1,at,.8f,950);
}
protected override void OnStart()
{
var mist=Components.Create<ParticleEffect>();mist.MaxParticles=small ? 9 : 22;mist.Lifetime=.65f;mist.LocalSpace=0;
mist.ApplyColor=false;mist.ApplyAlpha=false;mist.ApplyShape=false;mist.ApplyRotation=false;
var sprite=Components.Create<ParticleSpriteRenderer>();sprite.Sprite=new Sprite{Animations=[new Sprite.Animation{Name="Default",Frames=[new Sprite.Frame{Texture=RagePuff.Cloud()}]}]};
sprite.Lighting=true;sprite.Shadows=false;sprite.Additive=false;sprite.Scale=1;
mist.OnStep=(p,dt)=>{p.Alpha=(1-p.Age/.65f).Clamp(0,1)*.2f;p.Size+=new Vector3(dt*25);};
for(int i=0;i<mist.MaxParticles;i++)
{
var dir=Rotation.From(new Angles(Game.Random.Float(-70,70),i*137.5f,0)).Forward;
var p=mist.Emit(WorldPosition+dir*(small ? 9 : 18),0);p.Color=new Color(.6f,.86f,1);p.Size=new Vector3(small ? 11 : 20);p.Alpha=.2f;
p.Velocity=dir*(small ? 35 : 145)+Vector3.Up*15;
}
if(!shatter)return;
// Scatter pieces through the whole frozen silhouette, not just its head.
for(int i=0;i<20;i++)CryoShard.Spawn(GameObject,WorldPosition+new Vector3(Game.Random.Float(-10,10),Game.Random.Float(-10,10),Game.Random.Float(-30,31)),i);
}
protected override void OnUpdate(){age+=Time.Delta;if(age>2.8f)GameObject.Destroy();}
}
public sealed class CryoShard : Component
{
private Vector3 velocity;
private Angles spin;
private float age;
private ModelRenderer render;
public static void Spawn(GameObject parent,Vector3 point,int index)
{
var go=new GameObject(parent,true,"Frosted fractured chunk"){NetworkMode=NetworkMode.Never,WorldPosition=point};
go.Tags.Add("paintball_debris");
var c=go.Components.Create<CryoShard>();c.render=go.Components.Create<ModelRenderer>();
c.render.Model=Model.Load($"models/sausage/grumpy/head_piece_0_{index%3}.vmdl");c.render.MaterialOverride=CryoBurst.Frost;
c.render.Tint=new Color(.5f,.82f,1);go.WorldScale=new Vector3(Game.Random.Float(.3f,.8f));
c.velocity=Rotation.FromYaw(index*137.5f).Forward*Game.Random.Float(90,260)+Vector3.Up*Game.Random.Float(70,220);
c.spin=new Angles(Game.Random.Float(-220,220),Game.Random.Float(-220,220),Game.Random.Float(-220,220));
}
protected override void OnUpdate()
{
age+=Time.Delta;velocity+=Vector3.Down*430*Time.Delta;
var end=WorldPosition+velocity*Time.Delta;
var hit=Scene.Trace.Sphere(.7f,WorldPosition,end).WithoutTags("paintball_actor","paintball_debris","overdrive_enemy").Run();
if(hit.Hit){WorldPosition=hit.EndPosition+hit.Normal;velocity=(velocity-2*Vector3.Dot(velocity,hit.Normal)*hit.Normal)*.28f;}
else WorldPosition=end;
WorldRotation*=Rotation.From(spin*Time.Delta);
render.Tint=new Color(.5f,.82f,1,((2.7f-age)/.5f).Clamp(0,1));
}
}