Combat: each enemy starts with fresh swing timers; show the pause after a kill

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-20 07:51:16 +01:00
parent 60b7b23a05
commit 5898e2006f
4 changed files with 23 additions and 33 deletions

22
game.py
View file

@ -1195,12 +1195,9 @@ def fight_odds(player: dict, action: dict) -> dict:
"kill_time": round(kill, 1), "damage": round(hurt, 1)}
def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random, carry: float = 0) -> dict:
"""Play out one fight swing by swing. Nothing changes until it's over: the result is applied then.
`carry` is how long ago (in game seconds) you last swung: your swing timer runs on from one
enemy to the next, so outclassing an enemy means killing as fast as you can swing.
"""
def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random) -> dict:
"""Play out one fight swing by swing, both swing timers starting empty. Nothing changes
until it's over: the result is applied then."""
foe, blessed, brew = action["enemy"], blessing(player), potion_effect(player, "combat")
me = fighter(player, blessed, brew, foe)
shrug = 0 if me["pierce"] else foe["reduction"]
@ -1211,16 +1208,13 @@ def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random,
wind = blessed["wind"] and not (player["action"] or {}).get("wind_used")
pace = speed(world)
# A bow looses its first arrow before the enemy closes in.
ready = max(0.0, me["interval"] - carry)
mine, theirs, result, t = (min(ready, 0.5) if me["first"] else ready), foe["interval"], None, 0.0
swung = -carry
mine, theirs, result, t = (0.5 if me["first"] else me["interval"]), foe["interval"], None, 0.0
while result is None:
t = min(mine, theirs)
if t > MAX_FIGHT:
result = "fled"
break
if mine <= theirs:
swung = mine
mine += me["interval"]
crit = rng.random() < me["crit"]
landed = rng.random() < hit
@ -1258,7 +1252,7 @@ def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random,
elif starving:
result = "fled"
return {"enemy": foe["name"], "enemy_hp": foe["hp"], "top": top, "start_hp": round(player["hp"]),
"hp": hp, "result": result, "end": round(t / pace, 2), "duration": t / pace, "carry": t - swung,
"hp": hp, "result": result, "end": round(t / pace, 2), "duration": t / pace,
"interval": me["interval"] / pace, "enemy_interval": foe["interval"] / pace, "events": events, "style": me["style"],
"eaten": eaten, "damage": hurt, "pp": blessed["cost"],
"wind_used": blessed["wind"] and not wind and not (player["action"] or {}).get("wind_used")}
@ -1519,8 +1513,8 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
if "enemy" in action:
outing = player["action"]
fight = outing.pop("fight", None) or simulate_fight(world, player, action, rng, outing.get("carry", 0))
outing["carry"] = fight.get("carry", 0) + RESPAWN # fights from before the carry-over have none
fight = outing.pop("fight", None) or simulate_fight(world, player, action, rng)
outing.pop("carry", None) # from a short-lived version that ran the swing timer on
if player["prayers"] and not fight["pp"]:
summary["prayer_out"] = True
paid = min(player["pp"], fight["pp"])
@ -1647,7 +1641,7 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N
if "enemy" in action and not can_start(world, player, action):
if not current.get("fight"):
regen(world, player, current["start"], blessing(player)["regen"])
current["fight"] = simulate_fight(world, player, action, rng, current.get("carry", 0))
current["fight"] = simulate_fight(world, player, action, rng)
duration = current["fight"]["duration"]
else:
duration = action_time(world, player, action)