diff --git a/game.py b/game.py index 6e5541f..a9b7dd7 100644 --- a/game.py +++ b/game.py @@ -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: diff --git a/static/app.js b/static/app.js index 7f69d97..0267adb 100644 --- a/static/app.js +++ b/static/app.js @@ -478,9 +478,17 @@ function fightLine(e, enemy) { } function animateFight(now) { - const panel = $("[data-fight]"), a = S.me?.action, f = a?.fight; - if (!panel || !f) return; + const a = S.me?.action, f = a?.fight; + if (!f) return; const t = now - a.start; + { + let i = -1; + while (i + 1 < f.events.length && f.events[i + 1][0] <= t) i++; + const left = i >= 0 ? f.events[i][5] : f.enemy_hp; + for (const el of document.querySelectorAll("[data-progress]")) el.style.width = `${left / f.enemy_hp * 100}%`; + } + const panel = $("[data-fight]"); + if (!panel) return; let shown = -1; while (shown + 1 < f.events.length && f.events[shown + 1][0] <= t) shown++; const last = f.events[shown]; @@ -506,7 +514,7 @@ function animateFight(now) { const lines = f.events.slice(Math.max(0, shown - 5), shown + 1).reverse().map((e) => fightLine(e, f.enemy)); if (over) lines.unshift(f.result === "won" ? `