Definitions for lot-run content: enums and data classes that describe room kinds, individual rooms (LotRoomDef), and locations (LotLocationDef) with a static Catalog of predefined locations and a Find(id) lookup.
namespace NoChillquarium;
/// <summary>What happens in one dungeon room.</summary>
public enum LotRoomKind
{
/// <summary>Flavor only — click Continue.</summary>
Look,
/// <summary>Auto-resolve skirmish with local AI fish.</summary>
Fight,
/// <summary>Instant Ð (and maybe chaos).</summary>
Loot,
/// <summary>Risk: injury / chaos / small Ð.</summary>
Hazard,
/// <summary>Two buttons with different outcomes.</summary>
Choice,
/// <summary>Harder fight; clearing ends the run.</summary>
Boss
}
/// <summary>One room on a lot-run map.</summary>
public sealed class LotRoomDef
{
public string Title { get; init; }
public string Body { get; init; }
public LotRoomKind Kind { get; init; } = LotRoomKind.Look;
public string Button { get; init; } = "Continue";
// Fight / boss
public float AiStatMin { get; init; } = 2f;
public float AiStatMax { get; init; } = 4f;
public string[] EnemyNames { get; init; }
public string EnemyColor { get; init; } = "#8090a0";
public double FightRewardMin { get; init; }
public double FightRewardMax { get; init; }
// Loot
public double LootMin { get; init; }
public double LootMax { get; init; }
// Hazard
public float InjuryChance { get; init; } = 0.35f;
public float ChaosOnHazard { get; init; } = 2f;
// Choice
public string ChoiceA { get; init; }
public string ChoiceB { get; init; }
public string ChoiceAResult { get; init; }
public string ChoiceBResult { get; init; }
public double ChoiceALoot { get; init; }
public double ChoiceBLoot { get; init; }
public float ChoiceAChaos { get; init; }
public float ChoiceBChaos { get; init; }
public float ChoiceBInjuryChance { get; init; }
}
/// <summary>A crawlable location off Grandma's lot (jr high, alley, pool, …).</summary>
public sealed class LotLocationDef
{
public string Id { get; init; }
public string Name { get; init; }
public string Note { get; init; }
public string Blurb { get; init; }
public string ThemeClass { get; init; } = "";
public double EntryFee { get; init; }
public int TeamSize { get; init; } = 1;
/// <summary>Min playtime seconds before the map shows as unlocked.</summary>
public float UnlockPlaytime { get; init; }
/// <summary>Need this many fight wins (or equal losses+wins) to enter.</summary>
public int UnlockFights { get; init; }
/// <summary>Must have trained once.</summary>
public bool RequireTrained { get; init; }
public double ClearBonus { get; init; }
public string ClearBanner { get; init; }
/// <summary>First adventure: only entered by flushing a fish (then re-openable from map).</summary>
public bool FlushOrigin { get; init; }
public IReadOnlyList<LotRoomDef> Rooms { get; init; }
public static IReadOnlyList<LotLocationDef> Catalog { get; } = new[]
{
// 0 — first crawl (flush entry)
new LotLocationDef
{
Id = "toilet_pipes",
Name = "Toilet Pipes",
Note = "under the trailer",
Blurb = "Municipal pipe. U-bends. Your fish is ahead of you.",
ThemeClass = "run-toilet",
EntryFee = 0,
TeamSize = 1,
UnlockPlaytime = 0f,
RequireTrained = false,
UnlockFights = 0,
FlushOrigin = true,
ClearBonus = 40,
ClearBanner = "Pipes cleared. Other maps unlocked on the board.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Into the trap",
Body = "Tile walls. Cold water. They're already past the U-bend.",
Button = "Follow"
},
new LotRoomDef
{
Kind = LotRoomKind.Hazard,
Title = "U-Bend",
Body = "Narrow curve. Rough scale. Easy to scrape if you rush.",
Button = "Squeeze through",
InjuryChance = 0.25f,
ChaosOnHazard = 1.5f
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Pipe fauna",
Body = "Small, mean things that live in standing water.",
Button = "Fight",
AiStatMin = 1.4f,
AiStatMax = 2.8f,
EnemyNames = new[] { "Pipe Mite", "Silt Ray", "Trap Snail", "Scale Flake" },
EnemyColor = "#607080",
FightRewardMin = 12,
FightRewardMax = 22
},
new LotRoomDef
{
Kind = LotRoomKind.Loot,
Title = "Silt pocket",
Body = "A ring, some coins, grit. Worth pocketing.",
Button = "Take it",
LootMin = 16,
LootMax = 34
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "Main line",
Body = "Where house lines join the lot trunk. Something large blocks the flow.",
Button = "Fight",
AiStatMin = 2.4f,
AiStatMax = 4.2f,
EnemyNames = new[] { "Mainline Carp", "Trunk Bass", "Joint Eel", "Sewer Perch" },
EnemyColor = "#405060",
FightRewardMin = 28,
FightRewardMax = 48
}
}
},
// 1 — after first flush adventure
new LotLocationDef
{
Id = "jr_high_night",
Name = "Jr High After Hours",
Note = "after dark",
Blurb = "Fence, halls, principal's office. Bring one trained fish.",
ThemeClass = "run-school",
EntryFee = 12,
TeamSize = 1,
UnlockPlaytime = 6f * 60f,
RequireTrained = true,
UnlockFights = 0,
ClearBonus = 35,
ClearBanner = "School run done. Stay off the cameras.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Chain-link",
Body = "Wax and metal. Hall lights on a timer.",
Button = "Climb in"
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Hall patrol",
Body = "Someone's fish in a plastic carrier. They take the job seriously.",
Button = "Fight",
AiStatMin = 1.6f,
AiStatMax = 3.2f,
EnemyNames = new[] { "Carrier Guppy", "Badge Molly", "Tardy Tetra", "Whistle Ray" },
EnemyColor = "#708090",
FightRewardMin = 10,
FightRewardMax = 18
},
new LotRoomDef
{
Kind = LotRoomKind.Loot,
Title = "Vending machine",
Body = "Kick once. Coins rattle out.",
Button = "Kick",
LootMin = 8,
LootMax = 22
},
new LotRoomDef
{
Kind = LotRoomKind.Choice,
Title = "Split hall",
Body = "Detention wing is quiet. Gym still smells like bleach.",
ChoiceA = "Detention",
ChoiceB = "Gym",
ChoiceAResult = "Teacher's coffee can — cash and receipts.",
ChoiceBResult = "Hard floor. Your fish takes a knock.",
ChoiceALoot = 14,
ChoiceBLoot = 6,
ChoiceBChaos = 1.5f,
ChoiceBInjuryChance = 0.25f
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "Principal's office",
Body = "Nameplate. Plastic plant. A prize guppy in a desk tank.",
Button = "Fight",
AiStatMin = 2.8f,
AiStatMax = 4.6f,
EnemyNames = new[] { "Desk Guppy", "Office Betta", "Policy Pike", "Badge Bass" },
EnemyColor = "#405070",
FightRewardMin = 28,
FightRewardMax = 48
}
}
},
// 2
new LotLocationDef
{
Id = "dark_alley",
Name = "Dark Alley",
Note = "behind 4B",
Blurb = "Dumpsters and standing water. Watch for glass.",
ThemeClass = "run-alley",
EntryFee = 22,
TeamSize = 1,
UnlockPlaytime = 10f * 60f,
RequireTrained = true,
UnlockFights = 1,
ClearBonus = 55,
ClearBanner = "Alley cleared.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Dumpster line",
Body = "Gas-station neon. Grease film on the water.",
Button = "Enter"
},
new LotRoomDef
{
Kind = LotRoomKind.Hazard,
Title = "Broken glass",
Body = "Shards in the runoff. Easy cut if you're careless.",
Button = "Push through",
InjuryChance = 0.4f,
ChaosOnHazard = 2.5f
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Alley stock",
Body = "Half-feral fish living off trash water.",
Button = "Fight",
AiStatMin = 2.4f,
AiStatMax = 4.2f,
EnemyNames = new[] { "Grease Molly", "Moth Betta", "Neon Trash", "Curb Guppy" },
EnemyColor = "#504060",
FightRewardMin = 16,
FightRewardMax = 30
},
new LotRoomDef
{
Kind = LotRoomKind.Loot,
Title = "Dropped wallet",
Body = "Not yours. Still money.",
Button = "Take Ð",
LootMin = 18,
LootMax = 40
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "4B cousin",
Body = "Related to Brad. Runs this strip like he owns it.",
Button = "Fight",
AiStatMin = 3.5f,
AiStatMax = 5.5f,
EnemyNames = new[] { "4B Cousin", "Alley Lead", "Strip Boss", "Lot Ray" },
EnemyColor = "#803040",
FightRewardMin = 40,
FightRewardMax = 70
}
}
},
// 3
new LotLocationDef
{
Id = "neighbor_pool_night",
Name = "Neighbor's Pool",
Note = "after midnight",
Blurb = "Chlorine, cameras, deed-rule koi. Team of two.",
ThemeClass = "run-pool",
EntryFee = 35,
TeamSize = 2,
UnlockPlaytime = 14f * 60f,
RequireTrained = true,
UnlockFights = 2,
ClearBonus = 80,
ClearBanner = "Pool cleared. Don't leave wet footprints.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Back gate",
Body = "Motion light. Timer. You have a window.",
Button = "Slip in"
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Skimmer line",
Body = "Pool fish used to clean water. Territorial.",
Button = "Fight",
AiStatMin = 3f,
AiStatMax = 5f,
EnemyNames = new[] { "Skimmer Ray", "Float Bass", "Filter Guppy", "Deep Molly" },
EnemyColor = "#2080a0",
FightRewardMin = 22,
FightRewardMax = 40
},
new LotRoomDef
{
Kind = LotRoomKind.Hazard,
Title = "Chemical shock",
Body = "High chlorine. Hard on gills if you linger.",
Button = "Push through",
InjuryChance = 0.45f,
ChaosOnHazard = 3f
},
new LotRoomDef
{
Kind = LotRoomKind.Choice,
Title = "Cabana / board",
Body = "Quiet loot at the cabana, or risk noise off the board.",
ChoiceA = "Cabana",
ChoiceB = "Board",
ChoiceAResult = "Cash in a towel bin.",
ChoiceBResult = "Splash carries. Injury risk.",
ChoiceALoot = 28,
ChoiceBLoot = 12,
ChoiceBChaos = 4f,
ChoiceBInjuryChance = 0.35f
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "Deed koi",
Body = "HOA champion stock. Big, trained, expensive.",
Button = "Fight",
AiStatMin = 4.2f,
AiStatMax = 6.5f,
EnemyNames = new[] { "Covenant Koi", "Deed Bass", "Laminate Ray", "Rule Carp" },
EnemyColor = "#30a060",
FightRewardMin = 55,
FightRewardMax = 95
}
}
},
// 4
new LotLocationDef
{
Id = "gas_station",
Name = "Gas Station Dumpsters",
Note = "behind pumps",
Blurb = "Grease trap water. Janitor pretends not to see.",
ThemeClass = "run-gas",
EntryFee = 28,
TeamSize = 1,
UnlockPlaytime = 12f * 60f,
RequireTrained = true,
UnlockFights = 1,
ClearBonus = 60,
ClearBanner = "Dumpster line cleared.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Pump row",
Body = "Janitor mops past. \"Not my dumpsters.\"",
Button = "Around back"
},
new LotRoomDef
{
Kind = LotRoomKind.Loot,
Title = "Change tray",
Body = "Uncounted change under the register shelf.",
Button = "Take it",
LootMin = 12,
LootMax = 28
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Grease trap",
Body = "Something lives in the trap water. Hungry.",
Button = "Fight",
AiStatMin = 2.8f,
AiStatMax = 4.8f,
EnemyNames = new[] { "Trap Carp", "Oil Ray", "Sludge Molly", "Pump Perch" },
EnemyColor = "#806020",
FightRewardMin = 18,
FightRewardMax = 34
},
new LotRoomDef
{
Kind = LotRoomKind.Hazard,
Title = "Oil film",
Body = "Slick surface. Hard to get purchase. Easy bruise.",
Button = "Cross",
InjuryChance = 0.3f,
ChaosOnHazard = 2f
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "Under-counter tank",
Body = "Illegal display stock. Big, fed on scraps, mean.",
Button = "Fight",
AiStatMin = 3.8f,
AiStatMax = 5.8f,
EnemyNames = new[] { "Counter Bass", "Shelf Shark", "Lottery Carp", "Shift Lead" },
EnemyColor = "#a05020",
FightRewardMin = 45,
FightRewardMax = 75
}
}
},
// 5
new LotLocationDef
{
Id = "storm_drain",
Name = "Storm Drain",
Note = "lot runoff",
Blurb = "Concrete under the street. Team of two. Watch the drop.",
ThemeClass = "run-drain",
EntryFee = 40,
TeamSize = 2,
UnlockPlaytime = 18f * 60f,
RequireTrained = true,
UnlockFights = 3,
ClearBonus = 90,
ClearBanner = "Drain cleared.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Grate",
Body = "Lift with a stick. Cold air, wet concrete.",
Button = "Drop in"
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Runoff school",
Body = "Fish living on street wash. Tough and dirty.",
Button = "Fight",
AiStatMin = 3.2f,
AiStatMax = 5.2f,
EnemyNames = new[] { "Leaf Carp", "Curb Ray", "Grate Guppy", "Gutter Bass" },
EnemyColor = "#405848",
FightRewardMin = 24,
FightRewardMax = 42
},
new LotRoomDef
{
Kind = LotRoomKind.Loot,
Title = "Coin silt",
Body = "Fountain and parking runoff. Small metal, real Ð.",
Button = "Scoop",
LootMin = 20,
LootMax = 50
},
new LotRoomDef
{
Kind = LotRoomKind.Choice,
Title = "Forked pipe",
Body = "Left is quieter. Right is deeper and richer.",
ChoiceA = "Quiet pipe",
ChoiceB = "Deep pipe",
ChoiceAResult = "Cleaner water. Small take.",
ChoiceBResult = "Bigger take. Something bit.",
ChoiceALoot = 15,
ChoiceBLoot = 45,
ChoiceBChaos = 3f,
ChoiceBInjuryChance = 0.4f
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "Trunk blockage",
Body = "Large fish lodged in the main trunk. Clear it or turn back.",
Button = "Fight",
AiStatMin = 4.5f,
AiStatMax = 7f,
EnemyNames = new[] { "Trunk Carp", "Block Bass", "Cap Ray", "Mainline Eel" },
EnemyColor = "#304838",
FightRewardMin = 70,
FightRewardMax = 110
}
}
},
// 6
new LotLocationDef
{
Id = "bingo_hall",
Name = "Bingo Hall Basement",
Note = "downstairs",
Blurb = "Under the hall Grandma plays. She pretends not to know the door code.",
ThemeClass = "run-bingo",
EntryFee = 50,
TeamSize = 2,
UnlockPlaytime = 22f * 60f,
RequireTrained = true,
UnlockFights = 4,
ClearBonus = 120,
ClearBanner = "Basement cleared. Don't tell bingo night.",
Rooms = new[]
{
new LotRoomDef
{
Kind = LotRoomKind.Look,
Title = "Side door",
Body = "Coffee, ash, floor wax. Her crowd.",
Button = "Enter"
},
new LotRoomDef
{
Kind = LotRoomKind.Fight,
Title = "Basement stock",
Body = "Fish kept for side bets. Ink-stained carriers.",
Button = "Fight",
AiStatMin = 3.6f,
AiStatMax = 5.8f,
EnemyNames = new[] { "Daub Molly", "Card Ray", "Caller Carp", "Pot Bass" },
EnemyColor = "#704080",
FightRewardMin = 28,
FightRewardMax = 48
},
new LotRoomDef
{
Kind = LotRoomKind.Loot,
Title = "Tip jar",
Body = "She said not to. You take a little.",
Button = "Take Ð",
LootMin = 30,
LootMax = 70
},
new LotRoomDef
{
Kind = LotRoomKind.Hazard,
Title = "Smoke break",
Body = "Stale air. Hard on gills if you hang around.",
Button = "Push through",
InjuryChance = 0.35f,
ChaosOnHazard = 2f
},
new LotRoomDef
{
Kind = LotRoomKind.Boss,
Title = "Hall champion",
Body = "Not Grandma. Someone who wins too often to be lucky.",
Button = "Fight",
AiStatMin = 5f,
AiStatMax = 7.5f,
EnemyNames = new[] { "Hall Champ", "Blackout Bass", "Hard-Way Ray", "House Carp" },
EnemyColor = "#903070",
FightRewardMin = 90,
FightRewardMax = 140
}
}
},
};
public static LotLocationDef Find( string id )
{
if ( string.IsNullOrWhiteSpace( id ) )
return null;
foreach ( var loc in Catalog )
{
if ( string.Equals( loc.Id, id, StringComparison.OrdinalIgnoreCase ) )
return loc;
}
return null;
}
}