DiamondDebris.Shatter.cs
using System;
using System.Collections.Generic;
using Sandbox;
using static Diamonds.DiamondBoard;

namespace Diamonds;

public sealed partial class DiamondDebris
{
	public sealed class ShatterSunburst
	{
		public Vector2 Position { get; internal set; }
		public float Age { get; internal set; }
		public float Lifetime { get; internal set; }
		public float Scale { get; internal set; }
		public float Rotation { get; internal set; }
		public float Brightness { get; internal set; }
		public int ColorIndex { get; internal set; }
		internal Vector2 Velocity;
	}

	readonly List<ShatterSunburst> shatterSunbursts = new();
	public IReadOnlyList<ShatterSunburst> ShatterSunbursts => shatterSunbursts;
	readonly List<ShatterSunburst> crushSunbursts = new();
	public IReadOnlyList<ShatterSunburst> CrushSunbursts => crushSunbursts;
	public const float CrushBurstDuration = 0.25f;
	public const float CrushBurstScale = 0.6f;

	void EmitCrushSunburst( Particle shard )
	{
		if ( crushSunbursts.Count >= MaxShards ) crushSunbursts.RemoveAt( 0 );
		crushSunbursts.Add( new ShatterSunburst
		{
			Position = shard.Position, ColorIndex = shard.ColorIndex,
			Lifetime = CrushBurstDuration, Scale = CrushBurstScale,
			Rotation = Range( -MathF.PI, MathF.PI ), Brightness = 0.4f
		} );
	}

	void UpdateShatterSunbursts( float delta )
	{
		UpdateSunbursts( shatterSunbursts, delta );
		UpdateSunbursts( crushSunbursts, delta );
	}

	static void UpdateSunbursts( List<ShatterSunburst> bursts, float delta )
	{
		for ( int i = bursts.Count - 1; i >= 0; i-- )
		{
			var burst = bursts[i];
			burst.Age += delta;
			if ( burst.Age >= burst.Lifetime ) { bursts.RemoveAt( i ); continue; }
			burst.Position += burst.Velocity * delta;
		}
	}

	void EmitShatterEffects( Diamond cell, bool gameOver )
	{
		var center = new Vector2( LaneX( cell.Lane ), cell.Y );
		for ( int i = 0; i < 3; i++ )
		{
			if ( shatterSunbursts.Count >= 192 ) shatterSunbursts.RemoveAt( 0 );
			float angle = Range( -MathF.PI, MathF.PI );
			var outward = new Vector2( MathF.Cos( angle ), MathF.Sin( angle ) );
			shatterSunbursts.Add( new ShatterSunburst
			{
				Position = center + outward * (i == 0 ? 0 : Range( 8, 18 )),
				Velocity = outward * (i == 0 ? 0 : Range( 35, 75 )),
				Lifetime = Range( 0.35f, 0.5f ), Scale = i == 0 ? 1.3f : Range( 0.5f, 0.8f ),
				Rotation = Range( -MathF.PI, MathF.PI ), Brightness = Range( 0.4f, 0.6f ), ColorIndex = cell.ColorIndex
			} );
		}
		int count = gameOver ? random.Next( 9, 15 ) : random.Next( 28, 43 );
		for ( int i = 0; i < count; i++ )
		{
			float angle = Range( -MathF.PI, MathF.PI );
			var outward = new Vector2( MathF.Cos( angle ), MathF.Sin( angle ) );
			float radius = Range( 0.8f, 1.9f );
			var position = center + outward * Range( 2, 18 );
			if ( !HasRoom( position, radius + 0.5f ) ) continue;
			// The vanished gem leaves an open pocket. Launch from inside it so
			// nearby surviving gems naturally block and deflect the explosion.
			AddSpark( position, outward * Range( 450, 950 ), radius, cell.ColorIndex );
		}
	}
}