Combat rework: packs, abilities, rest, health potions, group bosses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-24 09:49:21 +01:00
parent 472609ac70
commit 57d11e30da
10 changed files with 1562 additions and 380 deletions

View file

@ -170,6 +170,7 @@ class ActionTest(unittest.TestCase):
world = world_with(("wil", "commoner"))
p = world["players"]["wil"]
p["bank"] = {"bread": 500}
p["equip"].update(weapon="copper_sword", helmet="copper_helmet", body="copper_body", legs="copper_legs")
game.start_action(world, p, "fight_wolves", T0)
summary = game.process_player(world, p, T0 + 3600, random.Random(5))
self.assertGreater(summary["done"], 30) # a fight lasts as long as it takes
@ -505,31 +506,58 @@ class CombatTest(unittest.TestCase):
self.assertEqual(p["bank"].get("bread", 0), 100 - summary["eaten"]["bread"])
self.assertGreaterEqual(p["hp"], 1)
def test_out_of_food_means_retreat_and_nothing_lost(self):
def test_three_knockouts_in_a_row_stop_you_and_nothing_is_lost(self):
world, p = self.fighter(level=10, weapon="copper_sword")
p["bank"] = {"stone": 10}
game.start_action(world, p, "fight_scouts", T0)
summary = game.process_player(world, p, T0 + 6 * 3600, random.Random(1))
self.assertIsNone(p["action"])
self.assertTrue(summary["stopped"].startswith("Retreated"))
self.assertEqual(summary["stopped"], "Knocked out 3 times in a row")
self.assertEqual(summary["losses"], 3)
self.assertEqual(p["equip"]["weapon"], "copper_sword")
self.assertEqual(p["bank"]["stone"], 10)
self.assertLess(p["hp"], game.max_hp(p) * game.TOO_HURT)
self.assertEqual(game.start_action(world, p, "fight_scouts", T0 + 6 * 3600), "Too hurt to fight. Rest, or bring food")
self.assertAlmostEqual(p["hp"], game.max_hp(p) * game.KNOCKED_OUT_HP)
self.assertIsNone(game.start_action(world, p, "fight_scouts", T0 + 6 * 3600)) # you can always go again
def test_eating_holds_back_your_swings(self):
world, p = self.fighter(level=15)
p["bank"] = {"cooked_shrimp": 100}
p["hp"] = 1
fight = game.simulate_fight(world, p, game.ACTION_BY_ID["fight_clay_golems"], random.Random(3))
mine = [event for event in fight["events"] if event[1] == p["id"]]
bite = next(i for i, event in enumerate(mine) if event[2] == "eat")
swing = next(event for event in mine[bite:] if event[2] != "eat")
self.assertGreaterEqual(swing[0] - mine[bite][0], game.EAT_DELAY)
scouts = game.ACTION_BY_ID["fight_scouts"]
eating = game.fight_odds(p, scouts)["kill_time"]
def test_a_win_resets_the_knockouts(self):
world, p = self.fighter(level=1, weapon="copper_sword")
game.start_action(world, p, "fight_wolves", T0)
summary = game.process_player(world, p, T0 + 3600, random.Random(1))
self.assertGreater(summary["losses"], 2) # knocked out now and then, never three in a row
self.assertEqual(p["action"]["id"], "fight_wolves")
self.assertGreater(summary["wins"], 20)
def test_food_shortens_the_rest_between_fights(self):
world, p = self.fighter(level=30)
top = game.max_hp(p)
p["bank"] = {}
self.assertGreater(eating, game.fight_odds(p, scouts)["kill_time"])
resting, eaten = game.rest_plan(world, p, top / 2)
self.assertEqual(eaten, {})
self.assertAlmostEqual(resting, top / 2 / (top * game.REST_RATE))
p["bank"] = {"bread": 100}
bread, eaten = game.rest_plan(world, p, top / 2)
self.assertLess(bread, resting)
self.assertEqual(eaten, {"bread": 10}) # 80 HP back in 8 HP bites
p["bank"] = {"cooked_salmon": 100}
salmon, eaten = game.rest_plan(world, p, top / 2)
self.assertLess(salmon, bread) # better food, shorter rest
self.assertEqual(eaten, {"cooked_salmon": 3})
self.assertEqual(game.rest_plan(world, p, top)[0], game.RESPAWN) # at full: a breather
p["xp"]["combat"] = game.XP_TABLE[99]
p["bank"] = {"bread": 100}
self.assertEqual(game.rest_plan(world, p, 100)[1], {}) # a bite of bread is no faster than resting
def test_food_is_eaten_resting_not_fighting(self):
world, p = self.fighter(level=15, weapon="iron_sword", helmet="iron_helmet", body="iron_body", legs="iron_legs")
p["bank"] = {"cooked_trout": 100}
p["hp"] = 10
fight = game.simulate_fight(world, p, game.ACTION_BY_ID["fight_clay_golems"], random.Random(3))
mine = game.member_of(fight, p["id"])
self.assertGreater(sum(mine["eaten"].values()), 0)
self.assertGreater(fight["rest"], game.RESPAWN)
self.assertEqual(mine["hp_in"], game.max_hp(p)) # every fight starts rested
self.assertTrue(all(e[0] >= fight["rest"] for e in fight["events"]))
self.assertFalse(any(e[3] == "eat" for e in fight["events"]))
def test_rest_heals(self):
world, p = self.fighter()
@ -547,20 +575,24 @@ class CombatTest(unittest.TestCase):
self.assertLess(hurt(veteran), hurt(bare))
self.assertGreater(game.fight_odds(veteran, scouts)["hit"], game.fight_odds(bare, scouts)["hit"])
def test_a_fight_is_played_out_swing_by_swing(self):
world, p = self.fighter(level=10, weapon="iron_sword")
def test_a_fight_is_played_out_slice_by_slice(self):
world, p = self.fighter(level=20, weapon="iron_sword", helmet="iron_helmet", body="iron_body", legs="iron_legs")
p["bank"] = {"bread": 50}
game.start_action(world, p, "fight_scouts", T0)
game.process_player(world, p, T0 + 1, random.Random(2))
fight = p["action"]["fight"] # simulated, waiting to finish, for the page to replay
self.assertEqual(fight["result"], "won")
self.assertTrue(all(e[1] in (p["id"], "foe") and e[2] in ("hit", "crit", "miss", "eat", "wind") for e in fight["events"]))
self.assertEqual(fight["events"][-1][5], 0) # the last swing finished it
kinds = {"hit", "crit", "tick", "miss", "block", "absorb", "heal", "mend", "cast", "use", "stop", "cc", "free",
"die", "enrage", "wind", "down", "left"}
self.assertTrue(all(e[3] in kinds for e in fight["events"]))
self.assertTrue(all(e[1] in (p["id"], None) or e[1].startswith("e") for e in fight["events"]))
dead = [e[2] for e in fight["events"] if e[3] == "die"]
self.assertEqual(sorted(dead), sorted(f"e{i}" for i in range(len(fight["pack"])))) # the last of them finished it
self.assertAlmostEqual(fight["duration"], fight["end"], places=1) # rewards land on the kill
summary = game.process_player(world, p, T0 + fight["duration"] + 0.01, random.Random(2))
self.assertEqual(summary["wins"], 1)
self.assertAlmostEqual(p["hp"], game.member_of(fight, p["id"])["hp"], delta=0.1)
self.assertAlmostEqual(p["action"]["start"], T0 + fight["duration"] + game.RESPAWN) # then a short pause
self.assertAlmostEqual(p["action"]["start"], T0 + fight["duration"]) # straight on to the next rest
def test_outclassing_an_enemy_kills_faster(self):
kills = {}
@ -570,15 +602,17 @@ class CombatTest(unittest.TestCase):
kills[level] = game.process_player(world, p, T0 + 600, random.Random(1))["wins"]
self.assertGreater(kills[90], kills[20] * 1.3)
def test_each_enemy_starts_with_both_swing_timers_empty(self):
def test_each_fight_starts_with_every_swing_timer_empty(self):
world, p = self.fighter(level=60, weapon="rune_sword")
game.start_action(world, p, "fight_wolves", T0)
game.process_player(world, p, T0 + 0.1, random.Random(1))
first = p["action"]["fight"]
game.process_player(world, p, T0 + first["duration"] + 0.01, random.Random(1))
self.assertAlmostEqual(p["action"]["start"], T0 + first["duration"] + game.RESPAWN) # the pause starts at the kill
self.assertAlmostEqual(p["action"]["start"], T0 + first["duration"]) # the rest starts at the kill
second = p["action"]["fight"]
self.assertEqual(next(e[0] for e in second["events"] if e[1] == p["id"]), 2.4) # a full swing, from scratch
swing = next(e[0] for e in second["events"] if e[1] == p["id"] and e[6] in game.DAMAGE_TYPES)
self.assertAlmostEqual(swing, second["rest"] + 2.5) # a full swing from scratch, to the slice
def test_preferred_food_is_eaten_first(self):
world, p = self.fighter()
p["bank"] = {"bread": 5, "cooked_salmon": 5}
@ -587,6 +621,99 @@ class CombatTest(unittest.TestCase):
self.assertEqual(game.edible(p), ["cooked_salmon", "bread"])
class AbilityTest(unittest.TestCase):
def fighter(self, cls, level, tier=1, **equip):
world = world_with(("ash", cls))
p = world["players"]["ash"]
p["xp"]["combat"] = game.XP_TABLE[level]
p["equip"].update(equip)
p["hp"] = game.max_hp(p)
return world, p
def test_the_weapon_gives_four_abilities_and_the_trade_one(self):
known = lambda style, cls, level, shield=False: game.combat.known_abilities(style, cls, level, shield)
self.assertEqual(set(known("hammer", "miner", 1)), {"crushing_blow", "find_the_seam"})
self.assertEqual(set(known("hammer", "miner", 45)), {"crushing_blow", "concuss", "earthshaker", "unstoppable",
"find_the_seam"})
self.assertEqual(set(known("bow", "miner", 15)), {"aimed_shot", "pinning_shot", "find_the_seam"}) # any weapon
self.assertEqual(set(known("sword", "builder", 45)), {"challenge", "sweeping_cut", "barricade"}) # no shield
self.assertEqual(set(known("sword", "builder", 45, True)), {"shield_bash", "challenge", "sweeping_cut",
"shield_wall", "barricade"})
self.assertEqual(known("staff", "homesteader", 30), ["mend", "ward", "hearty_meal", "bolt"]) # auto's order
self.assertEqual(game.combat.role("sword", True), "tank")
self.assertEqual(game.combat.role("sword", False), "damage")
self.assertEqual(game.combat.role("staff", False), "healer")
def test_abilities_are_used_in_the_fight(self):
world, p = self.fighter("ranger", 30, weapon="willow_bow")
fight = game.simulate_fight(world, p, game.ACTION_BY_ID["fight_clay_golems"], random.Random(1))
used = {e[6] for e in fight["events"] if e[1] == p["id"] and e[6] in game.combat.ABILITIES}
self.assertIn("aimed_shot", used)
casts = [e for e in fight["events"] if e[1] == p["id"] and e[3] == "cast"]
self.assertTrue(casts)
def test_casters_get_stopped(self):
world, p = self.fighter("ranger", 30, weapon="willow_bow")
harpies = game.ACTION_BY_ID["fight_harpies"]
stops = 0
for seed in range(20):
fight = game.simulate_fight(world, p, harpies, random.Random(seed))
stops += sum(1 for e in fight["events"] if e[3] == "stop" and e[6] == "hex_bolt")
self.assertGreater(stops, 0) # Pinning Shot stops a windcaster's hex
def test_a_health_potion_once_a_fight_when_low(self):
world, p = self.fighter("miner", 10, weapon="copper_warhammer")
p["bank"] = {"health_potion_1": 5, "health_potion_2": 5}
self.assertEqual(game.health_potion(p), "health_potion_2")
game.start_action(world, p, "fight_scouts", T0)
summary = game.process_player(world, p, T0 + 3600, random.Random(1))
drunk = summary["used"].get("health_potion_2", 0)
self.assertGreater(drunk, 0)
self.assertLessEqual(drunk, summary["done"])
self.assertEqual(p["bank"].get("health_potion_2", 0), 5 - drunk)
fight = game.simulate_fight(world, p, game.ACTION_BY_ID["fight_scouts"], random.Random(3))
self.assertLessEqual(sum(1 for e in fight["events"] if e[3] == "heal" and e[6] == "potion"), 1)
def test_health_potions_are_brewed(self):
brew = game.ACTION_BY_ID["brew_health_potion_3"]
self.assertEqual((brew["skill"], brew["level"]), ("herblore", 30))
self.assertEqual(brew["outputs"], {"health_potion_3": 1})
self.assertIn("tarromin", brew["inputs"])
self.assertIn("cooked_salmon", brew["inputs"])
self.assertEqual(game.ITEMS["health_potion_3"]["drink"], game.HEALTH_HEAL[2])
self.assertNotIn("health_potion_3", game.edible({"bank": {"health_potion_3": 1}, "food": None}))
def test_volley_spends_arrows(self):
world, p = self.fighter("ranger", 45, weapon="maple_bow")
p["bank"] = {"arrows": 100}
game.start_action(world, p, "fight_goblins", T0)
summary = game.process_player(world, p, T0 + 1800, random.Random(2))
shot = summary["used"].get("arrows", 0)
self.assertGreater(shot, 0)
self.assertEqual(shot % 5, 0)
self.assertEqual(p["bank"].get("arrows", 0), 100 - shot)
def test_a_party_rests_as_long_as_its_slowest(self):
world = world_with(("wil", "builder"), ("kay", "builder"))
wil, kay = world["players"]["wil"], world["players"]["kay"]
for p in (wil, kay):
p["xp"]["combat"] = game.XP_TABLE[20]
wil["hp"], kay["hp"] = game.max_hp(wil), 10
fight = game.draw_fight(world, [wil, kay], game.ACTION_BY_ID["fight_scouts"], random.Random(1))
self.assertAlmostEqual(fight["rest"], game.rest_plan(world, kay)[0])
def test_fights_in_progress_are_drawn_again_after_the_update(self):
world = world_with(("wil", "builder"))
wil = world["players"]["wil"]
game.start_action(world, wil, "fight_wolves", T0)
wil["action"]["fight"] = {"old": True}
world["parties"]["1"] = {"id": "1", "action": "fight_wolves", "members": [], "start": T0, "fight": {"old": True}}
world["version"] = 25
game.migrate(world, T0)
self.assertNotIn("fight", wil["action"])
self.assertIsNone(world["parties"]["1"]["fight"])
class NewSkillsTest(unittest.TestCase):
def test_tools_speed_up_gathering(self):
world = world_with(("pia", "commoner"))
@ -789,14 +916,18 @@ class TierTest(unittest.TestCase):
self.assertEqual(game.build_cost("walls", 5)["steel_bar"], 180)
def test_dragons_drop_their_areas_reagents(self):
world = world_with(("wil", "commoner"))
world = world_with(("wil", "commoner"), ("kay", "commoner"))
for p in world["players"].values():
p["xp"]["combat"] = game.XP_TABLE[80]
p["equip"].update(weapon="rune_sword", shield="rune_shield", helmet="rune_helmet", body="rune_body",
legs="rune_legs")
p["hp"] = game.max_hp(p)
p["bank"] = {"cooked_swordfish": 200}
p["last_active"] = T0 + 3600 * 2
p = world["players"]["wil"]
p["xp"]["combat"] = game.XP_TABLE[80]
p["equip"].update(weapon="rune_sword", shield="rune_shield", helmet="rune_helmet", body="rune_body", legs="rune_legs")
p["hp"] = game.max_hp(p)
p["bank"] = {"cooked_swordfish": 200}
game.start_action(world, p, "fight_green_dragon", T0)
game.process_player(world, p, T0 + 3600 * 2, random.Random(3))
game.start_action(world, p, "fight_green_dragon", T0) # a group fight
game.join_fight(world, world["players"]["kay"], "wil", T0)
game.catch_up(world, T0 + 3600 * 2, random.Random(3))
self.assertGreater(sum(p["bank"].get(game.reagent(weight, 3), 0) for weight in game.WEIGHTS), 30)
@ -843,27 +974,26 @@ class PrayerTest(unittest.TestCase):
self.assertTrue(summary["prayer_out"])
self.assertEqual(p["xp"]["prayer"], game.XP_TABLE[10] + 4 * game.PRAYER_XP_PER_POINT)
def test_second_wind_saves_one_retreat(self):
def test_second_wind_saves_one_knockout(self):
world, p = self.priest(level=40, combat=10)
game.set_prayers(p, ["second_wind"])
p["pp"] = 1000
game.start_action(world, p, "fight_scouts", T0)
summary = game.process_player(world, p, T0 + 6 * 3600, random.Random(1))
self.assertEqual(summary["second_wind"], 1)
self.assertTrue(summary["stopped"].startswith("Retreated"))
self.assertEqual(summary["stopped"], "Knocked out 3 times in a row")
def test_rapid_heal_mends_during_fights(self):
world, p = self.priest(level=10, combat=10)
scouts = game.ACTION_BY_ID["fight_scouts"]
p["pp"], p["hp"] = 1000, 50
p["pp"] = 1000
p["equip"].update(weapon="copper_sword", helmet="copper_helmet", body="copper_body", legs="copper_legs")
plain = game.simulate_fight(world, p, scouts, random.Random(2))
game.set_prayers(p, ["rapid_heal"])
mended = game.simulate_fight(world, p, scouts, random.Random(2))
self.assertEqual(plain["end"], mended["end"]) # the same fight, only the mending differs
swings = sum(1 for e in plain["events"] if e[1] == p["id"])
top = game.max_hp(p)
plain, mended = game.member_of(plain, p["id"]), game.member_of(mended, p["id"])
self.assertAlmostEqual(mended["hp"] - plain["hp"], swings * game.MEND * top * plain["interval"], places=6)
self.assertEqual([e[:4] for e in plain["events"] if e[1] == p["id"]][:5],
[e[:4] for e in mended["events"] if e[1] == p["id"]][:5]) # the same fight
self.assertGreater(game.member_of(mended, p["id"])["hp"], game.member_of(plain, p["id"])["hp"])
self.assertLess(game.fight_odds(p, scouts)["damage"], game.fight_odds({**p, "prayers": []}, scouts)["damage"])
def test_offerings_ward_the_city_until_the_raid(self):
@ -1452,11 +1582,27 @@ class WeaponStyleTest(unittest.TestCase):
self.assertLess(hurt(cloth, shaper), hurt(plate, shaper))
self.assertLess(hurt(plate, brute), hurt(cloth, brute))
def test_each_kill_is_one_of_the_variants(self):
def test_each_fight_is_a_pack_of_the_variants(self):
world, p = self.armed("miner", "iron_warhammer", level=30)
seen = {game.simulate_fight(world, p, game.ACTION_BY_ID["fight_goblins"], random.Random(seed))["enemy"]
for seed in range(60)}
self.assertEqual(seen, {"Goblin brute", "Goblin rogue", "Goblin warlock"})
goblins = game.ACTION_BY_ID["fight_goblins"]
packs = [game.simulate_fight(world, p, goblins, random.Random(seed))["pack"] for seed in range(60)]
self.assertEqual({foe["name"] for pack in packs for foe in pack}, {"Goblin brute", "Goblin rogue", "Goblin warlock"})
self.assertEqual({len(pack) for pack in packs}, {1, 2})
for pack in packs:
share = game.OUTNUMBERED[0] if len(pack) == 2 else 1
self.assertTrue(all(foe["share"] == share for foe in pack)) # two of them, each weaker
scouts = game.ACTION_BY_ID["fight_scouts"]
self.assertEqual({len(game.draw_pack(scouts, 3, random.Random(seed))) for seed in range(60)}, {3, 4})
def test_a_boss_comes_alone(self):
ogre = game.ACTION_BY_ID["fight_ruins"]
foe = ogre["enemy"]["variants"][0]
pair, four = game.draw_pack(ogre, 2, random.Random(1)), game.draw_pack(ogre, 4, random.Random(1))
self.assertEqual((len(pair), len(four)), (1, 1))
self.assertEqual(pair[0]["hp"], round(foe["hp"] * game.BOSS_HP * 2)) # tougher the more of you there are
self.assertEqual(four[0]["hp"], round(foe["hp"] * game.BOSS_HP * 4))
self.assertEqual(pair[0]["max_hit"], round(foe["max_hit"] * game.BOSS_HITS[0]))
self.assertGreater(four[0]["max_hit"], pair[0]["max_hit"])
def test_shield_smiths_stop_after_the_move(self):
world = world_with(("kay", "miner"))
@ -2059,7 +2205,7 @@ class PartyTest(unittest.TestCase):
for p in world["players"].values():
p["xp"]["combat"] = game.XP_TABLE[level]
p["hp"] = game.max_hp(p)
p["equip"]["weapon"] = "iron_sword"
p["equip"].update(weapon="iron_sword", helmet="iron_helmet", body="iron_body", legs="iron_legs")
p["bank"] = {"bread": 300}
return world, world["players"]["wil"], world["players"]["kay"]
@ -2096,14 +2242,12 @@ class PartyTest(unittest.TestCase):
self.assertEqual(together["done"], kay["away"]["done"])
self.assertEqual(together["with"], {"Kay": together["done"]})
self.assertEqual(wil["xp"]["combat"], kay["xp"]["combat"]) # the same kills, the full XP each
low, high = game.ACTION_BY_ID["fight_scouts"]["gold"]
self.assertTrue(together["wins"] * low <= wil["gold"] + kay["gold"] <= together["wins"] * high)
self.assertLessEqual(abs(wil["gold"] - kay["gold"]), together["wins"]) # split evenly, odd coins aside
self.assertEqual(wil["bank"].get("bones", 0) + kay["bank"].get("bones", 0), together["wins"]) # one each kill
self.assertGreater(wil["gold"] + kay["gold"], 0)
self.assertLessEqual(abs(wil["gold"] - kay["gold"]), together["wins"] * 4) # split evenly, odd coins aside
solo, alone = self.pair()[0:2]
game.start_action(solo, alone, "fight_scouts", T0)
self.online(solo, T0 + 3600, random.Random(2))
self.assertGreater(together["done"], alone["away"]["done"] * 1.5)
self.assertGreater(together["xp"]["combat"], alone["away"]["xp"]["combat"] * 1.3)
def test_leaving_lets_the_other_carry_on(self):
world, wil, kay = self.pair()
@ -2117,7 +2261,7 @@ class PartyTest(unittest.TestCase):
self.assertNotIn("party", wil["action"])
left = game.member_of(wil["action"]["fight"], "kay") # the fight goes on without her
self.assertEqual(left["result"], "left")
self.online(world, T0 + 600, rng)
self.online(world, T0 + 1200, rng)
self.assertGreater(wil["away"]["wins"], 3)
def test_away_too_long_steps_out(self):
@ -2134,17 +2278,42 @@ class PartyTest(unittest.TestCase):
self.assertNotIn("party", kay["action"])
self.assertGreater(wil["away"]["done"], kay["away"]["done"]) # wil carried on alone for the last hour
def test_a_retreat_leaves_the_rest_fighting(self):
def test_knocked_out_in_a_won_fight_still_shares_the_win(self):
world, wil, kay = self.pair()
kay["bank"] = {}
kay["hp"] = game.max_hp(kay) * 0.35
game.start_action(world, wil, "fight_scouts", T0)
self.assertIsNone(game.join_fight(world, kay, "wil", T0))
self.online(world, T0 + 600, random.Random(5))
self.assertIsNone(kay["action"])
self.assertTrue(kay["stopped"].startswith("Retreated"))
self.assertEqual(wil["action"]["id"], "fight_scouts")
self.assertEqual(world["parties"], {})
self.online(world, T0 + 0.1, random.Random(0))
kay["hp"] = 1 # tags in as she is
self.assertIsNone(game.join_fight(world, kay, "wil", T0 + 0.1))
fight = game.current_fight(world, wil)
mine = game.member_of(fight, "kay")
self.assertTrue(mine["down"])
self.assertEqual((fight["result"], mine["result"]), ("won", "won"))
self.online(world, T0 + fight["duration"] + 0.01, random.Random(5))
self.assertEqual(kay["away"]["wins"], 1)
self.assertAlmostEqual(kay["hp"], game.max_hp(kay) * game.KNOCKED_OUT_HP)
self.assertEqual(kay["action"]["id"], "fight_scouts")
def test_a_boss_needs_a_group(self):
world, wil, kay = self.pair(level=30)
for p in (wil, kay):
p["equip"].update(weapon="steel_sword", shield="steel_shield", helmet="steel_helmet", body="steel_body",
legs="steel_legs")
self.assertIsNone(game.start_action(world, wil, "fight_ruins", T0))
self.assertIn("Wil is looking for a group", world["ledger"][-1]["text"])
self.online(world, T0 + 3600, random.Random(1))
self.assertNotIn("fight", wil["action"]) # alone, nothing happens
self.assertEqual(wil["away"]["done"], 0) if "away" in wil else None
self.assertEqual(wil["action"]["start"], T0 + 3600)
self.assertIsNone(game.join_fight(world, kay, "wil", T0 + 3600))
self.online(world, T0 + 7200, random.Random(1))
self.assertGreater(wil["away"]["wins"], 0)
self.assertEqual(wil["away"]["wins"], kay["away"]["wins"])
self.assertIsNone(game.start_action(world, kay, "mine_stone", T0 + 7200))
self.online(world, T0 + 9000, random.Random(1))
wins = wil["away"]["wins"]
self.online(world, T0 + 12600, random.Random(1))
self.assertEqual(wil["away"]["wins"], wins) # back to waiting
self.assertEqual(wil["action"]["id"], "fight_ruins")
def test_who_can_join(self):
world, wil, kay = self.pair()
@ -2162,7 +2331,8 @@ class PartyTest(unittest.TestCase):
def test_bosses_give_everyone_their_own_loot(self):
world, wil, kay = self.pair(level=30)
for p in (wil, kay):
p["equip"].update(weapon="steel_sword", helmet="steel_helmet", body="steel_body", legs="steel_legs")
p["equip"].update(weapon="steel_sword", shield="steel_shield", helmet="steel_helmet", body="steel_body",
legs="steel_legs")
p["bank"] = {"cooked_salmon": 5000}
game.start_action(world, wil, "fight_ruins", T0)
self.assertIsNone(game.join_fight(world, kay, "wil", T0))