Active play and the Watchtower Depths dungeon

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-24 12:05:23 +01:00
parent db1a00e8aa
commit 9d61de2012
9 changed files with 790 additions and 56 deletions

View file

@ -1,7 +1,13 @@
#!/usr/bin/env python3
"""Balance report: an hour of fighting for each trade with its own weapon, at each tier.
python3 tools/balance.py [solo|party] [seeds] [levels, e.g. 22,52] [potions]
python3 tools/balance.py [solo|party|bosses] [seeds] [levels, e.g. 22,52] [potions]
python3 tools/balance.py dungeon [seeds] [level] [perfect]
`dungeon` plays each pull of every dungeon for a group of four and a pair, everyone with the
tier's gear, food and health potions. `perfect` stands in for active play: real cooldowns, no
waiting on auto's rules, quick interrupts. Bosses should beat an all-auto group and fall to one
that plays.
Each row is one encounter: wins and losses in the hour, XP an hour, food eaten an hour,
and the share of a kill's time spent fighting (the rest is resting between fights).
@ -97,8 +103,41 @@ def report(groups: dict, seeds: int, levels: set | None = None) -> None:
BOSSES = ["fight_ruins", "fight_barrow_king", "fight_green_dragon", "fight_blue_dragon", "fight_red_dragon"]
def dungeon(seeds: int, level: int | None) -> None:
for key, spec in game.DUNGEONS.items():
tier = spec["tier"]
at = level or spec["level"] + 3
action = game.ACTION_BY_ID[key]
for group in (["builder", "homesteader", "miner", "ranger"], ["builder", "homesteader"]):
global POTIONS
POTIONS = True
print(f"{spec['name']}: {'+'.join(group)} at {at}")
print(f"{'pull':18} {'win':>5} {'secs':>5} {'downs':>5} {'potions':>7}")
for index, (name, _pack) in enumerate(spec["pulls"]):
wins, secs, downs, drunk = 0, [], [], []
for seed in range(seeds):
world = game.new_world(T0)
players = [fighter(world, f"p{i}", cls, at, tier) for i, cls in enumerate(group)]
fight = game.draw_pull(world, players, action, {"pull": index, "wipes": 0}, random.Random(seed), T0)
if fight["result"] == "won":
wins += 1
secs.append(fight["duration"] - fight["rest"])
downs.append(sum(1 for m in fight["members"] if m["down"]))
drunk.append(sum(1 for m in fight["members"] if m["drank"]))
mean = lambda xs: statistics.mean(xs) if xs else 0
print(f"{name:18} {wins / seeds:>5.0%} {mean(secs):>5.0f} {mean(downs):>5.1f} {mean(drunk):>7.1f}")
print()
def main() -> None:
mode = sys.argv[1] if len(sys.argv) > 1 else "solo"
if mode == "dungeon":
if "perfect" in sys.argv:
game.combat.AUTO_CD, game.combat.AUTO_DELAY, game.combat.AUTO_REACT = 1.0, 0.0, 0.25
print("-- perfect hands --")
dungeon(int(sys.argv[2]) if len(sys.argv) > 2 else 20,
int(sys.argv[3]) if len(sys.argv) > 3 and sys.argv[3].isdigit() else None)
return
seeds = int(sys.argv[2]) if len(sys.argv) > 2 else 3
levels = {int(level) for level in sys.argv[3].split(",")} if len(sys.argv) > 3 and sys.argv[3] != "all" else None
global POTIONS