Utility static class that computes visual/audio 'death' effects for fish in a tank. It tracks peak sickness progress among visible fish, produces shake/flash values, stage labels, and triggers UI banners and audio callbacks when stages change.
namespace NoChillquarium;
/// <summary>
/// Ramps tension while any visible fish is dying of salinity:
/// shake, flash, and stage labels so you feel the clock.
/// </summary>
public static class DeathFeel
{
static float _time;
static float _shakeX;
static float _shakeY;
static float _flash;
static int _lastStageAnnounced = -1;
static int _version;
public static int Version => _version;
/// <summary>0–1 worst sickness among fish in the active tank (meth-safe fish ignored).</summary>
public static float PeakProgress { get; private set; }
public static int PeakStage => ProgressToStage( PeakProgress );
public static float ShakeX => _shakeX;
public static float ShakeY => _shakeY;
public static float Flash => _flash;
public static string StageLabel => PeakStage switch
{
0 => "",
1 => "SOMETHING'S OFF…",
2 => "STRUGGLING",
3 => "CRITICAL",
_ => "LAST GASP"
};
public static void Clear()
{
PeakProgress = 0f;
_shakeX = 0f;
_shakeY = 0f;
_flash = 0f;
_lastStageAnnounced = -1;
_time = 0f;
_version++;
}
public static void Tick( float dt )
{
if ( dt <= 0f )
return;
if ( dt > 0.05f )
dt = 0.05f;
_time += dt;
// Peak dying fish in the tank you're watching.
var peak = 0f;
if ( TankSim.IsActive )
{
foreach ( var fish in TankSim.Fish )
{
if ( !fish.IsSick )
continue;
peak = MathF.Max( peak, fish.SickProgress );
}
}
var prevStage = ProgressToStage( PeakProgress );
PeakProgress = peak;
var stage = ProgressToStage( peak );
if ( peak <= 0.001f )
{
_shakeX *= MathF.Max( 0f, 1f - dt * 8f );
_shakeY *= MathF.Max( 0f, 1f - dt * 8f );
_flash *= MathF.Max( 0f, 1f - dt * 6f );
if ( _lastStageAnnounced != 0 || prevStage != 0 )
{
_lastStageAnnounced = 0;
_version++;
// Hand music back to chill / chaos once the crisis ends.
GameAudio.OnDeathStageChanged( 0 );
}
return;
}
// Stage banners + music gear shifts once when crossing thresholds.
if ( stage > _lastStageAnnounced )
{
_lastStageAnnounced = stage;
var msg = stage switch
{
1 => "Wrong water — Grandma already set a plate",
2 => "Gasping… she loves you / she's hungry",
3 => "Critical — meth… or Grandma's skillet",
_ => "Last gasp — still hugs you after supper"
};
TankSim.ShowBanner( msg );
if ( stage >= 3 )
GameAudio.PlayUi( GameAudio.Error );
GameAudio.OnDeathStageChanged( stage );
_version++;
}
else if ( stage != prevStage )
{
// Stage dropped (meth save, etc.) — step music back down.
_lastStageAnnounced = stage;
GameAudio.OnDeathStageChanged( stage );
_version++;
}
// Shake intensity ramps hard near death (softened by Reduce Strobe).
var shakeAmp = peak * peak * 5.5f * GameSettings.ShakeScale;
var shakeHz = GameSettings.ReduceStrobe
? 1.2f + peak * 3f
: 3f + peak * 14f;
_shakeX = MathF.Sin( _time * shakeHz * MathF.PI * 2f ) * shakeAmp;
_shakeY = MathF.Cos( _time * shakeHz * 1.7f * MathF.PI * 2f ) * shakeAmp * 0.65f;
// Flash: slow pulse early, frantic red blink late — or steady dim with Reduce Strobe.
if ( GameSettings.ReduceStrobe )
{
_flash = Math.Clamp( peak * peak * 0.18f, 0f, 0.2f );
}
else
{
var flashHz = 0.6f + peak * peak * 8f;
var pulse = 0.5f + 0.5f * MathF.Sin( _time * flashHz * MathF.PI * 2f );
_flash = peak * peak * (0.15f + 0.85f * pulse);
}
_version++;
}
public static int ProgressToStage( float progress )
{
if ( progress < 0.08f ) return 0;
if ( progress < 0.30f ) return 1;
if ( progress < 0.55f ) return 2;
if ( progress < 0.78f ) return 3;
return 4;
}
}