FishSpecies.cs

A static catalog of fish species for the game NoChillquarium. Defines properties for species (id, name, water type, colors, dimensions, speed, economy values, sprite id), many predefined species, shop/unlock logic, and readonly catalogs (fresh, salt, base, monster, all).

File AccessNetworking
namespace NoChillquarium;

public enum WaterType
{
	Fresh,
	Salt,
	Any
}

/// <summary>Static species catalog. Sprites resolve via <see cref="SpriteCatalog"/>.</summary>
public sealed class FishSpecies
{
	public string Id { get; init; }
	public string Name { get; init; }
	public WaterType Water { get; init; }
	public string Color { get; init; }
	public string FinColor { get; init; }
	public float Width { get; init; }
	public float Height { get; init; }
	public float Speed { get; init; }
	public float IncomePerSecond { get; init; }
	public double BuyCost { get; init; }
	/// <summary>Playtime seconds before the shop sells this species (0 = day one).</summary>
	public float UnlockPlaytime { get; init; }
	/// <summary>Fusion abomination — not sold in shop, no further combine.</summary>
	public bool IsMonster { get; init; }
	/// <summary>Monster type index 1–10 (0 = normal).</summary>
	public int MonsterType { get; init; }

	/// <summary>Sprite id / filename stem (resolved via <see cref="SpriteCatalog"/>).</summary>
	public string SpritePath { get; init; }

	/// <summary>True when a Texture was built for this species (or its SpritePath alias).</summary>
	public bool HasSprite => SpriteCatalog.Has( SpriteId );

	/// <summary>Sprite id for &lt;spritebox&gt; — uses SpritePath so new species can reuse art.</summary>
	public string SpriteId =>
		string.IsNullOrWhiteSpace( SpritePath ) ? Id : SpritePath;

	/// <summary>
	/// Chillquarium-style ladder: cheap commons earn now;
	/// later species cost more and print more. Early payback ~60–90s adult.
	/// </summary>
	public static readonly FishSpecies Goldfish = new()
	{
		Id = "goldfish",
		Name = "Goldfish",
		Water = WaterType.Fresh,
		Color = "#f0a020",
		FinColor = "#ff6030",
		Width = 80f,
		Height = 56f,
		Speed = 70f,
		IncomePerSecond = 0.2f,
		BuyCost = 22,
		UnlockPlaytime = 0f,
		SpritePath = "goldfish"
	};

	public static readonly FishSpecies NeonTetra = new()
	{
		Id = "neon_tetra",
		Name = "Neon Tetra",
		Water = WaterType.Fresh,
		Color = "#2ec4b6",
		FinColor = "#e01e5a",
		Width = 62f,
		Height = 34f,
		Speed = 100f,
		IncomePerSecond = 0.24f,
		BuyCost = 36,
		UnlockPlaytime = 2f * 60f, // ~2 min
		SpritePath = "neon_tetra"
	};

	public static readonly FishSpecies Betta = new()
	{
		Id = "betta",
		Name = "Betta",
		Water = WaterType.Fresh,
		Color = "#7b2cbf",
		FinColor = "#ff6b6b",
		Width = 78f,
		Height = 62f,
		Speed = 55f,
		IncomePerSecond = 0.32f,
		BuyCost = 65,
		UnlockPlaytime = 5f * 60f, // ~5 min
		SpritePath = "betta"
	};

	public static readonly FishSpecies Clownfish = new()
	{
		Id = "clownfish",
		Name = "Clownfish",
		Water = WaterType.Salt,
		Color = "#ff8c1a",
		FinColor = "#ffffff",
		Width = 72f,
		Height = 46f,
		Speed = 75f,
		IncomePerSecond = 0.28f,
		BuyCost = 55,
		UnlockPlaytime = 6f * 60f, // ~6 min · salt midgame
		SpritePath = "clownfish"
	};

	public static readonly FishSpecies BlueTang = new()
	{
		Id = "blue_tang",
		Name = "Blue Tang",
		Water = WaterType.Salt,
		Color = "#2b6cff",
		FinColor = "#ffd24a",
		Width = 78f,
		Height = 50f,
		Speed = 85f,
		IncomePerSecond = 0.45f,
		BuyCost = 95,
		UnlockPlaytime = 12f * 60f, // ~12 min
		SpritePath = "blue_tang"
	};

	// --- Extra shop species (CSS bodies if no sprite; Chillquarium collect ladder) ---
	public static readonly FishSpecies Guppy = new()
	{
		Id = "guppy",
		Name = "Guppy",
		Water = WaterType.Fresh,
		Color = "#e060a0",
		FinColor = "#40d0c0",
		Width = 52f,
		Height = 30f,
		Speed = 110f,
		IncomePerSecond = 0.16f,
		BuyCost = 14,
		UnlockPlaytime = 0f,
		SpritePath = "guppy"
	};

	public static readonly FishSpecies Molly = new()
	{
		Id = "molly",
		Name = "Molly",
		Water = WaterType.Fresh,
		Color = "#303038",
		FinColor = "#f0e0a0",
		Width = 68f,
		Height = 44f,
		Speed = 78f,
		IncomePerSecond = 0.18f,
		BuyCost = 48,
		UnlockPlaytime = 5f * 60f,
		SpritePath = "molly"
	};

	public static readonly FishSpecies Koi = new()
	{
		Id = "koi",
		Name = "Koi",
		Water = WaterType.Fresh,
		Color = "#f06040",
		FinColor = "#ffffff",
		Width = 90f,
		Height = 52f,
		Speed = 62f,
		IncomePerSecond = 0.28f,
		BuyCost = 90,
		UnlockPlaytime = 10f * 60f,
		SpritePath = "koi"
	};

	public static readonly FishSpecies Angelfish = new()
	{
		Id = "angelfish",
		Name = "Angelfish",
		Water = WaterType.Fresh,
		Color = "#e8e0c8",
		FinColor = "#202028",
		Width = 64f,
		Height = 74f,
		Speed = 58f,
		IncomePerSecond = 0.32f,
		BuyCost = 110,
		UnlockPlaytime = 14f * 60f,
		SpritePath = "angelfish"
	};

	public static readonly FishSpecies Piranha = new()
	{
		Id = "piranha",
		Name = "Piranha",
		Water = WaterType.Fresh,
		Color = "#406040",
		FinColor = "#c03020",
		Width = 74f,
		Height = 52f,
		Speed = 95f,
		IncomePerSecond = 0.42f,
		BuyCost = 150,
		UnlockPlaytime = 18f * 60f,
		SpritePath = "piranha"
	};

	public static readonly FishSpecies Oscar = new()
	{
		Id = "oscar",
		Name = "Oscar",
		Water = WaterType.Fresh,
		Color = "#c07020",
		FinColor = "#101018",
		Width = 86f,
		Height = 62f,
		Speed = 52f,
		IncomePerSecond = 0.5f,
		BuyCost = 200,
		UnlockPlaytime = 28f * 60f,
		SpritePath = "oscar"
	};

	public static readonly FishSpecies Axolotl = new()
	{
		Id = "axolotl",
		Name = "Axolotl",
		Water = WaterType.Fresh,
		Color = "#f0a0b8",
		FinColor = "#e07090",
		Width = 84f,
		Height = 46f,
		Speed = 40f,
		IncomePerSecond = 0.58f,
		BuyCost = 260,
		UnlockPlaytime = 36f * 60f,
		SpritePath = "axolotl"
	};

	public static readonly FishSpecies Seahorse = new()
	{
		Id = "seahorse",
		Name = "Seahorse",
		Water = WaterType.Salt,
		Color = "#e0a040",
		FinColor = "#a06020",
		Width = 50f,
		Height = 74f,
		Speed = 35f,
		IncomePerSecond = 0.26f,
		BuyCost = 85,
		UnlockPlaytime = 14f * 60f,
		SpritePath = "seahorse"
	};

	public static readonly FishSpecies Puffer = new()
	{
		Id = "puffer",
		Name = "Puffer",
		Water = WaterType.Salt,
		Color = "#e0d060",
		FinColor = "#806020",
		Width = 72f,
		Height = 64f,
		Speed = 48f,
		IncomePerSecond = 0.34f,
		BuyCost = 130,
		UnlockPlaytime = 22f * 60f,
		SpritePath = "puffer"
	};

	public static readonly FishSpecies Lionfish = new()
	{
		Id = "lionfish",
		Name = "Lionfish",
		Water = WaterType.Salt,
		Color = "#e04040",
		FinColor = "#ffffff",
		Width = 84f,
		Height = 68f,
		Speed = 55f,
		IncomePerSecond = 0.48f,
		BuyCost = 190,
		UnlockPlaytime = 30f * 60f,
		SpritePath = "lionfish"
	};

	public static readonly FishSpecies Mandarin = new()
	{
		Id = "mandarin",
		Name = "Mandarin",
		Water = WaterType.Salt,
		Color = "#2050e0",
		FinColor = "#e0a020",
		Width = 62f,
		Height = 44f,
		Speed = 70f,
		IncomePerSecond = 0.55f,
		BuyCost = 240,
		UnlockPlaytime = 38f * 60f,
		SpritePath = "mandarin"
	};

	public static readonly FishSpecies Barracuda = new()
	{
		Id = "barracuda",
		Name = "Barracuda",
		Water = WaterType.Salt,
		Color = "#708090",
		FinColor = "#c0c8d0",
		Width = 110f,
		Height = 42f,
		Speed = 120f,
		IncomePerSecond = 0.72f,
		BuyCost = 340,
		UnlockPlaytime = 48f * 60f,
		SpritePath = "barracuda"
	};

	// --- Fusion monsters (combine only · not in shop) ---
	public static readonly FishSpecies Monster1 = new()
	{
		Id = "monster_01",
		Name = "Type-1 Abom",
		Water = WaterType.Fresh,
		Color = "#c04020",
		FinColor = "#9020a0",
		Width = 72f,
		Height = 52f,
		Speed = 55f,
		IncomePerSecond = 0.55f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 1,
		SpritePath = "monster_01"
	};

	public static readonly FishSpecies Monster2 = new()
	{
		Id = "monster_02",
		Name = "Type-2 Abom",
		Water = WaterType.Fresh,
		Color = "#20c0a0",
		FinColor = "#f0a020",
		Width = 68f,
		Height = 48f,
		Speed = 80f,
		IncomePerSecond = 0.62f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 2,
		SpritePath = "monster_02"
	};

	public static readonly FishSpecies Monster3 = new()
	{
		Id = "monster_03",
		Name = "Type-3 Abom",
		Water = WaterType.Any,
		Color = "#ff8030",
		FinColor = "#f0c040",
		Width = 70f,
		Height = 50f,
		Speed = 65f,
		IncomePerSecond = 0.7f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 3,
		SpritePath = "monster_03"
	};

	public static readonly FishSpecies Monster4 = new()
	{
		Id = "monster_04",
		Name = "Type-4 Abom",
		Water = WaterType.Any,
		Color = "#3060ff",
		FinColor = "#f0b020",
		Width = 74f,
		Height = 52f,
		Speed = 70f,
		IncomePerSecond = 0.85f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 4,
		SpritePath = "monster_04"
	};

	public static readonly FishSpecies Monster5 = new()
	{
		Id = "monster_05",
		Name = "Type-5 Abom",
		Water = WaterType.Fresh,
		Color = "#8030c0",
		FinColor = "#20e0c0",
		Width = 66f,
		Height = 50f,
		Speed = 75f,
		IncomePerSecond = 0.72f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 5,
		SpritePath = "monster_05"
	};

	public static readonly FishSpecies Monster6 = new()
	{
		Id = "monster_06",
		Name = "Type-6 Abom",
		Water = WaterType.Any,
		Color = "#e04080",
		FinColor = "#ff9030",
		Width = 70f,
		Height = 54f,
		Speed = 60f,
		IncomePerSecond = 0.78f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 6,
		SpritePath = "monster_06"
	};

	public static readonly FishSpecies Monster7 = new()
	{
		Id = "monster_07",
		Name = "Type-7 Abom",
		Water = WaterType.Any,
		Color = "#5020c0",
		FinColor = "#2080ff",
		Width = 76f,
		Height = 54f,
		Speed = 58f,
		IncomePerSecond = 0.95f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 7,
		SpritePath = "monster_07"
	};

