Data model for game save state and related save entities. Defines SaveData with many player and world progress fields, plus simple DecorSaveData and FishSaveData DTOs used for JSON serialization of saves.
namespace NoChillquarium;
/// <summary>
/// Serializable snapshot for FileSystem.Data JSON save.
/// </summary>
public sealed class SaveData
{
public const int CurrentVersion = 14;
public const string FileName = "nochill_save.json";
public int Version { get; set; } = CurrentVersion;
public double Dogecoin { get; set; }
public double LifetimeEarned { get; set; }
public float PlaytimeSeconds { get; set; }
public float Chaos { get; set; }
public float Karma { get; set; } = 40f;
/// <summary>Successful combine / evolve births (v9+).</summary>
public int AbominationsCreated { get; set; }
/// <summary>Fish that died of salinity / neglect (v9+).</summary>
public int FishDeaths { get; set; }
public bool HasWmd { get; set; }
public bool HasWhale { get; set; }
public string EndingReached { get; set; } = "";
public int MethBags { get; set; }
/// <summary>Bought M80 sticks in inventory (0 until purchased from Shady).</summary>
public int M80Bags { get; set; }
/// <summary>Visible glass cracks from failed meth-rush calms (0–5).</summary>
public int TankCracks { get; set; }
public List<string> UnlockedFoods { get; set; } = new();
/// <summary>Empty until the player buys their first food type.</summary>
public string SelectedFoodId { get; set; } = "";
public int Syringes { get; set; }
public int BattleWins { get; set; }
public int BattleLosses { get; set; }
public List<string> BattlesBeaten { get; set; } = new();
/// <summary>Habitat ids claimed as fight prizes (sewage duct, neighbor pool, …).</summary>
public List<string> PrizeHabitatsWon { get; set; } = new();
/// <summary>Championship Cup title belts won (v8+).</summary>
public int ChampionshipTitles { get; set; }
public int SharkKills { get; set; }
public int SharkEaten { get; set; }
public bool SharkEverSpawned { get; set; }
// Story / tutorial quests (v6+)
public int StoryIndex { get; set; }
public bool StoryDone { get; set; }
public string StoryBeatId { get; set; } = "";
public bool StoryFedOnce { get; set; }
public bool StoryTrainedOnce { get; set; }
public bool StoryBoomOnce { get; set; }
/// <summary>First successful combine / evolve (v10+).</summary>
public bool StoryCombinedOnce { get; set; }
/// <summary>First fish sold (v10+).</summary>
public bool StorySoldOnce { get; set; }
/// <summary>Grandma gift ids already claimed (fish_tank, cold_wallet, …) v11+.</summary>
public List<string> GrandmaGiftsReceived { get; set; } = new();
/// <summary>
/// Economy.PlaytimeSeconds when the last ladder gift was claimed — spaces intros so they don't stack.
/// </summary>
public float LastGiftClaimPlaytime { get; set; }
/// <summary>One-shot mid-game Grandma reaction ids (first feed/abom/fight/…) v12+.</summary>
public List<string> GrandmaReactSaid { get; set; } = new();
/// <summary>Lot Run location ids cleared at least once (v13+).</summary>
public List<string> LotRunsCleared { get; set; } = new();
/// <summary>Adventure Mode: player has flushed a fish at least once (v13+).</summary>
public bool AdventureFlushed { get; set; }
/// <summary>Unhatched tank eggs from rare breeding (v14+).</summary>
public List<EggSaveData> TankEggs { get; set; } = new();
// Porch crimes (optional — old saves default empty)
public int TankKicks { get; set; }
public int JanitorMethOffers { get; set; }
public bool RobbedCookieTin { get; set; }
public bool DosedPipes { get; set; }
public bool GiftedGrandmaMeth { get; set; }
public int GarageRansacks { get; set; }
public int BradSpikes { get; set; }
public bool BradSpikedPending { get; set; }
public int FightLootM80 { get; set; }
public int FightLootMeth { get; set; }
public bool JanitorHushM80 { get; set; }
public bool JanitorCaffeineCrack { get; set; }
/// <summary>Unlocked porch trophy ids.</summary>
public List<string> AchievementsUnlocked { get; set; } = new();
// Dual tanks (v2+)
public string FreshTankName { get; set; } = "Trailer Lot Tank";
public string SaltTankName { get; set; } = "Saltwater Tank";
public string FreshHabitatId { get; set; } = "tank_fresh_1";
public string SaltHabitatId { get; set; } = "tank_salt_1";
public int FreshCapacity { get; set; } = TankSim.DefaultCapacity;
public int SaltCapacity { get; set; } = TankSim.DefaultCapacity;
public bool SaltUnlocked { get; set; }
public string ActiveWater { get; set; } = "Fresh";
public List<FishSaveData> FreshFish { get; set; } = new();
public List<FishSaveData> SaltFish { get; set; } = new();
public List<DecorSaveData> FreshDecor { get; set; } = new();
public List<DecorSaveData> SaltDecor { get; set; } = new();
/// <summary>Back-glass poster ids (v15+). Empty = default water.</summary>
public string FreshBackdropId { get; set; }
public string SaltBackdropId { get; set; }
/// <summary>Gravel / sand floor ids. Empty = stock lot dirt.</summary>
public string FreshGravelId { get; set; }
public string SaltGravelId { get; set; }
// Legacy single-tank fields (v1 continue support)
public string TankName { get; set; } = "Trailer Lot Tank";
public string Water { get; set; } = "Fresh";
public int Capacity { get; set; } = TankSim.DefaultCapacity;
public List<FishSaveData> Fish { get; set; } = new();
}
public sealed class DecorSaveData
{
public string DefId { get; set; }
public float X { get; set; }
public float Y { get; set; }
}
public sealed class FishSaveData
{
public string SpeciesId { get; set; }
/// <summary>Optional player nickname (null/empty = species name).</summary>
public string Nickname { get; set; }
public float X { get; set; }
public float Y { get; set; }
public float Vx { get; set; }
public float Vy { get; set; }
public bool OnMeth { get; set; }
public float MethTimer { get; set; }
public float SickTimer { get; set; }
public float Addiction { get; set; }
public float WithdrawalTimer { get; set; }
public float Endurance { get; set; } = 3f;
public float SpeedStat { get; set; } = 3f;
public float Agility { get; set; } = 3f;
public bool Jacked { get; set; }
public float JackedTimer { get; set; }
public float Fat { get; set; }
public bool Stoned { get; set; }
public float StonedTimer { get; set; }
/// <summary>Seconds left of post-fight injury lockout (0 = can fight).</summary>
public float FightInjuryTimer { get; set; }
/// <summary>Seconds alive — drives guppy→adult growth (0 = treat as adult on old saves).</summary>
public float AgeSeconds { get; set; }
/// <summary>Monster predation bulk 0–100 (size from eating tankmates).</summary>
public float PredationBulk { get; set; }
/// <summary>How many fish this one has eaten.</summary>
public int FishEatenCount { get; set; }
/// <summary><see cref="FishRarity"/> as int.</summary>
public int Rarity { get; set; }
/// <summary>Seconds left carrying eggs (v14+).</summary>
public float PregnantTimer { get; set; }
/// <summary>Seconds before this fish can court again (v14+).</summary>
public float MateCooldown { get; set; }
}