Player/Tools/PulseTool.cs
namespace PlanetMeat;

[Title( "Pulse Ring" ), Icon( "offline_bolt" )]
public sealed class PulseTool : PlayerTool, IUpgradeableAttack, IUpradeableSize
{
	private static GameObject BurstPrefab => GameObject.GetPrefab( "effects/pulse_burst.prefab" );

	public const float MINIMUM_RADIUS = 75.0f;
	public const float PULSE_TOOL_COOLDOWN = 20.0f;
	public override float BaseCooldown => PULSE_TOOL_COOLDOWN;

	public ValueAggregator<int> AggregateDamage => field ??= ValueAggregator<int>.FromImprovement( "pulse_dmg", 10 );
	public ValueAggregator<int> AggregateSize => field ??= ValueAggregator<int>.FromImprovement( "pulse_size", 50 );

	[Property] private float RadiusSpeed { get; set; } = 500.0f;
	[Property] private Color RingColor { get; set; } = Color.Blue;

	public float Radius => MINIMUM_RADIUS + chargeTime * RadiusSpeed;
	public int Thickness => this.GetStrikeSize();
	private float chargeTime = 0.0f;
	public bool Charging { get; private set; } = false;

	private (float, float) Radii
	{
		get
		{
			var radius = Radius;
			var halfThick = 0.5f * Thickness;
			return (radius - halfThick, radius + halfThick);
		}
	}

	private AoeRing InnerRing
	{
		get;
		set
		{
			if ( field?.GameObject is GameObject ringGo && ringGo.IsValid )
				ringGo.Destroy();
			field = value;
		}
	}
	private AoeRing OuterRing
	{
		get;
		set
		{
			if ( field?.GameObject is GameObject ringGo && ringGo.IsValid )
				ringGo.Destroy();
			field = value;
		}
	}

	private void DestroyRings()
	{
		InnerRing = null;
		OuterRing = null;
	}

	public override void OnPlaceStarted( Vector3 position )
	{
		base.OnPlaceStarted( position );
		if ( IsUsable )
		{
			StartCooldown();
			chargeTime = 0.0f;
			Charging = true;

			DestroyRings();

			var (inner, outer) = Radii;
			InnerRing = AoeRing.Create( WorldPosition, inner );
			OuterRing = AoeRing.Create( WorldPosition, outer );
			InnerRing.RingColor = OuterRing.RingColor = RingColor;
		}
	}

	public override void OnPlaceUpdated( Vector3 fromPosition, Vector3 toPosition )
	{
		base.OnPlaceUpdated( fromPosition, toPosition );
		if ( Charging )
		{
			chargeTime += Time.Delta;
			(InnerRing.Radius, OuterRing.Radius) = Radii;
		}
	}

	private void FireChargedPulse()
	{
		Charging = false;

		var (inner, outer) = Radii;
		var innerSq = inner * inner;
		var outerSq = outer * outer;
		var myPos = WorldPosition;

		var damage = new DamageInfo( this.GetAttackDamage(), GameObject, GameObject );
		damage.SetExplosive( true );
		foreach ( var t in Scene.GetAllComponents<Targetable>() )
		{
			var distSq = myPos.DistanceSquared( t.WorldPosition );
			if ( distSq >= innerSq && distSq <= outerSq )
				t.OnDamage( in damage );
		}

		DestroyRings();

		Components.Get<PreparedEffects>()?.PerformEffects();
		if ( BurstPrefab?.Clone( WorldPosition ).Components.Get<ParticleRingEmitter>() is ParticleRingEmitter ringEmitter )
		{
			ringEmitter.Radius = Radius;
			ringEmitter.Thickness = Thickness;
		}

		chargeTime = 0.0f;
	}

	public override void OnPlaceEnded( Vector3 position )
	{
		base.OnPlaceEnded( position );
		if ( Charging )
			FireChargedPulse();
	}

	protected override void OnUseEnds()
	{
		base.OnUseEnds();
		if ( Charging )
			FireChargedPulse();
	}
}