DiamondDebris.Smoke.cs
using System;
using System.Collections.Generic;
using Sandbox;

namespace Diamonds;

public sealed partial class DiamondDebris
{
	/// <summary>Presentation-only dust: deliberately has no physics body or collision shape.</summary>
	public sealed class SmokePuff
	{
		public Vector2 Position { get; internal set; }
		public float Age { get; internal set; }
		public float Lifetime { get; internal set; }
		public float Radius { get; internal set; }
		internal Vector2 Velocity;
	}

	const int MaxSmoke = 192;
	readonly List<SmokePuff> smoke = new();
	public IReadOnlyList<SmokePuff> Smoke => smoke;

	void EmitSmoke( Particle chip, bool crushed )
	{
		int count = crushed ? random.Next( 2, 5 ) : random.Next( 1, 3 );
		var velocity = chip.Body.Velocity;
		var position = chip.Position;
		var inherited = new Vector2( velocity.x, velocity.y );
		inherited *= 0.1f * MathF.Min( 1, 60 / MathF.Max( 1, inherited.Length ) );
		for ( int i = 0; i < count; i++ )
		{
			if ( smoke.Count >= MaxSmoke ) smoke.RemoveAt( 0 );
			float angle = Range( -MathF.PI, MathF.PI );
			var direction = new Vector2( MathF.Cos( angle ), MathF.Sin( angle ) );
			smoke.Add( new SmokePuff
			{
				Position = position + direction * Range( 0, chip.Radius * 0.4f ),
				Velocity = direction * Range( 12, 30 ) + new Vector2( 0, -Range( 14, 25 ) ) + inherited,
				Radius = Range( 2, 4 ), Lifetime = Range( 0.45f, 0.75f )
			} );
		}
	}

	void UpdateSmoke( float delta )
	{
		for ( int i = smoke.Count - 1; i >= 0; i-- )
		{
			var puff = smoke[i];
			puff.Age += delta;
			if ( puff.Age >= puff.Lifetime ) { smoke.RemoveAt( i ); continue; }
			puff.Velocity = (puff.Velocity + new Vector2( 0, -8 * delta )) * MathF.Exp( -1.8f * delta );
			puff.Position += puff.Velocity * delta;
		}
	}
}