Karma.cs

Game gameplay state helper that tracks "Karma" (Grandma's respect meter). Stores a float value with max, exposes read-only formatted texts and label, provides Reset/Set/Add, periodic ticks that modify karma based on tank/battle/progression state, and save/load serialization helpers.

namespace NoChillquarium;

/// <summary>
/// Grandma's respect meter. Opposite pressure to <see cref="Chaos"/>.
/// She loves abominations, championships, and quality-control booms.
/// She cools off if you chicken out of fights, and blames you when fish die.
/// </summary>
public static class Karma
{
	public const float Max = 100f;

	static float _value = 40f; // slightly neutral-good start
	static int _version;

	public static float Value => _value;
	public static int Version => _version;
	public static string ValueText => $"{_value:0}";

	/// <summary>Grandma's current vibe — player-facing label.</summary>
	public static string Label =>
		_value >= 70f ? "PROUD" :
		_value >= 50f ? "FOND" :
		_value >= 30f ? "COOLING" :
		"DISAPPOINTED";

	/// <summary>HUD / credits kicker (was "Karma").</summary>
	public static string MeterName => "Grandma";

	public static void Reset()
	{
		_value = 40f;
		_version++;
	}

	public static void Set( float value )
	{
		_value = Math.Clamp( value, 0f, Max );
		_version++;
	}

	public static void Add( float amount )
	{
		if ( MathF.Abs( amount ) < 0.0001f )
			return;
		_value = Math.Clamp( _value + amount, 0f, Max );
		_version++;
	}

	/// <summary>
	/// Slow fondness while the tank is thriving — she likes a full, ugly empire.
	/// </summary>
	public static void TickCalmCare( float dt )
	{
		if ( !TankSim.IsActive || dt <= 0f )
			return;
		if ( !EndingSystem.ThrivingTank )
			return;
		if ( Chaos.Value > 40f )
			return;

		// Prefer abom tanks: monsters get a little more love.
		var rate = TankSim.HasAnyMonster ? 0.035f : 0.02f;
		Add( dt * rate );
	}

	/// <summary>
	/// Chicken out of the fight ladder too long and Grandma stops respecting you.
	/// Only after Fight is actually on the menu (trained + can pay).
	/// </summary>
	public static void TickWuss( float dt )
	{
		if ( !TankSim.IsActive || dt <= 0f )
			return;
		// Don't punish people still learning gym / still broke.
		if ( !Progression.FightUnlocked )
			return;
		// Already fighting = not a wuss.
		if ( BattleSystem.Wins + BattleSystem.Losses >= 2 )
			return;
		if ( BattleSystem.ChampionshipTitles > 0 )
			return;

		// ~-1 respect per ~60s of hiding once Fight is available.
		Add( -dt * 0.017f );
	}

	public static void WriteToSave( SaveData data )
	{
		if ( data is not null )
			data.Karma = _value;
	}

	public static void LoadFromSave( SaveData data )
	{
		if ( data is null )
		{
			Reset();
			return;
		}

		// Old saves without karma field deserialize as 0 — treat as fresh mid.
		if ( data.Karma <= 0.01f && data.PlaytimeSeconds < 5f )
			_value = 40f;
		else if ( data.Version < 5 && data.Karma <= 0.01f )
			_value = 40f;
		else
			_value = Math.Clamp( data.Karma, 0f, Max );
		_version++;
	}
}