Combat rework: damage types, races with variants, armour weights, tailoring
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a35bcd6512
commit
ba7556de4d
7 changed files with 715 additions and 254 deletions
|
|
@ -730,9 +730,13 @@ class TierTest(unittest.TestCase):
|
|||
self.assertIn(f"{key}_{piece}", game.ITEMS)
|
||||
self.assertIn(f"smith_{key}_{piece}", game.ACTION_BY_ID)
|
||||
self.assertIn(f"make_{key}_shield", game.ACTION_BY_ID) # builders make shields
|
||||
for *_, prefix, _label, _level, _armour in game.LEATHERS:
|
||||
for *_, prefix, _label, _level in game.LEATHERS:
|
||||
for piece in ("cap", "legs", "body"):
|
||||
self.assertIn(f"{prefix}_{piece}", game.ITEMS)
|
||||
for *_, prefix, _label in game.CLOTHS:
|
||||
for piece in ("hood", "legs", "body"):
|
||||
self.assertIn(f"{prefix}_{piece}", game.ITEMS)
|
||||
self.assertIn(f"sew_{prefix}_{piece}", game.ACTION_BY_ID)
|
||||
self.assertEqual(game.ITEMS["rune_body"]["plural"], "Rune cuirasses")
|
||||
|
||||
def test_gear_needs_levels_to_wear(self):
|
||||
|
|
@ -758,7 +762,7 @@ class TierTest(unittest.TestCase):
|
|||
self.assertEqual(game.build_cost("walls", 5)["mithril_brackets"], 60)
|
||||
self.assertEqual(game.build_cost("walls", 5)["steel_bar"], 180)
|
||||
|
||||
def test_dragons_drop_dragonhide(self):
|
||||
def test_dragons_drop_their_areas_reagents(self):
|
||||
world = world_with(("wil", "commoner"))
|
||||
p = world["players"]["wil"]
|
||||
p["xp"]["combat"] = game.XP_TABLE[80]
|
||||
|
|
@ -767,7 +771,7 @@ class TierTest(unittest.TestCase):
|
|||
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))
|
||||
self.assertGreater(p["bank"].get("green_dragonhide", 0), 30)
|
||||
self.assertGreater(sum(p["bank"].get(game.reagent(weight, 3), 0) for weight in game.WEIGHTS), 30)
|
||||
|
||||
|
||||
|
||||
|
|
@ -1345,28 +1349,30 @@ class WeaponStyleTest(unittest.TestCase):
|
|||
p["equip"].update(weapon=weapon, shield=shield, body="iron_body", helmet="iron_helmet", legs="iron_legs")
|
||||
return world, p
|
||||
|
||||
def test_each_trade_fights_its_own_monster_best(self):
|
||||
def test_no_trade_is_funnelled_into_one_race(self):
|
||||
# Every race mixes its weaknesses across its variants, so no weapon has one easy target.
|
||||
styles = {"miner": "iron_warhammer", "builder": "iron_sword", "homesteader": "oak_staff", "ranger": "oak_bow"}
|
||||
prey = {"miner": "fight_clay_golem", "builder": "fight_cave_troll", "homesteader": "fight_will_o_wisp",
|
||||
"ranger": "fight_giant_bat"}
|
||||
races = ["fight_clay_golems", "fight_cave_trolls", "fight_restless_dead", "fight_harpies"]
|
||||
for cls, weapon in styles.items():
|
||||
_world, p = self.armed(cls, weapon, "iron_shield" if cls == "builder" else None)
|
||||
times = {target: game.fight_odds(p, game.ACTION_BY_ID[target])["kill_time"] for target in prey.values()}
|
||||
self.assertEqual(min(times, key=times.get), prey[cls], cls)
|
||||
times = [game.fight_odds(p, game.ACTION_BY_ID[race])["kill_time"] for race in races]
|
||||
self.assertLess(max(times) / min(times), 1.45, cls)
|
||||
|
||||
def test_style_traits(self):
|
||||
_w, swordsman = self.armed("builder", "iron_sword", "iron_shield")
|
||||
_w, smith = self.armed("miner", "iron_warhammer")
|
||||
_w, archer = self.armed("ranger", "oak_bow")
|
||||
spec = lambda p: game.fighter(p, game.NO_BLESSING, {}, game.ACTION_BY_ID["fight_clay_golem"]["enemy"])
|
||||
brute = game.ACTION_BY_ID["fight_clay_golems"]["enemy"]["variants"][0] # vulnerable to blunt, resists piercing
|
||||
spec = lambda p: game.fighter(p, game.NO_BLESSING, {}, brute)
|
||||
self.assertEqual(spec(swordsman)["block"], game.BLOCK_CHANCE)
|
||||
self.assertEqual(spec(smith)["crit"], 0.10)
|
||||
self.assertEqual(spec(smith)["weakness"], 1.5)
|
||||
self.assertEqual(spec(archer)["weakness"], 0.5)
|
||||
self.assertEqual(spec(smith)["affinity"], game.VULNERABLE)
|
||||
self.assertEqual(spec(archer)["affinity"], game.RESISTANT)
|
||||
self.assertEqual(spec(swordsman)["affinity"], 1)
|
||||
_w, other = self.armed("builder", "iron_warhammer")
|
||||
self.assertGreater(spec(smith)["max_hit"], spec(other)["max_hit"]) # the trade's own weapon hits harder
|
||||
world, archer = self.armed("ranger", "oak_bow")
|
||||
fight = game.simulate_fight(world, archer, game.ACTION_BY_ID["fight_giant_bat"], random.Random(1))
|
||||
fight = game.simulate_fight(world, archer, game.ACTION_BY_ID["fight_harpies"], random.Random(1))
|
||||
self.assertEqual(fight["events"][0][1], "you") # the bow strikes first
|
||||
|
||||
def test_two_handed_weapons_leave_no_room_for_a_shield(self):
|
||||
|
|
@ -1377,13 +1383,40 @@ class WeaponStyleTest(unittest.TestCase):
|
|||
|
||||
def test_weapons_past_copper_need_trophies(self):
|
||||
for action_id, trophy in (("smith_iron_warhammer", "clay_core"), ("smith_iron_sword", "cave_troll_hide"),
|
||||
("make_iron_shield", "cave_troll_hide"), ("brew_oak_staff", "wisp_essence"),
|
||||
("lw_oak_bow", "bat_sinew"), ("smith_rune_warhammer", "runic_core")):
|
||||
("make_iron_shield", "cave_troll_hide"), ("brew_oak_staff", "grave_dust"),
|
||||
("lw_oak_bow", "harpy_sinew"), ("smith_rune_warhammer", "magma_core")):
|
||||
self.assertIn(trophy, game.ACTION_BY_ID[action_id]["inputs"], action_id)
|
||||
for action_id in ("smith_copper_warhammer", "smith_copper_sword", "make_copper_shield",
|
||||
"brew_wooden_staff", "lw_wooden_bow"):
|
||||
self.assertFalse(any(item in game.TROPHIES for item in game.ACTION_BY_ID[action_id]["inputs"]), action_id)
|
||||
|
||||
def test_armour_past_copper_needs_its_areas_reagent(self):
|
||||
self.assertEqual(game.ACTION_BY_ID["smith_iron_body"]["inputs"], {"iron_bar": 5, "watchtower_plating": 3})
|
||||
self.assertEqual(game.ACTION_BY_ID["lw_hard_leather_cap"]["inputs"], {"hard_leather": 2, "watchtower_lacing": 1})
|
||||
self.assertEqual(game.ACTION_BY_ID["sew_moonweave_legs"]["inputs"], {"moonweave": 3, "ironhill_thread": 2})
|
||||
for action_id in ("smith_copper_body", "lw_cap", "sew_linen_body"):
|
||||
self.assertFalse(any(item in game.REAGENTS for item in game.ACTION_BY_ID[action_id]["inputs"]), action_id)
|
||||
harpies = game.ACTION_BY_ID["fight_harpies"]["drops"]
|
||||
self.assertIn(["watchtower_thread", 1, game.REAGENT_ODDS], harpies)
|
||||
|
||||
def test_heavy_stops_blows_light_stops_magic(self):
|
||||
_w, plate = self.armed("miner", "iron_warhammer")
|
||||
_w, cloth = self.armed("miner", "iron_warhammer")
|
||||
cloth["equip"].update(helmet="cotton_hood", body="cotton_body", legs="cotton_legs")
|
||||
self.assertGreater(game.armour(plate), game.armour(cloth))
|
||||
self.assertGreater(game.magic_ward(cloth), game.magic_ward(plate))
|
||||
shaper = next(v for v in game.ACTION_BY_ID["fight_clay_golems"]["enemy"]["variants"] if v["name"] == "Mud shaper")
|
||||
brute = game.ACTION_BY_ID["fight_clay_golems"]["enemy"]["variants"][0]
|
||||
hurt = lambda p, foe: game._variant_odds(p, foe, game.NO_BLESSING, {})["damage"]
|
||||
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):
|
||||
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"})
|
||||
|
||||
def test_shield_smiths_stop_after_the_move(self):
|
||||
world = world_with(("kay", "miner"))
|
||||
world["version"] = 13
|
||||
|
|
@ -1463,3 +1496,24 @@ class IconTest(unittest.TestCase):
|
|||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class CombatReworkMigrationTest(unittest.TestCase):
|
||||
def test_old_monsters_trophies_and_dragonhide_carry_over(self):
|
||||
world = world_with(("kay", "ranger"))
|
||||
world["version"] = 15
|
||||
p = world["players"]["kay"]
|
||||
del p["xp"]["tailoring"]
|
||||
p["bank"] = {"bat_sinew": 3, "harpy_sinew": 2, "green_dragonhide": 4, "red_dragon_leather": 1}
|
||||
p["equip"]["body"] = "blue_dhide_body"
|
||||
p["action"] = {"id": "fight_giant_bat", "start": T0}
|
||||
world["players"]["kay"] = p
|
||||
game.migrate(world, T0)
|
||||
self.assertEqual(p["bank"], {"harpy_sinew": 3, "spider_silk": 2, "ironhill_lacing": 4, "mammoth_leather": 1})
|
||||
self.assertEqual(p["equip"]["body"], "sabrecat_leather_body")
|
||||
self.assertEqual(p["action"]["id"], "fight_harpies")
|
||||
self.assertEqual(p["xp"]["tailoring"], 0)
|
||||
|
||||
def test_homesteaders_tailor(self):
|
||||
self.assertIn("tailoring", game.CLASSES["homesteader"]["fast"])
|
||||
self.assertIn("tailoring", game.CLASSES["homesteader"]["craft"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue