Combat: rewards on the kill, 1s pause, swing timer runs on between enemies

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-20 07:32:54 +01:00
parent 4cb6ff5ec0
commit 60b7b23a05
3 changed files with 61 additions and 15 deletions

24
game.py
View file

@ -432,7 +432,7 @@ def _tiered_actions() -> list[dict]:
# the trophies higher weapons need, and a boss.
CRIT_CHANCE = 0.05
CRIT_MULT = 1.5
RESPAWN = 3 # seconds before the next enemy turns up
RESPAWN = 1 # seconds after a kill before the next enemy turns up
MAX_FIGHT = 600 # a fight that drags on longer is broken off
FLEE_BELOW = 0.2 # out of food and under this share of HP: get out
@ -1195,8 +1195,12 @@ 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) -> dict:
"""Play out one fight swing by swing. Nothing changes until it's over: the result is applied then."""
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.
"""
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"]
@ -1207,13 +1211,16 @@ 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.
mine, theirs, result, t = (0.5 if me["first"] else me["interval"]), foe["interval"], None, 0.0
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
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
@ -1251,7 +1258,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 + RESPAWN) / pace,
"hp": hp, "result": result, "end": round(t / pace, 2), "duration": t / pace, "carry": t - swung,
"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")}
@ -1512,7 +1519,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)
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
if player["prayers"] and not fight["pp"]:
summary["prayer_out"] = True
paid = min(player["pp"], fight["pp"])
@ -1639,7 +1647,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["fight"] = simulate_fight(world, player, action, rng, current.get("carry", 0))
duration = current["fight"]["duration"]
else:
duration = action_time(world, player, action)
@ -1667,6 +1675,8 @@ def process_player(world: dict, player: dict, now: float, rng: random.Random | N
_complete(world, player, action, current["start"] + duration, rng, summary)
summary["done"] += 1
current["start"] += duration
if "enemy" in action and player["action"] is current:
current["start"] += RESPAWN / speed(world) # a moment before the next enemy turns up
if current.get("plot2"):
current["turn"] = 1 - current.get("turn", 0)
if player["action"] is current and "left" in current: