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.
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"
};
}