FusionFeel.cs

Utility static class that drives a short fusion visual/screen-effects sequence: multi-color strobe flash, ring progress, and tank-local shake and spark origin. It exposes intensity, flash amount, phase, shake offsets, origin coordinates, and a version counter; provides Trigger, Tick, and Clear to control the effect.

File Access
namespace NoChillquarium;

/// <summary>
/// Fusion VFX: hard multi-color flash + tank shake + spark at the merge point.
/// Respects Reduce Strobe (softer, fewer flashes).
/// </summary>
public static class FusionFeel
{
	static float _time;
	static float _duration;
	static float _flashLeft;
	static float _shakeLeft;
	static float _shakeAmp;
	static float _cx;
	static float _cy;
	static int _flashCount;
	static int _version;

	public static int Version => _version;
	public static bool IsActive => _time < _duration && _duration > 0f;

	/// <summary>0–1 overall intensity while playing.</summary>
	public static float Intensity
	{
		get
		{
			if ( !IsActive )
				return 0f;
			var t = _time / _duration;
			// Peak mid-fusion, fade out.
			if ( t < 0.35f )
				return t / 0.35f;
			return Math.Clamp( 1f - (t - 0.35f) / 0.65f, 0f, 1f );
		}
	}

	/// <summary>Strobe flash amount 0–1 (drives full-screen overlay).</summary>
	public static float Flash { get; private set; }

	/// <summary>Which flash color phase (0 white, 1 magenta, 2 toxic green).</summary>
	public static int FlashPhase { get; private set; }

	public static float ShakeX { get; private set; }
	public static float ShakeY { get; private set; }

	/// <summary>Tank-local fusion epicenter.</summary>
	public static float OriginX => _cx;
	public static float OriginY => _cy;

	/// <summary>Ring expand 0–1 for CSS scale.</summary>
	public static float RingNorm
	{
		get
		{
			if ( !IsActive )
				return 0f;
			return Math.Clamp( _time / MathF.Max( 0.05f, _duration ), 0f, 1f );
		}
	}

	public static void Clear()
	{
		_time = 0f;
		_duration = 0f;
		_flashLeft = 0f;
		_shakeLeft = 0f;
		_shakeAmp = 0f;
		Flash = 0f;
		FlashPhase = 0;
		ShakeX = 0f;
		ShakeY = 0f;
		_flashCount = 0;
		_version++;
	}

	/// <summary>Kick off abomination birth flash at tank-local point.</summary>
	public static void Trigger( float tankX, float tankY )
	{
		_cx = tankX;
		_cy = tankY;
		var soft = GameSettings.ReduceStrobe;
		_duration = soft ? 0.55f : 1.15f;
		_time = 0f;
		_shakeAmp = (soft ? 6f : 14f) * GameSettings.ShakeScale;
		_shakeLeft = _duration;
		_flashLeft = soft ? 0.08f : 0.14f;
		_flashCount = 0;
		FlashPhase = 0;
		Flash = soft ? 0.45f : 0.85f;
		_version++;
		GameAudio.PlayUi( GameAudio.Upgrade );
	}

	public static void Tick( float dt )
	{
		if ( _duration <= 0f )
			return;
		if ( dt <= 0f )
			return;
		if ( dt > 0.05f )
			dt = 0.05f;

		_time += dt;
		if ( _time >= _duration )
		{
			Clear();
			return;
		}

		// Strobe: fire short flash pulses through the fusion.
		var soft = GameSettings.ReduceStrobe;
		var pulseHz = soft ? 4f : 9f;
		var expected = (int)(_time * pulseHz);
		if ( expected > _flashCount )
		{
			_flashCount = expected;
			FlashPhase = (_flashCount % 3);
			_flashLeft = soft ? 0.06f : 0.09f;
			Flash = (soft ? 0.4f : 0.75f) * GameSettings.FlashScale;
			_version++;
		}

		if ( _flashLeft > 0f )
		{
			_flashLeft -= dt;
			// Decay this pulse
			var peak = soft ? 0.5f : 0.9f;
			Flash = Math.Clamp( (_flashLeft / 0.1f) * peak * GameSettings.FlashScale, 0f, 1f );
		}
		else
		{
			Flash = 0f;
		}

		if ( _shakeLeft > 0f )
		{
			_shakeLeft -= dt;
			var n = Math.Clamp( _shakeLeft / _duration, 0f, 1f );
			var amp = _shakeAmp * n;
			ShakeX = MathF.Sin( _time * 48f ) * amp;
			ShakeY = MathF.Cos( _time * 41f ) * amp * 0.7f;
		}
		else
		{
			ShakeX = 0f;
			ShakeY = 0f;
		}

		_version++;
	}

	/// <summary>CSS class for current flash color.</summary>
	public static string FlashClass => FlashPhase switch
	{
		1 => "fusion-mag",
		2 => "fusion-toxic",
		_ => "fusion-white"
	};
}