	public static readonly FishSpecies Monster8 = new()
	{
		Id = "monster_08",
		Name = "Type-8 Abom",
		Water = WaterType.Any,
		Color = "#20d0b0",
		FinColor = "#ff7020",
		Width = 64f,
		Height = 46f,
		Speed = 90f,
		IncomePerSecond = 0.8f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 8,
		SpritePath = "monster_08"
	};

	public static readonly FishSpecies Monster9 = new()
	{
		Id = "monster_09",
		Name = "Type-9 Abom",
		Water = WaterType.Any,
		Color = "#1090e0",
		FinColor = "#40f0c0",
		Width = 72f,
		Height = 50f,
		Speed = 85f,
		IncomePerSecond = 0.9f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 9,
		SpritePath = "monster_09"
	};

	public static readonly FishSpecies Monster10 = new()
	{
		Id = "monster_10",
		Name = "Type-10 Abom",
		Water = WaterType.Salt,
		Color = "#1040c0",
		FinColor = "#ff9030",
		Width = 78f,
		Height = 56f,
		Speed = 62f,
		IncomePerSecond = 1.1f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 10,
		SpritePath = "monster_10"
	};

	// --- Same-species doubles (gold+gold, betta+betta, …) ---
	public static readonly FishSpecies MonsterGoldfish = new()
	{
		Id = "monster_goldfish",
		Name = "Monster Goldfish",
		Water = WaterType.Fresh,
		Color = "#f0a020",
		FinColor = "#ff4020",
		Width = 80f,
		Height = 56f,
		Speed = 50f,
		IncomePerSecond = 0.48f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 11,
		SpritePath = "monster_goldfish"
	};

	public static readonly FishSpecies MonsterBetta = new()
	{
		Id = "monster_betta",
		Name = "Monster Betta",
		Water = WaterType.Fresh,
		Color = "#7b2cbf",
		FinColor = "#ff2060",
		Width = 78f,
		Height = 60f,
		Speed = 48f,
		IncomePerSecond = 0.65f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 12,
		SpritePath = "monster_betta"
	};

	public static readonly FishSpecies MonsterNeon = new()
	{
		Id = "monster_neon",
		Name = "Monster Neon",
		Water = WaterType.Fresh,
		Color = "#2ec4b6",
		FinColor = "#e01e5a",
		Width = 64f,
		Height = 40f,
		Speed = 95f,
		IncomePerSecond = 0.52f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 13,
		SpritePath = "monster_neon"
	};

	public static readonly FishSpecies MonsterClown = new()
	{
		Id = "monster_clown",
		Name = "Monster Clown",
		Water = WaterType.Salt,
		Color = "#ff8c1a",
		FinColor = "#ffffff",
		Width = 74f,
		Height = 52f,
		Speed = 58f,
		IncomePerSecond = 0.7f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 14,
		SpritePath = "monster_clown"
	};

	public static readonly FishSpecies MonsterTang = new()
	{
		Id = "monster_tang",
		Name = "Monster Tang",
		Water = WaterType.Salt,
		Color = "#2b6cff",
		FinColor = "#ffd24a",
		Width = 82f,
		Height = 54f,
		Speed = 72f,
		IncomePerSecond = 0.95f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 15,
		SpritePath = "monster_tang"
	};

	// --- Evolution chain: Goldfish / Betta line ---
	// Stage 2: Monster Goldfish + Betta (or Monster Betta + Goldfish / Type-1 + mono)
	public static readonly FishSpecies EvoGoldBetta = new()
	{
		Id = "evo_gold_betta",
		Name = "Gilded Fury",
		Water = WaterType.Fresh,
		Color = "#e07020",
		FinColor = "#a020c0",
		Width = 92f,
		Height = 70f,
		Speed = 52f,
		IncomePerSecond = 1.45f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 30,
		SpritePath = "evo_gold_betta"
	};

	// Stage 3 apex: Monster Goldfish + Monster Betta (or Gilded Fury + mono monster)
	public static readonly FishSpecies EvoGoldBettaApex = new()
	{
		Id = "evo_gold_betta_apex",
		Name = "Crown Atrocity",
		Water = WaterType.Fresh,
		Color = "#ff9020",
		FinColor = "#d040ff",
		Width = 108f,
		Height = 82f,
		Speed = 48f,
		IncomePerSecond = 2.6f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 31,
		SpritePath = "evo_gold_betta_apex"
	};

	/// <summary>
	/// Monster + normal fish (or any unhandled monster pair) → giant Lovecraftian tentacle beast.
	/// </summary>
	public static readonly FishSpecies MonsterTentacle = new()
	{
		Id = "monster_tentacle",
		Name = "Tentacle Beast",
		Water = WaterType.Any,
		Color = "#1a3040",
		FinColor = "#60e0a0",
		// Giant — fills a chunk of the glass.
		Width = 128f,
		Height = 96f,
		Speed = 32f,
		IncomePerSecond = 2.15f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 40,
		SpritePath = "monster_tentacle"
	};

	// --- Fish + decor hybrids (combine a fish with a tank prop) ---
	public static readonly FishSpecies HybridPlant = new()
	{
		Id = "hybrid_plant",
		Name = "Kelp That Bites",
		Water = WaterType.Any,
		Color = "#40a050",
		FinColor = "#f0a020",
		Width = 70f,
		Height = 90f,
		Speed = 35f,
		IncomePerSecond = 0.42f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 20,
		SpritePath = "hybrid_plant"
	};

	public static readonly FishSpecies HybridChest = new()
	{
		Id = "hybrid_chest",
		Name = "Chest Maw",
		Water = WaterType.Any,
		Color = "#c09030",
		FinColor = "#ffd24a",
		Width = 78f,
		Height = 64f,
		Speed = 40f,
		IncomePerSecond = 0.88f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 21,
		SpritePath = "hybrid_chest"
	};

	public static readonly FishSpecies HybridBubble = new()
	{
		Id = "hybrid_bubble",
		Name = "Party Parasite",
		Water = WaterType.Any,
		Color = "#80e0ff",
		FinColor = "#ff80c0",
		Width = 58f,
		Height = 88f,
		Speed = 70f,
		IncomePerSecond = 0.5f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 22,
		SpritePath = "hybrid_bubble"
	};

	public static readonly FishSpecies HybridForty = new()
	{
		Id = "hybrid_forty",
		Name = "40oz Bass",
		Water = WaterType.Any,
		Color = "#3060a0",
		FinColor = "#f0a020",
		Width = 64f,
		Height = 80f,
		Speed = 45f,
		IncomePerSecond = 0.55f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 23,
		SpritePath = "hybrid_forty"
	};

	public static readonly FishSpecies HybridCuffs = new()
	{
		Id = "hybrid_cuffs",
		Name = "Felony Fin",
		Water = WaterType.Any,
		Color = "#808890",
		FinColor = "#e0e0e0",
		Width = 76f,
		Height = 58f,
		Speed = 50f,
		IncomePerSecond = 0.6f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 24,
		SpritePath = "hybrid_cuffs"
	};

	public static readonly FishSpecies HybridBoot = new()
	{
		Id = "hybrid_boot",
		Name = "Sole Survivor",
		Water = WaterType.Any,
		Color = "#604020",
		FinColor = "#f0a020",
		Width = 70f,
		Height = 70f,
		Speed = 38f,
		IncomePerSecond = 0.45f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 25,
		SpritePath = "hybrid_boot"
	};

	public static readonly FishSpecies HybridMeds = new()
	{
		Id = "hybrid_meds",
		Name = "Script Kiddie",
		Water = WaterType.Any,
		Color = "#e0e8f0",
		FinColor = "#e04060",
		Width = 66f,
		Height = 66f,
		Speed = 55f,
		IncomePerSecond = 0.58f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 26,
		SpritePath = "hybrid_meds"
	};

	public static readonly FishSpecies HybridDoge = new()
	{
		Id = "hybrid_doge",
		Name = "Such Abomination",
		Water = WaterType.Any,
		Color = "#e8c060",
		FinColor = "#f0a020",
		Width = 84f,
		Height = 72f,
		Speed = 42f,
		IncomePerSecond = 1.25f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 27,
		SpritePath = "hybrid_doge"
	};

	public static readonly FishSpecies HybridM80 = new()
	{
		Id = "hybrid_m80",
		Name = "Walking Violation",
		Water = WaterType.Any,
		Color = "#c41e1e",
		FinColor = "#2a2a2a",
		Width = 76f,
		Height = 62f,
		Speed = 65f,
		IncomePerSecond = 0.75f,
		BuyCost = 0,
		UnlockPlaytime = 99999f,
		IsMonster = true,
		MonsterType = 28,
		SpritePath = "hybrid_m80"
	};

	public static bool IsShopUnlocked( FishSpecies species ) =>
		species is not null
		&& !species.IsMonster
		&& Economy.PlaytimeSeconds >= species.UnlockPlaytime;

	public static string ShopUnlockHint( FishSpecies species )
	{
		if ( species is null )
			return "";
		if ( IsShopUnlocked( species ) )
			return "";
		var left = species.UnlockPlaytime - Economy.PlaytimeSeconds;
		var totalMin = (int)(left / 60f);
		if ( totalMin >= 60 )
		{
			var h = totalMin / 60;
			var m = totalMin % 60;
			return m > 0 ? $"Unlocks in {h}h {m}m" : $"Unlocks in {h}h";
		}
		if ( totalMin > 0 )
			return $"Unlocks in {totalMin}m";
		return $"Unlocks in {(int)MathF.Max( 1f, left )}s";
	}

	public static IReadOnlyList<FishSpecies> FreshCatalog { get; } = new[]
	{
		Guppy, Goldfish, NeonTetra, Molly, Betta, Koi, Angelfish, Piranha, Oscar, Axolotl
	};

	public static IReadOnlyList<FishSpecies> SaltCatalog { get; } = new[]
	{
		Clownfish, Seahorse, BlueTang, Puffer, Lionfish, Mandarin, Barracuda
	};

	public static IReadOnlyList<FishSpecies> BaseCatalog { get; } = new[]
	{
		Guppy, Goldfish, NeonTetra, Molly, Betta, Koi, Angelfish,
		Clownfish, Seahorse, Piranha, BlueTang, Puffer, Oscar,
		Lionfish, Axolotl, Mandarin, Barracuda
	};

	public static IReadOnlyList<FishSpecies> MonsterCatalog { get; } = new[]
	{
		Monster1, Monster2, Monster3, Monster4, Monster5,
		Monster6, Monster7, Monster8, Monster9, Monster10,
		MonsterGoldfish, MonsterBetta, MonsterNeon, MonsterClown, MonsterTang,
		EvoGoldBetta, EvoGoldBettaApex, MonsterTentacle,
		HybridPlant, HybridChest, HybridBubble, HybridForty, HybridCuffs,
		HybridBoot, HybridMeds, HybridDoge, HybridM80
	};

	/// <summary>Every species including fusion monsters (load / find).</summary>
	public static IReadOnlyList<FishSpecies> AllCatalog { get; } = new[]
	{
		Guppy, Goldfish, NeonTetra, Molly, Betta, Koi, Angelfish, Piranha, Oscar, Axolotl,
		Clownfish, Seahorse, BlueTang, Puffer, Lionfish, Mandarin, Barracuda,
		Monster1, Monster2, Monster3, Monster4, Monster5,
		Monster6, Monster7, Monster8, Monster9, Monster10,
		MonsterGoldfish, MonsterBetta, MonsterNeon, MonsterClown, MonsterTang,
		EvoGoldBetta, EvoGoldBettaApex, MonsterTentacle,
		HybridPlant, HybridChest, HybridBubble, HybridForty, HybridCuffs,
		HybridBoot, HybridMeds, HybridDoge, HybridM80
	};

	/// <summary>Shop: base species only (monsters are combine-only).</summary>
	public static IReadOnlyList<FishSpecies> ShopCatalog => BaseCatalog;

	public const string SharkSpriteId = "cocaine_shark";
	public static bool SharkHasSprite => SpriteCatalog.Has( SharkSpriteId );

	public static FishSpecies Find( string id ) =>
		AllCatalog.FirstOrDefault( s => s.Id == id ) ?? Goldfish;
}