Firekeeping: shared guild fires

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-21 12:16:22 +01:00
parent c64e28fc38
commit 54ae3ae641
6 changed files with 657 additions and 34 deletions

View file

@ -741,7 +741,7 @@ class TierTest(unittest.TestCase):
world = world_with(("kay", "miner"))
p = world["players"]["kay"]
p["xp"]["smithing"] = game.XP_TABLE[30]
p["bank"] = {"iron_ore": 5, "coal": 10}
p["bank"] = {"iron_ore": 5, "coal": 10, "willow_logs": 5}
self.assertEqual(game.start_action(world, p, "smith_steel_bar", T0), "Needs Smithy level 2")
world["city"]["buildings"]["smithy"]["level"] = 2
self.assertIsNone(game.start_action(world, p, "smith_steel_bar", T0))
@ -903,7 +903,7 @@ class HerbloreTest(unittest.TestCase):
game.start_action(world, p, "grow_guam_leaf", T0)
game.process_player(world, p, T0 + 450, random.Random(1))
self.assertEqual(p["bank"]["guam_leaf"], 10)
p["bank"]["logs"] = 3
p["bank"]["logs"] = 6 # one a potion, one to burn under the still
self.assertIsNone(game.start_action(world, p, "brew_woodcutting_potion_1", T0 + 450)) # tier I needs no Herblore
game.process_player(world, p, T0 + 1000, random.Random(1))
self.assertEqual(p["bank"]["woodcutting_potion_1"], 3)
@ -1818,7 +1818,7 @@ class StationAndStorageTest(unittest.TestCase):
self.assertEqual(game.action_time(self.world, self.bob, labour), bare)
def test_stations_move_out_of_the_tool_slot(self):
self.world["version"] = game.WORLD_VERSION - 1
self.world["version"] = 22
self.bob["equip"]["tool_smithing"] = "oak_anvil_stand"
del self.bob["equip"]["station_smithing"]
game.migrate(self.world, T0)
@ -1847,3 +1847,108 @@ class StationAndStorageTest(unittest.TestCase):
self.assertEqual(app.Handler.equip(handler, self.bob, "wooden_strongbox", None), "Bank too full for that")
self.assertEqual(self.bob["equip"]["storage"], "oak_strongbox")
self.assertEqual(self.bob["bank"]["wooden_strongbox"], 1)
class FirekeepingTest(unittest.TestCase):
def setUp(self):
self.world = world_with(("kai", "commoner"), ("sam", "commoner"))
self.kai, self.sam = self.world["players"]["kai"], self.world["players"]["sam"]
self.fires = self.world["city"]["fires"]
def tend(self, player, fire, log, at, seconds):
self.assertIsNone(game.start_action(self.world, player, f"tend_{fire}_{log}", at))
return game.process_player(self.world, player, at + seconds, random.Random(1))
def test_tending_feeds_only_that_fire_and_pays_by_the_minute(self):
self.kai["bank"] = {"logs": 3}
self.tend(self.kai, "forge", "logs", T0, 95)
level = game.skill_level(self.kai, "firekeeping")
self.assertEqual(self.kai["bank"].get("logs", 0), 0)
self.assertEqual(game.heat(self.world, "forge"), 0)
self.assertEqual(game.heat(self.world, "kitchen"), -1)
self.assertAlmostEqual(self.kai["xp"]["firekeeping"], 3 * game.TEND_XP[0] * game.fire_minutes(level))
def test_better_logs_need_the_level(self):
self.kai["bank"] = {"maple_logs": 3}
self.assertEqual(game.start_action(self.world, self.kai, "tend_forge_maple_logs", T0), "Needs level 45")
def test_a_full_fire_stops_tending_and_pays_nothing(self):
self.kai["bank"] = {"logs": 100}
summary = self.tend(self.kai, "forge", "logs", T0, 3000)
self.assertEqual(summary["stopped"], "The forge is full")
left = sum(chunk["left"] for chunk in self.fires["forge"]["fuel"])
self.assertGreater(left + game.fire_minutes(1) * 60, game.fire_bank(1) * 60) # no room for another log
xp = self.kai["xp"]["firekeeping"]
self.assertEqual(game.start_action(self.world, self.kai, "tend_forge_logs", T0 + 3000), "The forge is full")
self.assertEqual(self.kai["xp"]["firekeeping"], xp)
def test_a_better_log_pushes_cheaper_fuel_out_but_not_the_reverse(self):
bank = game.fire_bank(15) * 60
self.fires["kiln"].update(fuel=[{"tier": 0, "left": bank, "by": "sam"}], at=T0) # full of plain logs
self.kai["xp"]["firekeeping"] = game.XP_TABLE[15]
self.kai["bank"] = {"oak_logs": 1, "logs": 1}
self.assertEqual(game.start_action(self.world, self.kai, "tend_kiln_logs", T0), "The kiln is full")
self.assertIsNone(game.start_action(self.world, self.kai, "tend_kiln_oak_logs", T0)) # a better log still fits
game.feed_fire(self.world, self.kai, "kiln", 1)
self.assertEqual(game.heat(self.world, "kiln"), 1)
self.assertAlmostEqual(sum(chunk["left"] for chunk in self.fires["kiln"]["fuel"]), bank)
self.assertEqual(game.start_action(self.world, self.kai, "tend_kiln_logs", T0), "The kiln is full")
def test_fuel_burns_hottest_first_on_game_time(self):
fire = self.fires["still"]
fire["fuel"] = [{"tier": 0, "left": 600, "by": "sam"}, {"tier": 2, "left": 300, "by": "kai"}]
fire["at"] = T0
game.burn_fires(self.world, T0 + 200)
self.assertEqual(game.heat(self.world, "still"), 2)
game.burn_fires(self.world, T0 + 400)
self.assertEqual(game.heat(self.world, "still"), 0)
self.assertAlmostEqual(fire["fuel"][0]["left"], 500)
game.burn_fires(self.world, T0 + 100) # never backwards
self.assertEqual(fire["at"], T0 + 400)
def test_a_hot_forge_pays_the_log_and_a_coal_but_never_the_last(self):
steel, copper = game.ACTION_BY_ID["smith_steel_bar"], game.ACTION_BY_ID["smith_copper_bar"]
self.assertEqual(game.fire_inputs(self.world, steel), {"iron_ore": 1, "coal": 2, "willow_logs": 1})
self.fires["forge"]["fuel"] = [{"tier": 1, "left": 600, "by": "kai"}]
self.assertEqual(game.fire_inputs(self.world, steel), {"iron_ore": 1, "coal": 2, "willow_logs": 1}) # too cool
self.assertEqual(game.fire_inputs(self.world, copper), {"copper_ore": 1})
self.fires["forge"]["fuel"] = [{"tier": 3, "left": 600, "by": "kai"}]
self.assertEqual(game.fire_inputs(self.world, steel), {"iron_ore": 1, "coal": 1})
self.assertEqual(game.fire_inputs(self.world, game.ACTION_BY_ID["smith_mithril_bar"]), {"mithril_ore": 1, "coal": 2})
def test_a_potion_brewed_from_logs_keeps_its_ingredient_log(self):
brew = game.ACTION_BY_ID["brew_woodcutting_potion_1"]
self.fires["still"]["fuel"] = [{"tier": 0, "left": 600, "by": "kai"}]
self.assertEqual(game.fire_inputs(self.world, brew), {"guam_leaf": 1, "logs": 1})
def test_warmth_goes_to_whoever_fed_the_fire(self):
self.fires["kitchen"]["fuel"] = [{"tier": 0, "left": 6000, "by": "kai"}]
self.fires["kitchen"]["at"] = T0
self.sam["bank"] = {"raw_shrimp": 4}
game.start_action(self.world, self.sam, "cook_shrimp", T0)
game.process_player(self.world, self.sam, T0 + 85, random.Random(1))
self.assertEqual(self.sam["bank"]["cooked_shrimp"], 4)
self.assertNotIn("logs", self.sam["bank"])
self.assertAlmostEqual(self.kai["xp"]["firekeeping"], 4 * 6 * game.WARMTH)
self.assertAlmostEqual(self.kai["away"]["xp"]["firekeeping"], 4 * 6 * game.WARMTH)
self.assertEqual(self.fires["kitchen"]["saved"], 4)
self.assertEqual(self.sam["xp"]["firekeeping"], 0)
def test_catch_up_runs_everyone_and_keeps_what_they_missed(self):
game.start_action(self.world, self.sam, "mine_stone", T0)
self.assertTrue(game.catch_up(self.world, T0 + 100, random.Random(1)))
game.catch_up(self.world, T0 + 200, random.Random(1))
self.assertEqual(self.sam["away"]["done"], 10)
summary = game.take_summary(self.sam, game.process_player(self.world, self.sam, T0 + 220, random.Random(1)))
self.assertEqual(summary["done"], 11)
self.assertEqual(summary["items"], {"stone": 11})
self.assertNotIn("away", self.sam)
def test_old_worlds_get_their_fires(self):
del self.world["city"]["fires"]
del self.kai["xp"]["firekeeping"]
self.world["version"] = 23
game.migrate(self.world, T0)
self.assertEqual(set(self.world["city"]["fires"]), set(game.FIRES))
self.assertEqual(self.kai["xp"]["firekeeping"], 0)
self.assertIn("tool_firekeeping", self.kai["equip"])