Definition of HabitatDef, a plain data holder that lists all habitat (tank) upgrades and provides lookup helpers. It declares properties (Id, Name, Rank, Water, capacity, cost, etc.), many static readonly instances for different habitats, and utility methods Catalog, FightPrizes, Find, StarterFor, NextFor, MatchesTank.
namespace NoChillquarium;
/// <summary>
/// Habitat / container upgrade. Water type of the tank slot is unchanged —
/// habitats are bigger (or funnier) homes with more slots and flavor.
/// </summary>
public sealed class HabitatDef
{
public string Id { get; init; }
public string Name { get; init; }
public string Note { get; init; }
/// <summary>Upgrade rank within a chain (0 = starter).</summary>
public int Rank { get; init; }
/// <summary>Which tank slot may buy this (Any = both).</summary>
public WaterType Water { get; init; }
public int BaseCapacity { get; init; }
public int MaxDecor { get; init; }
public double Cost { get; init; }
/// <summary>Playtime seconds before shop offers this upgrade.</summary>
public float UnlockPlaytime { get; init; }
/// <summary>Multiplies fish + decor income on this tank.</summary>
public float IncomeMult { get; init; }
/// <summary>CSS class on tank-glass (e.g. hab-bathtub).</summary>
public string CssClass { get; init; }
public string Banner { get; init; }
/// <summary>Only awarded by winning fights — hidden from shop upgrade chain.</summary>
public bool FightPrizeOnly { get; init; }
// --- Fresh chain ---
public static readonly HabitatDef StarterFresh = new()
{
Id = "tank_fresh_1",
Name = "Starter Fresh Tank",
Note = "glass starter",
Rank = 0,
Water = WaterType.Fresh,
BaseCapacity = 10,
MaxDecor = 8,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1f,
CssClass = "hab-glass",
Banner = "Fresh glass online."
};
public static readonly HabitatDef DeluxeFresh = new()
{
Id = "tank_fresh_2",
Name = "Deluxe Fresh",
Note = "roomier glass",
Rank = 1,
Water = WaterType.Fresh,
BaseCapacity = 40,
MaxDecor = 10,
Cost = 120,
UnlockPlaytime = 8f * 60f, // ~8 min
IncomeMult = 1.08f,
CssClass = "hab-deluxe",
Banner = "Deluxe glass."
};
// --- Salt chain start ---
public static readonly HabitatDef StarterSalt = new()
{
Id = "tank_salt_1",
Name = "Saltwater Tank",
Note = "starter reef",
Rank = 0,
Water = WaterType.Salt,
BaseCapacity = 10,
MaxDecor = 8,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1f,
CssClass = "hab-glass",
Banner = "Salt glass online."
};
public static readonly HabitatDef DeluxeSalt = new()
{
Id = "tank_salt_2",
Name = "Deluxe Reef",
Note = "bigger salt box",
Rank = 1,
Water = WaterType.Salt,
BaseCapacity = 40,
MaxDecor = 10,
Cost = 130,
UnlockPlaytime = 8f * 60f,
IncomeMult = 1.08f,
CssClass = "hab-deluxe",
Banner = "Deluxe reef."
};
// --- Shared comedy / scale habitats (rank 2+) ---
public static readonly HabitatDef Bathtub = new()
{
Id = "bathtub",
Name = "Bathtub",
Note = "porcelain comedy",
Rank = 2,
Water = WaterType.Any,
BaseCapacity = 25,
MaxDecor = 10,
Cost = 350,
UnlockPlaytime = 25f * 60f, // ~25 min
IncomeMult = 1.12f,
CssClass = "hab-bathtub",
Banner = "Bathtub habitat."
};
public static readonly HabitatDef KiddiePool = new()
{
Id = "kiddie_pool",
Name = "Kiddie Pool",
Note = "backyard upgrade",
Rank = 3,
Water = WaterType.Any,
BaseCapacity = 35,
MaxDecor = 12,
Cost = 550,
UnlockPlaytime = 50f * 60f, // ~50 min
IncomeMult = 1.18f,
CssClass = "hab-kiddie",
Banner = "Kiddie pool."
};
public static readonly HabitatDef NeighborPool = new()
{
Id = "neighbor_pool",
Name = "Neighbor's Pool",
Note = "borrowed permanently",
Rank = 4,
Water = WaterType.Any,
BaseCapacity = 80,
MaxDecor = 14,
Cost = 1000,
UnlockPlaytime = 80f * 60f, // ~1h20
IncomeMult = 1.28f,
CssClass = "hab-neighbor",
Banner = "Neighbor's pool."
};
public static readonly HabitatDef Lake = new()
{
Id = "lake",
Name = "Lake",
Note = "real water now",
Rank = 5,
Water = WaterType.Any,
BaseCapacity = 120,
MaxDecor = 16,
Cost = 2000,
UnlockPlaytime = 2f * 3600f, // ~2h
IncomeMult = 1.4f,
CssClass = "hab-lake",
Banner = "Lake habitat."
};
public static readonly HabitatDef Gulf = new()
{
Id = "gulf",
Name = "Gulf of America",
Note = "branded brine",
Rank = 6,
Water = WaterType.Any,
BaseCapacity = 200,
MaxDecor = 18,
Cost = 4000,
UnlockPlaytime = 2.75f * 3600f, // ~2h45
IncomeMult = 1.55f,
CssClass = "hab-gulf",
Banner = "Gulf of America."
};
public static readonly HabitatDef Ocean = new()
{
Id = "ocean",
Name = "Ocean",
Note = "cocaine sharks",
Rank = 7,
Water = WaterType.Any,
BaseCapacity = 500,
MaxDecor = 20,
Cost = 7500,
UnlockPlaytime = 3.25f * 3600f, // ~3h15
IncomeMult = 1.75f,
CssClass = "hab-ocean",
Banner = "Ocean. Sharks inbound."
};
// --- Fight prizes (not sold — win them in AI circuits) ---
public static readonly HabitatDef SewageDuct = new()
{
Id = "sewage_duct",
Name = "Sewage Duct",
Note = "fight prize · fragrant",
Rank = 3,
Water = WaterType.Any,
BaseCapacity = 45,
MaxDecor = 11,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1.22f,
CssClass = "hab-sewage",
Banner = "Sewage duct claimed.",
FightPrizeOnly = true
};
public static readonly HabitatDef StormDrain = new()
{
Id = "storm_drain",
Name = "Storm Drain",
Note = "fight prize · runoff",
Rank = 3,
Water = WaterType.Any,
BaseCapacity = 50,
MaxDecor = 12,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1.24f,
CssClass = "hab-storm",
Banner = "Storm drain secured.",
FightPrizeOnly = true
};
public static readonly HabitatDef AbandonedSpa = new()
{
Id = "abandoned_spa",
Name = "Abandoned Spa",
Note = "fight prize · chlorine ghost",
Rank = 4,
Water = WaterType.Any,
BaseCapacity = 70,
MaxDecor = 13,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1.32f,
CssClass = "hab-spa",
Banner = "Abandoned spa.",
FightPrizeOnly = true
};
public static readonly HabitatDef CommunityFountain = new()
{
Id = "community_fountain",
Name = "Community Fountain",
Note = "fight prize · coins optional",
Rank = 4,
Water = WaterType.Any,
BaseCapacity = 65,
MaxDecor = 12,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1.3f,
CssClass = "hab-fountain",
Banner = "Community fountain.",
FightPrizeOnly = true
};
public static readonly HabitatDef HotTubHeist = new()
{
Id = "hot_tub_heist",
Name = "Stolen Hot Tub",
Note = "fight prize · jets of shame",
Rank = 5,
Water = WaterType.Any,
BaseCapacity = 90,
MaxDecor = 14,
Cost = 0,
UnlockPlaytime = 0f,
IncomeMult = 1.38f,
CssClass = "hab-hottub",
Banner = "Stolen hot tub.",
FightPrizeOnly = true
};
/// <summary>All habitats (including starters + fight prizes).</summary>
public static IReadOnlyList<HabitatDef> Catalog { get; } = new[]
{
StarterFresh, DeluxeFresh,
StarterSalt, DeluxeSalt,
Bathtub, KiddiePool, NeighborPool, Lake, Gulf, Ocean,
SewageDuct, StormDrain, AbandonedSpa, CommunityFountain, HotTubHeist
};
public static IEnumerable<HabitatDef> FightPrizes =>
Catalog.Where( h => h.FightPrizeOnly );
public static HabitatDef Find( string id )
{
if ( string.IsNullOrWhiteSpace( id ) )
return null;
foreach ( var h in Catalog )
{
if ( h.Id == id )
return h;
}
return null;
}
public static HabitatDef StarterFor( WaterType water ) =>
water == WaterType.Salt ? StarterSalt : StarterFresh;
/// <summary>Next buyable habitat for a tank at the given rank + water (shop chain only).</summary>
public static HabitatDef NextFor( WaterType tankWater, int currentRank )
{
var want = currentRank + 1;
HabitatDef best = null;
foreach ( var h in Catalog )
{
if ( h.FightPrizeOnly )
continue;
if ( h.Rank != want )
continue;
if ( h.Water != WaterType.Any && h.Water != tankWater )
continue;
// Prefer water-specific over Any at same rank.
if ( best is null || (h.Water == tankWater && best.Water == WaterType.Any) )
best = h;
}
return best;
}
public static bool MatchesTank( HabitatDef def, WaterType tankWater )
{
if ( def is null )
return false;
return def.Water == WaterType.Any || def.Water == tankWater;
}
}