Dungeon loot: patterns, masterworks, relics, uniques
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
60b446acfd
commit
8afcba79fc
8 changed files with 358 additions and 18 deletions
60
combat.py
60
combat.py
|
|
@ -145,6 +145,45 @@ def known_abilities(style: str, cls: str, level: int, shield: bool) -> list[str]
|
|||
return sorted(kit, key=PRIORITY.index)
|
||||
|
||||
|
||||
def modded(ab: dict, mod: dict | None) -> dict:
|
||||
"""An ability as something you wear changes it: `cd` and `cast` multiply, `c` multiplies every
|
||||
effect's size, `dur` adds seconds to every effect that lasts, `cost` replaces the arrows it takes,
|
||||
and `extra` adds effects."""
|
||||
if not mod:
|
||||
return ab
|
||||
ab = {**ab, "effects": [dict(eff) for eff in ab["effects"]]}
|
||||
for key in ("cd", "cast"):
|
||||
if key in mod and ab.get(key):
|
||||
ab[key] = ab[key] * mod[key]
|
||||
if "cost" in mod:
|
||||
ab["cost"] = mod["cost"]
|
||||
for eff in ab["effects"]:
|
||||
if "c" in eff and "c" in mod:
|
||||
eff["c"] *= mod["c"]
|
||||
if "dur" in eff and "dur" in mod:
|
||||
eff["dur"] += mod["dur"]
|
||||
ab["effects"] += [dict(eff) for eff in mod.get("extra", [])]
|
||||
return ab
|
||||
|
||||
|
||||
def combine_mods(mods: list[dict]) -> dict:
|
||||
"""Several things changing the same abilities: multipliers multiply, durations add, extras pile up."""
|
||||
out = {}
|
||||
for mod in mods:
|
||||
for ab_id, change in mod.items():
|
||||
have = out.setdefault(ab_id, {})
|
||||
for key, value in change.items():
|
||||
if key in ("cd", "cast", "c"):
|
||||
have[key] = have.get(key, 1) * value
|
||||
elif key == "dur":
|
||||
have[key] = have.get(key, 0) + value
|
||||
elif key == "extra":
|
||||
have[key] = have.get(key, []) + value
|
||||
else:
|
||||
have[key] = value
|
||||
return out
|
||||
|
||||
|
||||
def role(style: str, shield: bool) -> str:
|
||||
return "tank" if style == "sword" and shield else "healer" if style == "staff" else "damage"
|
||||
|
||||
|
|
@ -225,6 +264,7 @@ def _member_unit(member: dict) -> dict:
|
|||
"damage": me["damage"], "interval": me["interval"], "accuracy": me["accuracy"], "evasion": me["evasion"],
|
||||
"crit": me["crit"], "block": me["block"], "taken": me["taken"], "base": me["base"],
|
||||
"abilities": list(me["abilities"]), "cds": {}, "busy": 0.0, "cast": None, "auras": [],
|
||||
"kit": {a: modded(ABILITIES[a], me.get("mods", {}).get(a)) for a in me["abilities"]},
|
||||
"next": member["at"] + (0.5 if me["first"] else me["interval"]), "stun": 0.0, "target": None,
|
||||
"mend": member["mend"], "wind": member["wind"], "wind_used": False,
|
||||
"potion": member.get("potion") or 0, "drank": False, "arrows": member.get("arrows", 0), "shot": 0,
|
||||
|
|
@ -428,7 +468,7 @@ def _think(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
|||
watching = t < unit["watch"]
|
||||
queued = unit["queued"]
|
||||
if queued:
|
||||
ab = ABILITIES[queued["ab"]]
|
||||
ab = unit["kit"][queued["ab"]]
|
||||
why = _ready(unit, ab, t, manual=True)
|
||||
if why is None:
|
||||
unit["queued"] = None
|
||||
|
|
@ -440,7 +480,7 @@ def _think(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
|||
elif why == "arrows" or t - queued["at"] > QUEUE:
|
||||
unit["queued"] = None
|
||||
for ab_id in unit["abilities"]:
|
||||
ab = ABILITIES[ab_id]
|
||||
ab = unit["kit"][ab_id]
|
||||
if (watching and ab_id in unit["manual"]) or _ready(unit, ab, t, manual=False):
|
||||
continue
|
||||
target = _rule(state, unit, ab, t)
|
||||
|
|
@ -491,7 +531,7 @@ def _use(state: dict, unit: dict, ab: dict, target, t: float, rng: random.Random
|
|||
def _finish_cast(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
||||
cast, unit["cast"] = unit["cast"], None
|
||||
if unit["side"] == "party":
|
||||
_apply(state, unit, ABILITIES[cast["ab"]], cast["target"], t, rng)
|
||||
_apply(state, unit, unit["kit"][cast["ab"]], cast["target"], t, rng)
|
||||
else:
|
||||
_enemy_effect(state, unit, cast["ab"], cast["target"], t, rng)
|
||||
|
||||
|
|
@ -536,13 +576,17 @@ def _apply(state: dict, unit: dict, ab: dict, target_id, t: float, rng: random.R
|
|||
"amount": eff["c"] * _avg(unit, target), "every": eff["every"],
|
||||
"next": t + eff["every"], "until": t + eff["dur"] + 1e-6})
|
||||
elif kind == "heal":
|
||||
_heal(state, unit, target, eff["c"] * _avg(unit) * spread(), t, ab["id"])
|
||||
_heal(state, unit, _second(state, target) if eff.get("second") else target,
|
||||
eff["c"] * _avg(unit) * spread(), t, ab["id"])
|
||||
elif kind == "hot":
|
||||
for ally in _party(state):
|
||||
_add_aura(ally, {"id": ab["id"], "kind": "hot", "by": unit["id"], "amount": eff["c"] * _avg(unit),
|
||||
"every": eff["every"], "next": t + eff["every"], "until": t + eff["dur"] + 1e-6})
|
||||
elif kind == "shield":
|
||||
_add_aura(target, {"id": ab["id"], "kind": "shield", "by": unit["id"], "amount": eff["c"] * _avg(unit),
|
||||
holder = _second(state, target) if eff.get("second") else target
|
||||
if not holder:
|
||||
continue
|
||||
_add_aura(holder, {"id": ab["id"], "kind": "shield", "by": unit["id"], "amount": eff["c"] * _avg(unit),
|
||||
"until": t + eff["dur"]})
|
||||
_heal_threat(state, unit, eff["c"] * _avg(unit))
|
||||
elif kind == "aura":
|
||||
|
|
@ -580,6 +624,12 @@ def _apply(state: dict, unit: dict, ab: dict, target_id, t: float, rng: random.R
|
|||
enemy["next"] += eff["by"]
|
||||
|
||||
|
||||
def _second(state: dict, first: dict | None) -> dict | None:
|
||||
"""The most hurt of your side after `first`: for heals and wards that reach a second ally."""
|
||||
others = [u for u in _party(state) if u is not first]
|
||||
return min(others, key=_share) if others else None
|
||||
|
||||
|
||||
def _stop_cast(state: dict, enemy: dict, by: dict, t: float, force: bool = False, quiet: bool = False) -> None:
|
||||
cast = enemy.get("cast")
|
||||
if not cast or (not force and not ENEMY_ABILITIES[cast["ab"]]["interrupt"]):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue