Active play and the Watchtower Depths dungeon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
db1a00e8aa
commit
9d61de2012
9 changed files with 790 additions and 56 deletions
16
README.md
16
README.md
|
|
@ -56,6 +56,19 @@ Beta. The core game is in place; changes from here are mostly balance and tuning
|
|||
(miner: a target's resistance stops counting), Barricade (builder: the group
|
||||
takes less), Hearty Meal (homesteader: the group heals over time), Mark Prey
|
||||
(ranger: a target takes more from everyone).
|
||||
- **Active play:** any ability can be switched from AUTO (a rule uses it, on a
|
||||
1.5× cooldown) to YOU: you press it (tap, or keys 1–9) on its real cooldown,
|
||||
and tap frames to pick your target or aim your heals. Your hands only count
|
||||
while you watch: look away for 12 seconds and it's all auto again. Presses are
|
||||
kept with the fight, which is played again from its seed with them, so
|
||||
nothing that already happened changes.
|
||||
- **Dungeons:** Watchtower Depths (combat 20) for 2 to 4: eight pulls in order,
|
||||
sized for the group, ending in the Watchtower ogre. Trash falls on auto; the
|
||||
bosses (Clay colossus, Harpy matriarch, the ogre) don't: interruptible curses
|
||||
and shrieks, ground pounds to brace for, adds at half health and an enrage
|
||||
timer. Join before the first pull. A wipe repeats the pull, three on one pull
|
||||
end the run, and the run waits while anyone looks away. Bosses give everyone
|
||||
their own loot; clearing pays gold, a trophy and reagents.
|
||||
- **Armour:** three weights. Heavy plate (Smithing) is strong against blows
|
||||
and weak to magic, light cloth (Tailoring) the other way round, medium
|
||||
leather (Leatherworking) a bit of both. Every trade gathers its own base
|
||||
|
|
@ -198,7 +211,8 @@ snapshot and exits; the NixOS module runs it nightly when
|
|||
|
||||
- `game.py`: content tables and the rules. Pure functions of `(world, now)`.
|
||||
- `combat.py`: the fight engine and the abilities.
|
||||
- `tools/balance.py`: an hour of fighting per trade and tier (`solo`, `party`, `bosses`).
|
||||
- `tools/balance.py`: an hour of fighting per trade and tier (`solo`, `party`, `bosses`), and
|
||||
each dungeon pull on auto or with `perfect` hands (`dungeon`).
|
||||
- `app.py`: stdlib HTTP server, persistence and the background ticker.
|
||||
- `static/`: the client (vanilla JS, no build step).
|
||||
|
||||
|
|
|
|||
14
app.py
14
app.py
|
|
@ -126,6 +126,9 @@ def player_view(world: dict, player: dict, now: float) -> dict:
|
|||
view["action"] = {**player["action"], "growing": action["id"], "duration": duration}
|
||||
if action.get("together") and not fight:
|
||||
view["action"]["waiting"] = True # for a group: nothing to count down
|
||||
run = game.run_of(world, player)
|
||||
if run:
|
||||
view["action"]["run"] = run
|
||||
if fight:
|
||||
view["action"]["fight"] = game.fight_view(fight)
|
||||
party = game.party_of(world, player)
|
||||
|
|
@ -288,6 +291,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||
summary = game.take_summary(player, game.empty_summary())
|
||||
game.regen(world, player, now)
|
||||
player["last_active"] = now
|
||||
game.watch_fight(world, player, now) # looking at it: your manual abilities hold
|
||||
self.store.save()
|
||||
self.send_json(self.state(uid, summary))
|
||||
elif path.startswith("/api/"):
|
||||
|
|
@ -319,6 +323,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||
summary = game.take_summary(player, game.empty_summary())
|
||||
game.regen(world, player, now)
|
||||
player["last_active"] = now
|
||||
game.watch_fight(world, player, now)
|
||||
error = self.player_route(path, player, body, now)
|
||||
if error is None:
|
||||
game.process_player(world, player, now, self.store.rng) # a new fight is drawn up at once
|
||||
|
|
@ -347,6 +352,15 @@ class Handler(BaseHTTPRequestHandler):
|
|||
return game.start_action(world, player, str(body.get("id")), now, once=bool(body.get("once")))
|
||||
if path == "/api/join":
|
||||
return game.join_fight(world, player, str(body.get("id")), now)
|
||||
if path == "/api/press":
|
||||
target = body.get("target")
|
||||
return game.press(world, player, str(body.get("id")), now, str(target) if target else None)
|
||||
if path == "/api/target":
|
||||
return game.set_fight_target(world, player, str(body.get("id")), now)
|
||||
if path == "/api/pull":
|
||||
return game.pull_now(world, player, now)
|
||||
if path == "/api/manual":
|
||||
return game.set_manual(world, player, str(body.get("id")), bool(body.get("on")), now)
|
||||
if path == "/api/order":
|
||||
return game.post_order(world, player, body.get("kind"), str(item), qty, body.get("provide"),
|
||||
body.get("reward"), now)
|
||||
|
|
|
|||
137
combat.py
137
combat.py
|
|
@ -5,8 +5,12 @@ steps out, so the page can replay it. Each slice draws from a random seeded by t
|
|||
the slice number: playing it again with a latecomer changes nothing before they arrive.
|
||||
|
||||
Everyone still swings on their weapon's timer. On top of that, the weapon in your hands
|
||||
gives four abilities (learned at combat 1, 15, 30 and 45) and your trade one more. For now
|
||||
every ability is on auto: a rule decides when, and runs its cooldown half again as long.
|
||||
gives four abilities (learned at combat 1, 15, 30 and 45) and your trade one more. Each is
|
||||
on auto (a rule decides when, and runs its cooldown half again as long) or in your hands
|
||||
(pressed, on its real cooldown). Your hands only count while you're watching: a member's
|
||||
`watch` is the moment of the fight their manual choices hold until, and after it everything
|
||||
goes back to auto. Presses, targets and switches are a member's `inputs`, each at the moment
|
||||
it was made, so playing the fight again from the start with them changes nothing before then.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -18,9 +22,12 @@ GCD = 1.5
|
|||
AUTO_DELAY = 0.5 # auto waits this long after it could act
|
||||
AUTO_CD = 1.5 # ...and runs cooldowns this much slower
|
||||
AUTO_REACT = 1.0 # seconds into an enemy's cast before auto stops it
|
||||
WATCH = 12 # real seconds since a player last looked that their manual choices still hold
|
||||
QUEUE = 1.5 # a press waits this long for its cooldown or the global one before it's dropped
|
||||
CRIT_MULT = 1.5
|
||||
ENEMY_CRIT = 0.05
|
||||
SPREAD = 0.1 # abilities land within 10% of their size
|
||||
HARD_ENRAGE = 3.0 # how much harder a dungeon boss hits once its time is up
|
||||
HEAL_THREAT = 0.5
|
||||
STICKY = 1.1 # an enemy only turns when someone has 10% more threat than its target
|
||||
TANK_THREAT = 2.0
|
||||
|
|
@ -159,6 +166,15 @@ ENEMY_ABILITIES = {
|
|||
"target": "all", "effect": ("aoe", 1.4, "magic")},
|
||||
"tail_swipe": {"name": "Tail Swipe", "cast": 0, "every": 12, "first": 7, "interrupt": False,
|
||||
"target": "target", "effect": ("hit", 1.2, "blunt")},
|
||||
# Dungeon bosses' own: the ones a group has to answer.
|
||||
"stone_curse": {"name": "Stone Curse", "cast": 2.0, "every": 6, "first": 4, "interrupt": True,
|
||||
"target": "target", "effect": ("hit", 3.5, "magic")},
|
||||
"ground_pound": {"name": "Ground Pound", "cast": 3.0, "every": 18, "first": 10, "interrupt": False,
|
||||
"target": "all", "effect": ("aoe", 1.6, "blunt")},
|
||||
"shatter": {"name": "Shatter", "cast": 2.5, "every": 14, "first": 6, "interrupt": False,
|
||||
"target": "all", "effect": ("aoe", 1.5, "blunt")},
|
||||
"shriek": {"name": "Shriek", "cast": 2.0, "every": 9, "first": 4, "interrupt": True,
|
||||
"target": "all", "effect": ("aoe", 1.4, "magic")},
|
||||
}
|
||||
ARCHETYPE_ABILITIES = {"brute": ["heavy_slam"], "skirmisher": ["lunge"], "caster": ["hex_bolt"],
|
||||
"elite": ["heavy_slam"], "boss": ["heavy_slam", "sweep"], "dragon": ["fire_breath", "tail_swipe"]}
|
||||
|
|
@ -178,7 +194,9 @@ def _member_unit(member: dict) -> dict:
|
|||
"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,
|
||||
"dealt": 0, "hurt": 0, "down": False}
|
||||
"dealt": 0, "hurt": 0, "down": False, "manual": set(member.get("manual", [])),
|
||||
"watch": member.get("watch", -1.0), "inputs": sorted(member.get("inputs", []), key=lambda i: i[0]),
|
||||
"seen": 0, "queued": None, "picked": False, "ftarget": None}
|
||||
|
||||
|
||||
def _enemy_unit(index: int, foe: dict) -> dict:
|
||||
|
|
@ -190,7 +208,10 @@ def _enemy_unit(index: int, foe: dict) -> dict:
|
|||
"next": foe["interval"] + 0.4 * index, "swings": 0, "cast": None, "auras": [], "threat": {},
|
||||
"target": None, "taunt": None, "since": 0.0, "stun": 0.0, "cc": 0.0, "dead": False, "enraged": 1.0,
|
||||
"abil": [{"id": a, "next": ENEMY_ABILITIES[a]["first"] + 0.5 * index}
|
||||
for a in ARCHETYPE_ABILITIES.get(foe["archetype"], [])]}
|
||||
for a in foe.get("abilities") or ARCHETYPE_ABILITIES.get(foe["archetype"], [])],
|
||||
# A dungeon boss's: adds that wait (`dormant`) until it's hurt enough, shouts, and a hard enrage.
|
||||
"dormant": foe.get("dormant", False), "phases": [dict(p) for p in foe.get("phases", [])],
|
||||
"enrage_at": foe.get("enrage_at")}
|
||||
|
||||
|
||||
def run(fight: dict) -> dict:
|
||||
|
|
@ -238,7 +259,7 @@ def _party(state: dict) -> list[dict]:
|
|||
|
||||
|
||||
def _enemies(state: dict) -> list[dict]:
|
||||
return [u for u in state["units"].values() if u["side"] == "enemy" and not u["dead"]]
|
||||
return [u for u in state["units"].values() if u["side"] == "enemy" and not u["dead"] and not u["dormant"]]
|
||||
|
||||
|
||||
# --- a slice -------------------------------------------------------------------
|
||||
|
|
@ -261,6 +282,7 @@ def _step(state: dict, t: float, rng: random.Random) -> str | None:
|
|||
if _alive(unit) and unit["cast"] and unit["cast"]["end"] <= t + 1e-9:
|
||||
_finish_cast(state, unit, t, rng)
|
||||
for unit in _party(state):
|
||||
_inputs(state, unit, t)
|
||||
_think(state, unit, t, rng)
|
||||
for enemy in _enemies(state):
|
||||
if enemy["cc"] and enemy["cc"] <= t + 1e-9:
|
||||
|
|
@ -275,6 +297,16 @@ def _step(state: dict, t: float, rng: random.Random) -> str | None:
|
|||
if rule and enemy["enraged"] == 1.0 and enemy["hp"] < enemy["top"] * rule[0]:
|
||||
enemy["enraged"] = rule[1]
|
||||
_event(state, t, enemy["id"], enemy["id"], "enrage")
|
||||
if enemy["enrage_at"] is not None and t >= enemy["enrage_at"] and enemy["enraged"] < HARD_ENRAGE:
|
||||
enemy["enraged"] = HARD_ENRAGE # out of patience
|
||||
_event(state, t, enemy["id"], enemy["id"], "enrage", 0, "hard")
|
||||
for phase in enemy["phases"]:
|
||||
if not phase.get("done") and enemy["hp"] <= enemy["top"] * phase["at"]:
|
||||
phase["done"] = True
|
||||
if phase.get("say"):
|
||||
_event(state, t, enemy["id"], None, "yell", 0, phase["say"])
|
||||
for uid in phase.get("wake", []):
|
||||
_wake(state, state["units"][uid], t)
|
||||
if not _enemies(state):
|
||||
return "won"
|
||||
if not _party(state) and not any(u["side"] == "party" and not u["in"] and not u["out"] for u in units.values()):
|
||||
|
|
@ -286,8 +318,22 @@ def _step(state: dict, t: float, rng: random.Random) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
def _wake(state: dict, enemy: dict, t: float) -> None:
|
||||
"""An add joins the fight: on whoever's tanking, else the first of you."""
|
||||
enemy["dormant"] = False
|
||||
enemy["next"] = t + 1.0
|
||||
for slot in enemy["abil"]:
|
||||
slot["next"] = t + ENEMY_ABILITIES[slot["id"]]["first"]
|
||||
party = _party(state)
|
||||
for unit in party:
|
||||
enemy["threat"][unit["id"]] = 10.0 if unit["role"] == "tank" else 0.0
|
||||
if party and not any(enemy["threat"].values()):
|
||||
enemy["threat"][party[0]["id"]] = 1.0
|
||||
_event(state, t, enemy["id"], None, "join")
|
||||
|
||||
|
||||
def _alive(unit: dict) -> bool:
|
||||
return not unit["dead"] if unit["side"] == "enemy" else unit["in"] and not unit["out"]
|
||||
return not unit["dead"] and not unit["dormant"] if unit["side"] == "enemy" else unit["in"] and not unit["out"]
|
||||
|
||||
|
||||
def _tick(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
||||
|
|
@ -309,6 +355,35 @@ def _tick(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
|||
|
||||
# --- your side -----------------------------------------------------------------
|
||||
|
||||
def _inputs(state: dict, unit: dict, t: float) -> None:
|
||||
"""What the player did up to now: presses queue up, taps pick targets, switches move abilities
|
||||
between auto and their hands."""
|
||||
inputs = unit["inputs"]
|
||||
while unit["seen"] < len(inputs) and inputs[unit["seen"]][0] <= t + 1e-9:
|
||||
at, kind, arg, *more = inputs[unit["seen"]]
|
||||
unit["seen"] += 1
|
||||
other = state["units"].get(arg or "")
|
||||
if kind == "press" and arg in unit["abilities"]:
|
||||
unit["queued"] = {"ab": arg, "target": more[0] if more else None, "at": at}
|
||||
elif kind == "target" and other and _alive(other):
|
||||
if other["side"] == "enemy":
|
||||
unit["target"], unit["picked"] = arg, True
|
||||
else:
|
||||
unit["ftarget"] = arg
|
||||
elif kind == "mode":
|
||||
(unit["manual"].add if more and more[0] else unit["manual"].discard)(arg)
|
||||
|
||||
|
||||
def _ready(unit: dict, ab: dict, t: float, manual: bool) -> str | None:
|
||||
if unit["cds"].get(ab["id"], 0) > t + 1e-9:
|
||||
return "cooldown"
|
||||
if not ab.get("offgcd") and unit["busy"] + (0 if manual else AUTO_DELAY) > t + 1e-9:
|
||||
return "gcd"
|
||||
if ab.get("cost", 0) > unit["arrows"]:
|
||||
return "arrows"
|
||||
return None
|
||||
|
||||
|
||||
def _think(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
||||
if unit["potion"] and not unit["drank"] and unit["hp"] < unit["top"] * POTION_BELOW:
|
||||
unit["drank"] = True
|
||||
|
|
@ -316,13 +391,23 @@ def _think(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
|||
if unit["stun"] > t or unit["cast"]:
|
||||
return
|
||||
_pick_target(state, unit, t)
|
||||
watching = t < unit["watch"]
|
||||
queued = unit["queued"]
|
||||
if queued:
|
||||
ab = ABILITIES[queued["ab"]]
|
||||
why = _ready(unit, ab, t, manual=True)
|
||||
if why is None:
|
||||
unit["queued"] = None
|
||||
target = _manual_target(state, unit, ab, queued["target"], t)
|
||||
if target is not False:
|
||||
_use(state, unit, ab, target, t, rng, manual=True)
|
||||
if unit["cast"] or not ab.get("offgcd"):
|
||||
return
|
||||
elif why == "arrows" or t - queued["at"] > QUEUE:
|
||||
unit["queued"] = None
|
||||
for ab_id in unit["abilities"]:
|
||||
ab = ABILITIES[ab_id]
|
||||
if unit["cds"].get(ab_id, 0) > t + 1e-9:
|
||||
continue
|
||||
if not ab.get("offgcd") and unit["busy"] + AUTO_DELAY > t + 1e-9:
|
||||
continue
|
||||
if ab.get("cost", 0) > unit["arrows"]:
|
||||
if (watching and ab_id in unit["manual"]) or _ready(unit, ab, t, manual=False):
|
||||
continue
|
||||
target = _rule(state, unit, ab, t)
|
||||
if target is False:
|
||||
|
|
@ -332,8 +417,31 @@ def _think(state: dict, unit: dict, t: float, rng: random.Random) -> None:
|
|||
return
|
||||
|
||||
|
||||
def _use(state: dict, unit: dict, ab: dict, target, t: float, rng: random.Random) -> None:
|
||||
unit["cds"][ab["id"]] = t + ab.get("cd", 0) * AUTO_CD
|
||||
def _manual_target(state: dict, unit: dict, ab: dict, given: str | None, t: float):
|
||||
"""Who a press lands on: the frame you tapped, else the one the ability would pick, else your target."""
|
||||
units = state["units"]
|
||||
kind = ab["target"]
|
||||
if kind in ("self", "aoe", "party"):
|
||||
return None
|
||||
if kind == "ally":
|
||||
for choice in (given, unit["ftarget"]):
|
||||
other = units.get(choice or "")
|
||||
if other and other["side"] == "party" and _alive(other):
|
||||
return other["id"]
|
||||
return min(_party(state), key=_share)["id"]
|
||||
other = units.get(given or "")
|
||||
if other and other["side"] == "enemy" and _alive(other):
|
||||
return other["id"]
|
||||
if ab["auto"]["r"] in ("stop", "cc", "taunt"):
|
||||
smart = _rule(state, unit, {**ab, "auto": {**ab["auto"], "else": False}}, t)
|
||||
if smart:
|
||||
return smart
|
||||
target = units.get(unit["target"] or "")
|
||||
return target["id"] if target and _alive(target) else False
|
||||
|
||||
|
||||
def _use(state: dict, unit: dict, ab: dict, target, t: float, rng: random.Random, manual: bool = False) -> None:
|
||||
unit["cds"][ab["id"]] = t + ab.get("cd", 0) * (1 if manual else AUTO_CD)
|
||||
if ab.get("cost"):
|
||||
unit["arrows"] -= ab["cost"]
|
||||
unit["shot"] += ab["cost"]
|
||||
|
|
@ -462,8 +570,9 @@ def _share(unit: dict) -> float:
|
|||
|
||||
def _pick_target(state: dict, unit: dict, t: float) -> None:
|
||||
current = state["units"].get(unit["target"] or "")
|
||||
if current and not current["dead"] and current["cc"] <= t:
|
||||
if current and not current["dead"] and (current["cc"] <= t or unit["picked"]):
|
||||
return
|
||||
unit["picked"] = False
|
||||
enemies = _enemies(state)
|
||||
free = [e for e in enemies if e["cc"] <= t] or enemies
|
||||
if not free:
|
||||
|
|
|
|||
355
game.py
355
game.py
|
|
@ -987,6 +987,83 @@ COMBAT_ACTIONS = [
|
|||
drops=[("rune_bar", 1, 0.1), ("dragon_bones", 2, 1.0), *_boss_drops(5, 0.8)]),
|
||||
]
|
||||
|
||||
# --- dungeons ----------------------------------------------------------------
|
||||
# A run of pulls for a group of two to four, in order, ending in the area's boss. Built for four:
|
||||
# a smaller group meets lighter packs. Trash a group can clear on auto; the bosses it can't. They
|
||||
# cast what has to be interrupted, hit what has to be braced for, call in help, and lose patience.
|
||||
DUNGEON_SIZE = 4 # the most in a run
|
||||
DUNGEON_WIPES = 3 # on one pull, and the run is over
|
||||
DUNGEON_AWAY = 90 # real seconds without looking before a run waits for you
|
||||
TRASH = (1.5, 1.5) # a pull's HP (and XP) and hits against the open world's
|
||||
MINI_BOSS = 5.0 # a mini-boss's HP (and XP, and loot odds) against its race's elite
|
||||
FINAL_BOSS = (2.5, 1.3) # the area boss's (and its adds') HP and hits against the open-world one
|
||||
|
||||
|
||||
def dungeon_scale(size: int) -> tuple[float, float]:
|
||||
"""HP and hits of a pull for a group this size."""
|
||||
return 0.25 * size, 0.4 + 0.15 * size
|
||||
|
||||
|
||||
def _dungeon_foe(action_id: str, variant: str, hp: float = 1.0, hits: float = 1.0, loot: float | None = None,
|
||||
**extra) -> dict:
|
||||
"""One of a race's variants, as a dungeon enemy: `hp` times as tough (and worth as much more XP),
|
||||
hitting `hits` times as hard. It drops what its race does, `loot` (else `hp`) times as often."""
|
||||
action = next(a for a in COMBAT_ACTIONS if a["id"] == action_id)
|
||||
foe = next(v for v in action["enemy"]["variants"] if v["name"] == variant)
|
||||
return {**{key: foe[key] for key in ("name", "archetype", "level", "interval", "accuracy", "evasion",
|
||||
"attack", "vulnerable", "resists")},
|
||||
"boss": extra.pop("boss", False), "hp": round(foe["hp"] * hp), "max_hit": max(1, round(foe["max_hit"] * hits)),
|
||||
"xp": foe["xp"] * hp, "share": hp if loot is None else loot, "loot": action_id, **extra}
|
||||
|
||||
|
||||
DUNGEONS = {
|
||||
"dungeon_watchtower": {
|
||||
"name": "Watchtower Depths", "area": "Old Watchtower", "tier": 1, "level": 20, "bonus_gold": 100,
|
||||
"pulls": [
|
||||
("Gatehouse", [_dungeon_foe("fight_clay_golems", "Clay brute", *TRASH, 1), _dungeon_foe("fight_clay_golems", "Mud shaper", *TRASH, 1),
|
||||
_dungeon_foe("fight_clay_golems", "Clay brute", *TRASH, 1)]),
|
||||
("Broken stair", [_dungeon_foe("fight_harpies", "Harpy slasher", *TRASH, 1), _dungeon_foe("fight_harpies", "Harpy slasher", *TRASH, 1),
|
||||
_dungeon_foe("fight_harpies", "Harpy windcaster", *TRASH, 1)]),
|
||||
("Clay colossus", [_dungeon_foe("fight_clay_golems", "Clay colossus", MINI_BOSS, 3.0, boss=True,
|
||||
abilities=["stone_curse", "shatter"], enrage_at=150)]),
|
||||
("Barracks", [_dungeon_foe("fight_restless_dead", "Skeleton warrior", *TRASH, 1),
|
||||
_dungeon_foe("fight_restless_dead", "Skeleton warrior", *TRASH, 1),
|
||||
_dungeon_foe("fight_restless_dead", "Wisp", *TRASH, 1), _dungeon_foe("fight_restless_dead", "Necromancer", *TRASH, 1)]),
|
||||
("Troll den", [_dungeon_foe("fight_cave_trolls", "Troll bruiser", *TRASH, 1), _dungeon_foe("fight_cave_trolls", "Troll rockhurler", *TRASH, 1),
|
||||
_dungeon_foe("fight_cave_trolls", "Troll hexer", *TRASH, 1)]),
|
||||
("Harpy matriarch", [_dungeon_foe("fight_harpies", "Harpy matriarch", MINI_BOSS, 5.5, boss=True,
|
||||
abilities=["shriek", "lunge"], enrage_at=150,
|
||||
phases=[{"at": 0.5, "say": "To me, sisters!", "wake": ["e1", "e2"]}]),
|
||||
_dungeon_foe("fight_harpies", "Harpy slasher", *TRASH, 1, dormant=True),
|
||||
_dungeon_foe("fight_harpies", "Harpy slasher", *TRASH, 1, dormant=True)]),
|
||||
("Crypt", [_dungeon_foe("fight_restless_dead", "Necromancer", *TRASH, 1), _dungeon_foe("fight_restless_dead", "Necromancer", *TRASH, 1),
|
||||
_dungeon_foe("fight_restless_dead", "Skeleton warrior", *TRASH, 1)]),
|
||||
("Watchtower ogre", [_dungeon_foe("fight_ruins", "Watchtower ogre", FINAL_BOSS[0], FINAL_BOSS[1], 2,
|
||||
boss=True, abilities=["stone_curse", "ground_pound", "heavy_slam"],
|
||||
enrage_at=210, phases=[{"at": 0.5, "say": "Crush them!", "wake": ["e1", "e2"]}]),
|
||||
_dungeon_foe("fight_cave_trolls", "Troll bruiser", *FINAL_BOSS, 1, dormant=True),
|
||||
_dungeon_foe("fight_cave_trolls", "Troll bruiser", *FINAL_BOSS, 1, dormant=True)]),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _dungeon_actions() -> list[dict]:
|
||||
actions = []
|
||||
for key, spec in DUNGEONS.items():
|
||||
bosses = [foe for _name, pack in spec["pulls"] for foe in pack if foe["boss"]]
|
||||
variants = [{**foe, "weight": 1} for foe in bosses]
|
||||
actions.append(_action(key, "combat", spec["name"], spec["level"], RESPAWN,
|
||||
round(sum(foe["xp"] for _name, pack in spec["pulls"] for foe in pack)),
|
||||
enemy={"name": spec["name"], "level": spec["level"], "boss": True, "variants": variants},
|
||||
group=spec["area"], dungeon=key, together=GROUP_SIZE, personal_loot=True,
|
||||
gold=[spec["bonus_gold"], spec["bonus_gold"]], drops=[list(d) for d in _boss_drops(spec["tier"])],
|
||||
pulls=[name for name, _pack in spec["pulls"]]))
|
||||
return actions
|
||||
|
||||
|
||||
COMBAT_ACTIONS += _dungeon_actions()
|
||||
|
||||
ACTIONS = [
|
||||
*COMBAT_ACTIONS,
|
||||
_action("mine_stone", "mining", "Quarry stone", 1, 20, 5, outputs={"stone": 1}),
|
||||
|
|
@ -1167,7 +1244,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows
|
|||
|
||||
# --- world ---------------------------------------------------------------
|
||||
|
||||
WORLD_VERSION = 27
|
||||
WORLD_VERSION = 28
|
||||
|
||||
|
||||
def new_world(now: float, speed: float = 1.0) -> dict:
|
||||
|
|
@ -1382,6 +1459,11 @@ def migrate(world: dict, now: float | None = None) -> dict:
|
|||
if world["next_event_at"] and not upcoming_event(world):
|
||||
world["next_event_at"] = min(world["next_event_at"], now)
|
||||
world["version"] = 27
|
||||
if world["version"] < 28:
|
||||
# Abilities in your own hands.
|
||||
for player in world["players"].values():
|
||||
player.setdefault("manual", [])
|
||||
world["version"] = 28
|
||||
return world
|
||||
|
||||
|
||||
|
|
@ -1638,7 +1720,7 @@ def new_player(uid: str, name: str, cls: str, now: float) -> dict:
|
|||
"hp": 44, "hp_at": now,
|
||||
"action": None, "stopped": None,
|
||||
"last_active": now, "overflow": "city",
|
||||
"perks": {"bank_slot": 0},
|
||||
"perks": {"bank_slot": 0}, "manual": [],
|
||||
"garrison": False,
|
||||
"donated": 0,
|
||||
"pinned": [], # skills pinned to the top of the sidebar
|
||||
|
|
@ -1901,7 +1983,9 @@ def fight_member(world: dict, player: dict, at: float = 0.0) -> dict:
|
|||
"role": me["role"], "abilities": me["abilities"], "interval": me["interval"] / speed(world),
|
||||
"fighter": me, "eaten": {}, "potion": ITEMS[potion]["drink"] if potion else 0, "potion_item": potion,
|
||||
"arrows": player["bank"].get("arrows", 0), "mend": blessed["mend"],
|
||||
"wind": blessed["wind"] and not (player["action"] or {}).get("wind_used"), "pp": blessed["cost"]}
|
||||
"wind": blessed["wind"] and not (player["action"] or {}).get("wind_used"), "pp": blessed["cost"],
|
||||
"manual": [a for a in player.get("manual", []) if a in me["abilities"]], "inputs": [],
|
||||
"watch": at + combat.WATCH * speed(world)}
|
||||
|
||||
|
||||
FIGHT_PRIVATE = ("fighter", "mend", "wind", "arrows")
|
||||
|
|
@ -1939,9 +2023,10 @@ def draw_pack(action: dict, size: int, rng: random.Random) -> list[dict]:
|
|||
"xp": variants[i]["xp"] * share, "share": share} for i in picks]
|
||||
|
||||
|
||||
def draw_fight(world: dict, players: list, action: dict, rng: random.Random) -> dict:
|
||||
def draw_fight(world: dict, players: list, action: dict, rng: random.Random, start: float | None = None) -> dict:
|
||||
"""Draw up the pack, rest everyone back to full (the longest rest is everyone's), and play the
|
||||
fight out."""
|
||||
fight out. A fight starting at `start` (real time) holds each player's manual abilities for as
|
||||
long as they were last seen watching."""
|
||||
members, rest = [], 0.0
|
||||
for player in players:
|
||||
seconds, eaten = rest_plan(world, player)
|
||||
|
|
@ -1951,11 +2036,101 @@ def draw_fight(world: dict, players: list, action: dict, rng: random.Random) ->
|
|||
members.append(member)
|
||||
fight = {"seed": rng.random(), "pace": speed(world), "rest": rest, "enemy": action["enemy"]["name"],
|
||||
"pack": draw_pack(action, len(players), rng), "members": members}
|
||||
for member, player in zip(members, players):
|
||||
member["watch"] = -1.0 if start is None else fight_time(fight, start, player["last_active"] + combat.WATCH)
|
||||
return play_fight(fight)
|
||||
|
||||
|
||||
def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random) -> dict:
|
||||
return draw_fight(world, [player], action, rng)
|
||||
def simulate_fight(world: dict, player: dict, action: dict, rng: random.Random, start: float | None = None) -> dict:
|
||||
return draw_fight(world, [player], action, rng, start)
|
||||
|
||||
|
||||
def fight_time(fight: dict, start: float, now: float) -> float:
|
||||
"""A real moment as seconds into a fight (after its rest) that started at `start`."""
|
||||
return (now - start) * fight["pace"] - fight["rest"]
|
||||
|
||||
|
||||
def fight_clock(world: dict, player: dict) -> tuple[dict | None, float]:
|
||||
"""The fight you're in and when it (its rest included) began."""
|
||||
current = player["action"] or {}
|
||||
party = world["parties"].get(current.get("party"))
|
||||
if party:
|
||||
return party["fight"], party["start"]
|
||||
return current.get("fight"), current.get("start", 0)
|
||||
|
||||
|
||||
def _my_fight(world: dict, player: dict, now: float) -> tuple[dict, dict, float] | str:
|
||||
fight, start = fight_clock(world, player)
|
||||
if not fight or now >= start + fight["duration"] or not any(m["id"] == player["id"] for m in fight["members"]):
|
||||
return "You're not fighting"
|
||||
member = member_of(fight, player["id"])
|
||||
if member["result"] in ("down", "left"):
|
||||
return "You're out of this fight"
|
||||
return fight, member, fight_time(fight, start, now)
|
||||
|
||||
|
||||
def watch_fight(world: dict, player: dict, now: float) -> None:
|
||||
"""You're looking: your manual abilities hold a while longer, and the fight's future is played
|
||||
again with them. Nothing that's already happened changes."""
|
||||
mine = _my_fight(world, player, now)
|
||||
if isinstance(mine, str):
|
||||
return
|
||||
fight, member, t = mine
|
||||
if not member["manual"] and not member["inputs"]:
|
||||
return
|
||||
watch = t + combat.WATCH * fight["pace"]
|
||||
if watch > member["watch"] + combat.STEP:
|
||||
member["watch"] = watch
|
||||
play_fight(fight)
|
||||
|
||||
|
||||
def _input(world: dict, player: dict, now: float, kind: str, *args) -> str | None:
|
||||
mine = _my_fight(world, player, now)
|
||||
if isinstance(mine, str):
|
||||
return mine
|
||||
fight, member, t = mine
|
||||
if t < 0:
|
||||
return "Still resting"
|
||||
at = math.ceil(t / combat.STEP - 1e-9) * combat.STEP # the next slice of the fight
|
||||
member["inputs"].append([at, kind, *args])
|
||||
member["watch"] = max(member["watch"], t + combat.WATCH * fight["pace"])
|
||||
play_fight(fight)
|
||||
return None
|
||||
|
||||
|
||||
def press(world: dict, player: dict, ability: str, now: float, target: str | None = None) -> str | None:
|
||||
"""Use one of your manual abilities: as soon as it's ready, if that's soon enough."""
|
||||
if ability not in combat.ABILITIES:
|
||||
return "No such ability"
|
||||
if ability not in player["manual"]:
|
||||
return "That one's on auto"
|
||||
return _input(world, player, now, "press", ability, target)
|
||||
|
||||
|
||||
def set_fight_target(world: dict, player: dict, target: str, now: float) -> str | None:
|
||||
"""Tap an enemy to fight it, or someone on your side to aim your heals at them."""
|
||||
return _input(world, player, now, "target", str(target))
|
||||
|
||||
|
||||
def set_manual(world: dict, player: dict, ability: str, manual: bool, now: float) -> str | None:
|
||||
"""Take an ability into your own hands (pressed, on its real cooldown, while you watch) or give it
|
||||
back to auto."""
|
||||
if ability not in combat.ABILITIES:
|
||||
return "No such ability"
|
||||
mine = player.setdefault("manual", [])
|
||||
if manual and ability not in mine:
|
||||
mine.append(ability)
|
||||
elif not manual and ability in mine:
|
||||
mine.remove(ability)
|
||||
fight = _my_fight(world, player, now)
|
||||
if isinstance(fight, str):
|
||||
return None
|
||||
fight, member, t = fight
|
||||
if t < 0:
|
||||
member["manual"] = list(mine)
|
||||
play_fight(fight)
|
||||
return None
|
||||
return _input(world, player, now, "mode", ability, manual)
|
||||
|
||||
|
||||
def play_fight(fight: dict) -> dict:
|
||||
|
|
@ -1966,11 +2141,12 @@ def play_fight(fight: dict) -> dict:
|
|||
|
||||
|
||||
def pack_xp(fight: dict, killed_only: bool = False) -> float:
|
||||
"""The pack's XP: on a loss, the enemies brought down and a third of the rest."""
|
||||
if not killed_only:
|
||||
return sum(foe["xp"] for foe in fight["pack"])
|
||||
"""The pack's XP: the enemies brought down, and on a loss a third of the rest (but never for an
|
||||
add that didn't join)."""
|
||||
kills = set(fight["kills"])
|
||||
return sum(foe["xp"] * (1 if f"e{i}" in kills else 1 / 3) for i, foe in enumerate(fight["pack"]))
|
||||
rest = 1 / 3 if killed_only else 0
|
||||
return sum(foe["xp"] * (1 if f"e{i}" in kills else 0 if foe.get("dormant") else rest)
|
||||
for i, foe in enumerate(fight["pack"]))
|
||||
|
||||
|
||||
# --- prayer -------------------------------------------------------------------
|
||||
|
|
@ -2370,7 +2546,7 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
|
|||
|
||||
if "enemy" in action:
|
||||
outing = player["action"]
|
||||
fight = fight or outing.pop("fight", None) or simulate_fight(world, player, action, rng)
|
||||
fight = fight or outing.pop("fight", None) or simulate_fight(world, player, action, rng, outing["start"])
|
||||
mine = member_of(fight, player["id"])
|
||||
xp = pack_xp(fight, killed_only=mine["result"] != "won") * mods["xp"]
|
||||
if player["prayers"] and not mine["pp"]:
|
||||
|
|
@ -2397,8 +2573,8 @@ def _complete(world: dict, player: dict, action: dict, now: float, rng: random.R
|
|||
# Knocked out, or the fight dragged on: nothing but some XP, and back to resting.
|
||||
summary["losses"] += 1
|
||||
gain_xp(world, player, "combat", xp, now, summary)
|
||||
outing["downs"] = outing.get("downs", 0) + 1
|
||||
reason = "Fell back: the fight dragged on" if mine["result"] == "fled" else \
|
||||
outing["downs"] = 0 if action.get("dungeon") else outing.get("downs", 0) + 1
|
||||
reason = None if action.get("dungeon") else "Fell back: the fight dragged on" if mine["result"] == "fled" else \
|
||||
f"Knocked out {KNOCKOUTS_TO_STOP} times in a row" if outing["downs"] >= KNOCKOUTS_TO_STOP else None
|
||||
if reason:
|
||||
drop_out(world, player, now, reason)
|
||||
|
|
@ -2534,7 +2710,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"])
|
||||
current["fight"] = simulate_fight(world, player, action, rng)
|
||||
current["fight"] = simulate_fight(world, player, action, rng, current["start"])
|
||||
duration = current["fight"]["duration"]
|
||||
else:
|
||||
duration = action_time(world, player, action)
|
||||
|
|
@ -2606,8 +2782,11 @@ def roll_loot(action: dict, rng: random.Random, share: float = 1.0) -> tuple[int
|
|||
def pack_loot(action: dict, fight: dict, rng: random.Random) -> tuple[int, dict]:
|
||||
"""Everything the pack carried."""
|
||||
gold, drops = 0, {}
|
||||
for foe in fight["pack"]:
|
||||
more, found = roll_loot(action, rng, foe["share"])
|
||||
kills = set(fight["kills"])
|
||||
for i, foe in enumerate(fight["pack"]):
|
||||
if f"e{i}" not in kills:
|
||||
continue
|
||||
more, found = roll_loot(ACTION_BY_ID[foe["loot"]] if foe.get("loot") else action, rng, foe["share"])
|
||||
gold += more
|
||||
for item, qty in found.items():
|
||||
drops[item] = drops.get(item, 0) + qty
|
||||
|
|
@ -2645,6 +2824,9 @@ def joinable(world: dict, player: dict) -> bool:
|
|||
if not current or "left" in current or "order" in current:
|
||||
return False
|
||||
action = ACTION_BY_ID[current["id"]]
|
||||
if action.get("dungeon"):
|
||||
party = world["parties"].get(current.get("party"))
|
||||
return not party or (not party.get("run", {}).get("pull") and len(party["members"]) < DUNGEON_SIZE)
|
||||
return "enemy" in action and not action.get("special")
|
||||
|
||||
|
||||
|
|
@ -2663,6 +2845,12 @@ def join_fight(world: dict, player: dict, uid: str, now: float) -> str | None:
|
|||
host = world["players"].get(uid)
|
||||
if not host or host is player:
|
||||
return "No one to join"
|
||||
run = party_of(world, host)
|
||||
if run and ACTION_BY_ID[run["action"]].get("dungeon") and run is not party_of(world, player):
|
||||
if run.get("run", {}).get("pull") or (run["fight"] and now >= run["start"] + run["fight"]["rest"] / run["fight"]["pace"]):
|
||||
return "The run has started"
|
||||
if len(run["members"]) >= DUNGEON_SIZE:
|
||||
return "The group is full"
|
||||
if not joinable(world, host):
|
||||
return f"{host['name']} isn't fighting"
|
||||
if offline_limit(world, host) <= now:
|
||||
|
|
@ -2742,7 +2930,18 @@ def process_party(world: dict, pid: str, now: float, rng: random.Random) -> None
|
|||
if world["parties"].get(pid) is not party:
|
||||
break
|
||||
members = [world["players"][uid] for uid in party["members"]]
|
||||
party["fight"] = draw_fight(world, members, action, rng)
|
||||
if action.get("dungeon"):
|
||||
# A run waits for anyone who's looked away: it's played together.
|
||||
away = [p for p in members if p["last_active"] < party["start"] - DUNGEON_AWAY]
|
||||
if away:
|
||||
party["start"] = max(party["start"], now)
|
||||
for player in members:
|
||||
player["action"]["start"] = party["start"]
|
||||
break
|
||||
party["fight"] = draw_pull(world, members, action, party.setdefault("run", {"pull": 0, "wipes": 0}),
|
||||
rng, party["start"])
|
||||
else:
|
||||
party["fight"] = draw_fight(world, members, action, rng, party["start"])
|
||||
end = party["start"] + party["fight"]["duration"]
|
||||
late = [player for player in members if offline_limit(world, player) < min(end, now)]
|
||||
if late:
|
||||
|
|
@ -2764,7 +2963,7 @@ def process_party(world: dict, pid: str, now: float, rng: random.Random) -> None
|
|||
player["action"]["start"] = party["start"]
|
||||
results = {m["id"]: m["result"] for m in fight["members"]}
|
||||
winners = [uid for uid in party["members"] if results.get(uid) == "won"]
|
||||
if action.get("personal_loot"):
|
||||
if action.get("personal_loot") and (not action.get("dungeon") or any(foe["boss"] for foe in fight["pack"])):
|
||||
shares = {uid: pack_loot(action, fight, rng) for uid in winners}
|
||||
else:
|
||||
shares = share_loot(pack_loot(action, fight, rng), winners, rng) if winners else {}
|
||||
|
|
@ -2778,6 +2977,105 @@ def process_party(world: dict, pid: str, now: float, rng: random.Random) -> None
|
|||
for other in members:
|
||||
if other is not player:
|
||||
together[other["name"]] = together.get(other["name"], 0) + 1
|
||||
if action.get("dungeon") and world["parties"].get(pid) is party:
|
||||
_advance_run(world, party, action, fight, members, end, rng)
|
||||
|
||||
|
||||
def draw_pull(world: dict, players: list, action: dict, run: dict, rng: random.Random, start: float) -> dict:
|
||||
"""The run's next pull, sized for the group, everyone rested first."""
|
||||
spec = DUNGEONS[action["dungeon"]]
|
||||
name, pack = spec["pulls"][run["pull"]]
|
||||
hp, hits = dungeon_scale(len(players))
|
||||
members, rest = [], 0.0
|
||||
for player in players:
|
||||
seconds, eaten = rest_plan(world, player)
|
||||
member = fight_member(world, player)
|
||||
member.update(eaten=eaten, hp_in=member["top"])
|
||||
rest = max(rest, seconds)
|
||||
members.append(member)
|
||||
fight = {"seed": rng.random(), "pace": speed(world), "rest": rest, "enemy": name, "members": members,
|
||||
"pack": [{**foe, "hp": max(1, round(foe["hp"] * hp)), "max_hit": max(1, round(foe["max_hit"] * hits)),
|
||||
"phases": [dict(phase) for phase in foe.get("phases", [])]} for foe in pack],
|
||||
"stage": {"dungeon": spec["name"], "pull": run["pull"] + 1, "of": len(spec["pulls"]), "wipes": run["wipes"]}}
|
||||
for member, player in zip(members, players):
|
||||
member["watch"] = fight_time(fight, start, player["last_active"] + combat.WATCH)
|
||||
return play_fight(fight)
|
||||
|
||||
|
||||
def _advance_run(world: dict, party: dict, action: dict, fight: dict, members: list, now: float, rng: random.Random) -> None:
|
||||
"""After a pull: on to the next, or the same again after a wipe. Clearing it or wiping too often ends it."""
|
||||
spec, run = DUNGEONS[action["dungeon"]], party["run"]
|
||||
names = ", ".join(p["name"] for p in members)
|
||||
if fight["result"] == "won":
|
||||
run["pull"] += 1
|
||||
run["wipes"] = 0
|
||||
if run["pull"] < len(spec["pulls"]):
|
||||
return
|
||||
log(world, now, "party", f"{names} cleared {spec['name']}!")
|
||||
for player in members:
|
||||
_clear_bonus(world, player, spec, now, rng)
|
||||
reason = f"Cleared {spec['name']}"
|
||||
else:
|
||||
run["wipes"] += 1
|
||||
if run["wipes"] < DUNGEON_WIPES:
|
||||
return
|
||||
reason = f"Wiped {DUNGEON_WIPES} times at {spec['pulls'][run['pull']][0]}"
|
||||
log(world, now, "party", f"{names}: {reason.lower()} in {spec['name']}")
|
||||
del world["parties"][party["id"]]
|
||||
for player in members:
|
||||
stop(player, reason)
|
||||
player.setdefault("away", empty_summary())["stopped"] = reason
|
||||
|
||||
|
||||
def _clear_bonus(world: dict, player: dict, spec: dict, now: float, rng: random.Random) -> None:
|
||||
"""What clearing a run pays everyone in it: gold, one of the area's trophies and two of its reagents."""
|
||||
summary = player.setdefault("away", empty_summary())
|
||||
player["gold"] += spec["bonus_gold"]
|
||||
summary["gold"] += spec["bonus_gold"]
|
||||
found = [trophy(rng.choice(list(STYLES)), spec["tier"]), *(reagent(rng.choice(list(REAGENT_NOUNS)), spec["tier"])
|
||||
for _ in range(2))]
|
||||
for item in found:
|
||||
kept = bank_add(world, player, item, 1, now, summary)
|
||||
if kept:
|
||||
summary["items"][item] = summary["items"].get(item, 0) + kept
|
||||
|
||||
|
||||
def run_of(world: dict, player: dict) -> dict | None:
|
||||
"""Where your group's dungeon run stands, if you're in one."""
|
||||
party = party_of(world, player)
|
||||
if not party or not ACTION_BY_ID[party["action"]].get("dungeon"):
|
||||
return None
|
||||
spec = DUNGEONS[ACTION_BY_ID[party["action"]]["dungeon"]]
|
||||
run = party.get("run", {"pull": 0, "wipes": 0})
|
||||
away = [world["players"][uid]["name"] for uid in party["members"]
|
||||
if world["players"][uid]["last_active"] < party["start"] - DUNGEON_AWAY]
|
||||
return {"dungeon": spec["name"], "pull": run["pull"] + 1, "of": len(spec["pulls"]), "wipes": run["wipes"],
|
||||
"next": spec["pulls"][min(run["pull"], len(spec["pulls"]) - 1)][0], "away": away,
|
||||
"leader": party["members"][0], "size": len(party["members"])}
|
||||
|
||||
|
||||
def pull_now(world: dict, player: dict, now: float) -> str | None:
|
||||
"""The leader cuts the rest short: everyone goes in as healed as they are by now."""
|
||||
party = party_of(world, player)
|
||||
if not party or not ACTION_BY_ID[party["action"]].get("dungeon"):
|
||||
return "Not in a dungeon"
|
||||
if party["members"][0] != player["id"]:
|
||||
return "Only the leader pulls"
|
||||
fight = party["fight"]
|
||||
if not fight:
|
||||
return "Nothing to pull yet"
|
||||
rested = (now - party["start"]) * fight["pace"]
|
||||
if rested >= fight["rest"]:
|
||||
return "Already fighting"
|
||||
rested = math.ceil(rested / combat.STEP) * combat.STEP
|
||||
share = rested / fight["rest"] if fight["rest"] else 1
|
||||
for member in fight["members"]:
|
||||
member["hp_in"] = member["start_hp"] + (member["hp_in"] - member["start_hp"]) * share
|
||||
member["eaten"] = {food: int(count * share) for food, count in member["eaten"].items() if int(count * share)}
|
||||
member["watch"] += fight["rest"] - rested
|
||||
fight["rest"] = rested
|
||||
play_fight(fight)
|
||||
return None
|
||||
|
||||
|
||||
def catch_up(world: dict, now: float, rng: random.Random | None = None) -> bool:
|
||||
|
|
@ -4728,6 +5026,7 @@ BUILDING_GLYPHS = {
|
|||
ACTION_GLYPHS = {
|
||||
"fight_wolves": ("wolf-head", "#9aa1ab"), "fight_scouts": ("hooded-assassin", "#a08060"),
|
||||
"fight_ruins": ("castle-ruins", "#a8a29e"), "fight_stronghold": ("castle", "#8a8f98"),
|
||||
"dungeon_watchtower": ("watchtower", "#d1a46a"),
|
||||
"fight_green_dragon": ("dragon-head", "#5fae5a"), "fight_blue_dragon": ("dragon-head", "#4f86d6"),
|
||||
"fight_red_dragon": ("dragon-head", "#d0503c"), "fight_black_dragon": ("dragon-head", "#8a8f98"),
|
||||
"sabotage": ("fire-bomb", "#e0703a"), "drill_militia": ("sword-brandish", "#e0a050"), "recon_roads": ("spyglass", "#6f8fec"), "recon_camps": ("spyglass", "#8a7fd0"),
|
||||
|
|
@ -4754,6 +5053,8 @@ ABILITY_GLYPHS = {
|
|||
"heavy_slam": ("punch-blast", "#e06a6a"), "lunge": ("pierced-body", "#e06a6a"),
|
||||
"hex_bolt": ("fireball", "#a77be8"), "sweep": ("spinning-blades", "#e06a6a"),
|
||||
"fire_breath": ("dragon-breath", "#ff8a3a"), "tail_swipe": ("spiked-tail", "#c08850"),
|
||||
"stone_curse": ("evil-hand", "#a77be8"), "ground_pound": ("earth-crack", "#c08850"),
|
||||
"shatter": ("shattered-glass", "#d1a46a"), "shriek": ("sonic-shout", "#a77be8"),
|
||||
}
|
||||
PRAYER_GLYPHS = {"thick_skin": ("shield", "#a8a29e"), "burst_of_strength": ("biceps", "#e06a6a"),
|
||||
"rapid_heal": ("heart-plus", "#e06a8a"), "steel_skin": ("armor-upgrade", "#d4dae2"),
|
||||
|
|
@ -4882,6 +5183,18 @@ ORDER_RECIPES, ORDER_RAW = _order_sources()
|
|||
# What changed, shown on the Patch notes page. Newest first; every shipped change adds a
|
||||
# line here (new / balance / fix), and a new release a new entry on top.
|
||||
PATCH_NOTES = [
|
||||
{"date": "2026-10-24", "title": "Active play and dungeons", "notes": [
|
||||
("new", "Active play: tap AUTO on an ability to take it yourself (YOU), then press it (tap, or keys 1–9) on its "
|
||||
"real cooldown instead of auto's 1.5×. Look away for 12s and it's back on auto."),
|
||||
("new", "Tap an enemy to fight it, or a friend to aim your heals at them."),
|
||||
("new", "Dungeon: Watchtower Depths (combat 20, 2–4 players), 8 pulls in order. Trash falls on auto; the Clay "
|
||||
"colossus, Harpy matriarch and Watchtower ogre need your own hands: interrupt Stone Curse and Shriek, "
|
||||
"brace for Ground Pound, beat the enrage timer. Start it and wait for others to join before the first pull."),
|
||||
("new", "A wipe repeats the pull, 3 on one pull ends the run. The run waits while anyone looks away for 90s. "
|
||||
"The leader can pull early."),
|
||||
("new", "Dungeon rewards: every boss gives everyone their own loot (mini-bosses 5× their race's odds). "
|
||||
"Clearing pays 100 gold, a trophy and 2 reagents each."),
|
||||
]},
|
||||
{"date": "2026-10-24", "title": "Livelier frontier", "notes": [
|
||||
("balance", "A threat lands every 2 days (±2h), and the next one is rumoured as soon as the last lands. "
|
||||
"Day 1: scout, drill, build. Day 2: the camp is found (intel III opens 24h out), with the exact hour."),
|
||||
|
|
@ -4960,8 +5273,8 @@ def content() -> dict:
|
|||
"potion_charges": POTION_CHARGES, "order_recipes": ORDER_RECIPES, "order_raw": ORDER_RAW,
|
||||
"order_limit": ORDER_LIMIT, "order_max": ORDER_MAX, "input_share": INPUT_SHARE, "problems": PROBLEMS, "fittings": list(FITTING_BY_ID.values()),
|
||||
"equip_slots": EQUIP_SLOTS, "class_speed": CLASS_SPEED, "rest_rate": REST_RATE, "bite": BITE,
|
||||
"abilities": combat.ABILITIES, "enemy_abilities": combat.ENEMY_ABILITIES, "unlock": combat.UNLOCK, "auto_cd": combat.AUTO_CD,
|
||||
"weapon_kits": combat.WEAPON_KITS, "group_size": GROUP_SIZE, "trade_ability": combat.TRADE_ABILITY, "potion_below": combat.POTION_BELOW,
|
||||
"abilities": combat.ABILITIES, "enemy_abilities": combat.ENEMY_ABILITIES, "unlock": combat.UNLOCK, "auto_cd": combat.AUTO_CD, "watch": combat.WATCH, "gcd": combat.GCD,
|
||||
"weapon_kits": combat.WEAPON_KITS, "group_size": GROUP_SIZE, "dungeon_size": DUNGEON_SIZE, "dungeon_wipes": DUNGEON_WIPES, "trade_ability": combat.TRADE_ABILITY, "potion_below": combat.POTION_BELOW,
|
||||
"styles": STYLES, "damage_types": DAMAGE_TYPES, "weights": WEIGHTS, "vulnerable": VULNERABLE,
|
||||
"resistant": RESISTANT, "jewel_stats": JEWEL_STATS, "stations": list(STATIONS), "class_style": CLASS_STYLE, "block_chance": BLOCK_CHANCE,
|
||||
"regen_per_hour": REGEN_PER_HOUR,
|
||||
|
|
|
|||
|
|
@ -899,14 +899,21 @@ function combatFrame() {
|
|||
const encounter = a && C.actions.find((x) => x.id === a.id);
|
||||
const waiting = a?.waiting;
|
||||
const others = f ? f.members.filter((m) => m.id !== S.uid) : [];
|
||||
const unit = (id, name, foe = false, extra = "") => `<div class="fighter ${foe ? "foe-unit" : id === S.uid ? "" : "ally"}" data-u="${esc(id)}">
|
||||
const unit = (id, name, foe = false, extra = "") => `<div class="fighter ${foe ? "foe-unit" : id === S.uid ? "" : "ally"} ${f ? "aimable" : ""}"
|
||||
data-u="${esc(id)}" ${f ? `data-aim="${esc(id)}" title="${foe ? "Tap to fight this one" : "Tap to aim your heals here"}"` : ""}>
|
||||
<div class="fighter-name"><b>${name}${extra}</b><span class="small muted"><span data-u-status></span> ${foe ? "" : HEART} <span data-u-hp>${id === S.uid ? `${fmt(me.hp)} / ${me.max_hp}` : ""}</span></span></div>
|
||||
<div class="bar hp ${foe ? "foe" : "you"}"><span data-u-bar${id === S.uid ? ` data-live-hpbar style="width:${me.hp / me.max_hp * 100}%"` : ""}></span></div>
|
||||
<div class="bar swing"><span data-u-swing></span></div>
|
||||
<div class="cast small" data-u-cast></div>
|
||||
</div>`;
|
||||
const you = unit(S.uid, esc(me.name)) + others.map((m) => unit(m.id, esc(m.name))).join("");
|
||||
let foe = `<div class="foe-idle muted">${waiting ? `👥 Waiting for a group · 1 / ${C.group_size}` : "Not fighting."}</div>`;
|
||||
const run = a?.run;
|
||||
// A dungeon run: where you are in it, and (for the leader, while resting) the button to go in early.
|
||||
const stage = run ? `<div class="run-head"><b>${esc(run.dungeon)}</b>
|
||||
<span class="muted small">${run.pull} / ${run.of} · ${esc(run.next)}${run.wipes ? ` · wipes ${run.wipes} / ${C.dungeon_wipes}` : ""}</span>
|
||||
${f && run.leader === S.uid ? `<button class="btn sm" data-pull data-pull-btn>Pull now</button>` : ""}</div>` : "";
|
||||
let foe = `<div class="foe-idle muted">${run?.away?.length ? `Waiting for ${run.away.map(esc).join(", ")} to come back`
|
||||
: run ? `Ready: ${esc(run.next)}` : waiting ? `👥 Waiting for a group · 1 / ${C.group_size}` : "Not fighting."}</div>`;
|
||||
if (f) {
|
||||
const mine = C.styles[me.fighter.style || "sword"]?.damage || me.fighter.damage;
|
||||
const traits = (v) => `<span class="v-attack">${v.attack.map((k) => dmgIcon(k, "attack")).join("")}</span>
|
||||
|
|
@ -918,21 +925,27 @@ function combatFrame() {
|
|||
}
|
||||
return `<div class="panel fight combat-frame" ${f ? `data-fight="${a.start}"` : ""}>
|
||||
<div class="you-side">${you}${abilityBar(me)}${prayerBar(me)}${paperDoll(me, true)}</div>
|
||||
<div class="foe-side">${foe}</div>
|
||||
<div class="foe-side">${stage}${foe}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Your abilities, from your weapon and your trade, on auto: each lights up as it's used and shows its
|
||||
// cooldown. The weapon's ones still to come are shown dimmed with the level they're learned at.
|
||||
// Your abilities, from your weapon and your trade: each lights up as it's used and shows its cooldown.
|
||||
// On AUTO a rule uses it on a longer cooldown; on YOU you press it (tap, or its number key) on the real
|
||||
// one, for as long as you're watching. The weapon's ones still to come are dimmed with their level.
|
||||
function abilityBar(me) {
|
||||
const known = me.fighter.abilities, style = me.fighter.style || "sword";
|
||||
const shield = !!me.equip.shield;
|
||||
const tile = (id, on) => {
|
||||
const ab = C.abilities[id];
|
||||
const ab = C.abilities[id], manual = on && me.manual.includes(id);
|
||||
const why = on ? "" : ab.needs === "shield" && !shield && me.levels.combat >= ab.level ? "needs a shield" : `combat ${ab.level}`;
|
||||
const tip = `${ab.name}: ${ab.tip}${ab.cd ? ` · ${ab.cd * C.auto_cd}s on auto` : ""}${why ? ` · ${why}` : ""}`;
|
||||
return `<span class="ab ${on ? "" : "locked"}" data-ab="${id}" title="${esc(tip)}">${ab.icon}<span class="ab-cd" data-ab-cd></span>
|
||||
${on ? "" : `<span class="ab-lv">${why === "needs a shield" ? "🛡️" : ab.level}</span>`}</span>`;
|
||||
const cd = ab.cd ? ` · ${manual ? ab.cd : ab.cd * C.auto_cd}s cooldown` : "";
|
||||
const tip = `${ab.name}: ${ab.tip}${cd}${why ? ` · ${why}` : ""}`;
|
||||
return `<span class="ab ${on ? (manual ? "manual" : "auto") : "locked"}" data-ab="${id}">
|
||||
<button class="ab-press" data-press="${id}" title="${esc(tip)}" ${on ? "" : "disabled"}>${ab.icon}<span class="ab-cd" data-ab-cd></span>
|
||||
${on ? "" : `<span class="ab-lv">${why === "needs a shield" ? glyph("checked-shield", "#d4dae2") : ab.level}</span>`}</button>
|
||||
${on ? `<button class="ab-mode" data-mode="${id}" title="${esc(manual ? `Yours: press it while you watch. Look away ${C.watch}s and it's back on auto. Tap for auto.`
|
||||
: `Auto: used by itself on a ${C.auto_cd}× cooldown. Tap to take it yourself.`)}">${manual ? "YOU" : "AUTO"}</button>` : ""}
|
||||
</span>`;
|
||||
};
|
||||
const kit = C.weapon_kits[style].map((id) => tile(id, known.includes(id)));
|
||||
const trade = C.trade_ability[me.cls];
|
||||
|
|
@ -976,7 +989,9 @@ function fightLine(e, f) {
|
|||
case "cc": return `<li class="good">${what} ${name(tgt)} held</li>`;
|
||||
case "free": return `<li class="muted">${name(tgt)} breaks free</li>`;
|
||||
case "die": return `<li class="good">${name(tgt)} falls</li>`;
|
||||
case "enrage": return `<li class="bad">${C.abilities.unstoppable.icon} ${name(src)} enrages</li>`;
|
||||
case "enrage": return `<li class="bad">${C.abilities.unstoppable.icon} ${name(src)} ${label === "hard" ? "<b>loses patience</b>" : "enrages"}</li>`;
|
||||
case "yell": return `<li class="warn">${name(src)}: <b>“${esc(label)}”</b></li>`;
|
||||
case "join": return `<li class="warn">${name(src)} joins the fight</li>`;
|
||||
case "wind": return `<li class="good">${C.prayers.find((p) => p.id === "second_wind").icon} Second wind! ${name(src)}</li>`;
|
||||
case "down": return `<li class="bad">${C.abilities.concuss.icon} <b>${src === S.uid ? "You're knocked out" : `${name(src)} is knocked out`}</b></li>`;
|
||||
case "left": return `<li class="muted">${name(src)} left</li>`;
|
||||
|
|
@ -984,12 +999,21 @@ function fightLine(e, f) {
|
|||
return "";
|
||||
}
|
||||
|
||||
// Whether a use of an ability came from a press (within the queue window before it), so it ran on its
|
||||
// real cooldown.
|
||||
function pressedNear(f, uid, ab, time) {
|
||||
const m = f.members.find((x) => x.id === uid);
|
||||
return (m.inputs || []).some((i) => i[1] === "press" && i[2] === ab && time >= (f.rest + i[0]) / f.pace - 0.01
|
||||
&& time <= (f.rest + i[0] + 1.5) / f.pace + 0.01);
|
||||
}
|
||||
|
||||
// Where everyone in the fight stands at `t` seconds into it: HP, last swing, cast under way, out or not,
|
||||
// and when each of your abilities was last used.
|
||||
function fightState(f, t) {
|
||||
const rest = f.rest / f.pace, st = {};
|
||||
for (const m of f.members) st[m.id] = { hp: m.hp_in, top: m.top, from: m.at / f.pace + rest, every: m.interval, swung: null, cast: null, out: null, cds: {} };
|
||||
f.pack.forEach((foe, i) => { st[`e${i}`] = { hp: foe.hp, top: foe.hp, from: rest, every: foe.interval / f.pace, swung: null, cast: null, out: null, held: false, enraged: false }; });
|
||||
for (const m of f.members) st[m.id] = { hp: m.hp_in, top: m.top, from: m.at / f.pace + rest, every: m.interval, swung: null,
|
||||
cast: null, out: null, cds: {}, manualAt: {}, pressed: new Set((m.inputs || []).filter((i) => i[1] === "press").map((i) => i[2])) };
|
||||
f.pack.forEach((foe, i) => { st[`e${i}`] = { hp: foe.hp, top: foe.hp, from: rest, every: foe.interval / f.pace, swung: null, cast: null, out: null, held: false, enraged: false, hidden: !!foe.dormant }; });
|
||||
let shown = -1;
|
||||
while (shown + 1 < f.events.length && f.events[shown + 1][0] <= t) {
|
||||
const [time, src, tgt, kind, , hp, label] = f.events[++shown];
|
||||
|
|
@ -1001,7 +1025,11 @@ function fightState(f, t) {
|
|||
who.cast = { label, from: time, until: time + spec.cast / f.pace };
|
||||
}
|
||||
const ab = C.abilities[label];
|
||||
if (who?.cds && ab && (ab.cast ? kind === "cast" : kind === "use" || SWINGS.has(kind))) who.cds[label] = time;
|
||||
if (who?.cds && ab && (ab.cast ? kind === "cast" : kind === "use" || SWINGS.has(kind)) && who.cds[label] !== time) {
|
||||
who.cds[label] = time;
|
||||
who.manualAt[label] = who.pressed.has(`${label}`) && pressedNear(f, src, label, time);
|
||||
}
|
||||
if (who?.cds && tgt?.[0] === "e" && (SWINGS.has(kind) || kind === "cast" || kind === "use")) who.aim = tgt;
|
||||
if (who && kind === "heal" && label === "potion") who.drank = true;
|
||||
if (them && (kind === "stop" || kind === "cc")) them.cast = null;
|
||||
if (them && kind === "cc") them.held = true;
|
||||
|
|
@ -1009,6 +1037,7 @@ function fightState(f, t) {
|
|||
if (them && kind === "die") them.out = "dead";
|
||||
if (who && (kind === "down" || kind === "left")) who.out = kind;
|
||||
if (who && kind === "enrage") who.enraged = true;
|
||||
if (who && kind === "join") { who.hidden = false; who.from = time; }
|
||||
}
|
||||
return { st, shown };
|
||||
}
|
||||
|
|
@ -1032,11 +1061,17 @@ function animateFight(now) {
|
|||
const panel = $("[data-fight]");
|
||||
if (!panel) return;
|
||||
panel.classList.toggle("waiting", resting);
|
||||
const pull = panel.querySelector("[data-pull-btn]");
|
||||
if (pull) pull.hidden = !resting;
|
||||
const pick = [...(mine.inputs || [])].reverse().find((i) => i[1] === "target" && (f.rest + i[0]) / f.pace <= t && i[2][0] === "e");
|
||||
const aim = pick && st[pick[2]] && !st[pick[2]].out ? pick[2] : me.aim && st[me.aim] && !st[me.aim].out ? me.aim : null;
|
||||
for (const el of panel.querySelectorAll("[data-u]")) {
|
||||
const u = st[el.dataset.u];
|
||||
if (!u) continue;
|
||||
el.classList.toggle("aimed", el.dataset.u === aim && !over);
|
||||
const gone = !!u.out || over;
|
||||
el.classList.toggle("out", !!u.out);
|
||||
el.hidden = !!u.hidden;
|
||||
el.querySelector("[data-u-bar]").style.width = `${Math.max(0, u.hp / u.top * 100)}%`;
|
||||
el.querySelector("[data-u-hp]").textContent = u.out === "left" ? "left" : t < u.from && !resting ? "joining…" : `${fmt(u.hp)} / ${fmt(u.top)}`;
|
||||
const status = u.out === "dead" ? "" : u.out === "down" ? "concuss" : u.held ? "thornbind" : u.enraged ? "unstoppable" : "";
|
||||
|
|
@ -1061,7 +1096,7 @@ function animateFight(now) {
|
|||
const id = el.dataset.ab;
|
||||
if (id === "potion") { el.classList.toggle("spent", !!me.drank); continue; }
|
||||
const ab = C.abilities[id], used = me.cds?.[id];
|
||||
const cd = (ab.cd || 0) * C.auto_cd / f.pace;
|
||||
const cd = (ab.cd || 0) * (me.manualAt?.[id] ? 1 : C.auto_cd) / f.pace;
|
||||
const leftCd = used == null || over ? 0 : Math.max(0, used + cd - t);
|
||||
el.querySelector("[data-ab-cd]").style.height = `${cd ? leftCd / cd * 100 : 0}%`;
|
||||
el.classList.toggle("fired", used != null && t - used < 0.6);
|
||||
|
|
@ -1181,7 +1216,8 @@ function actionCard(a) {
|
|||
let extra = "";
|
||||
if (a.enemy) {
|
||||
const o = me.fight_odds[a.id], e = a.enemy;
|
||||
const own = (a.together ? `<span class="tag warn" title="Fought by ${C.group_size} or more: alone, you wait for someone to join">👥 Group</span> ` : "")
|
||||
const own = (a.dungeon ? `<span class="tag warn" title="${esc(`${a.pulls.length} pulls: ${a.pulls.join(" · ")}. Trash falls on auto; the bosses take your own hands. ${C.dungeon_wipes} wipes on a pull and the run's over.`)}">🏰 Dungeon · ${C.group_size}–${C.dungeon_size}</span> `
|
||||
: a.together ? `<span class="tag warn" title="Fought by ${C.group_size} or more: alone, you wait for someone to join">👥 Group</span> ` : "")
|
||||
+ (a.personal_loot ? `<span class="tag good" title="Everyone in the fight gets their own loot">🎁 Own loot</span>` : "");
|
||||
extra = `${e.boss || own ? `<div>${e.boss ? `<span class="tag warn">Boss</span> ` : ""}${own}</div>` : ""}${variantRows(e, o)}${fightingHere(a, why)}`;
|
||||
if (a.gold) io += `<span class="item-chip" title="Gold: ${a.gold[0]}–${a.gold[1]} a kill">${COIN} ${a.gold[0]}–${a.gold[1]}</span>`;
|
||||
|
|
@ -2118,7 +2154,7 @@ function loop() {
|
|||
// --- events ---------------------------------------------------------------
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const t = e.target.closest("button, [data-menu], [data-close]");
|
||||
const t = e.target.closest("button, [data-menu], [data-close], [data-aim]");
|
||||
if (!t) return;
|
||||
const d = t.dataset;
|
||||
if (t.matches("[data-battle-skip]")) return playBattle.finish?.();
|
||||
|
|
@ -2137,6 +2173,14 @@ document.addEventListener("click", (e) => {
|
|||
render(); window.scrollTo(0, 0); return;
|
||||
}
|
||||
if (d.firewood) { firewood = Number(d.firewood); remember("firewood", d.firewood); return render(); }
|
||||
if (d.press) {
|
||||
if (!S.me.manual.includes(d.press)) return toast("On auto: tap AUTO to take it yourself");
|
||||
t.closest(".ab")?.classList.add("queued");
|
||||
return act("/api/press", { id: d.press });
|
||||
}
|
||||
if (d.mode) return act("/api/manual", { id: d.mode, on: !S.me.manual.includes(d.mode) });
|
||||
if ("pull" in d) return act("/api/pull", {});
|
||||
if (d.aim) return d.aim === S.uid ? undefined : act("/api/target", { id: d.aim });
|
||||
if (d.start) return act("/api/action", { id: d.start });
|
||||
if (d.once) return act("/api/action", { id: d.once, once: true });
|
||||
if ("stop" in d) return act("/api/stop", {});
|
||||
|
|
@ -2253,6 +2297,15 @@ document.addEventListener("change", (e) => {
|
|||
|
||||
$("#modal").addEventListener("click", (e) => { if (e.target.id === "modal") e.target.hidden = true; });
|
||||
|
||||
// Number keys press your abilities, in the bar's order (the ones you know).
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.target.matches("input, textarea, select") || e.ctrlKey || e.metaKey || e.altKey) return;
|
||||
const n = Number(e.key);
|
||||
if (!n) return;
|
||||
const tile = document.querySelectorAll(".ability-bar .ab:not(.locked) [data-press]")[n - 1];
|
||||
if (tile) { e.preventDefault(); tile.click(); }
|
||||
});
|
||||
|
||||
document.addEventListener("submit", async (e) => {
|
||||
const form = e.target.closest("[data-form]");
|
||||
if (!form) return;
|
||||
|
|
|
|||
|
|
@ -68,11 +68,13 @@
|
|||
<symbol id="dragon-spiral" viewBox="0 0 512 512"><path fill="currentColor" d="M215.125 17.188C142.557 18.134 72.68 64.075 40.187 132.25c-56.91 119.42 7.05 260.175 122.313 314.406c53.513 25.185 114.67 31.57 172.563 19.78l9.625 21.845l152.75-34.03c-.64-13.024-1.892-29.087-3.407-45.97a41.7 41.7 0 0 1-14.655 15.5c-19.667 12.21-45.51 6.137-57.72-13.53c-12.208-19.667-6.165-45.51 13.5-57.72a41.77 41.77 0 0 1 21.69-6.28c12.278-.118 24.32 5.173 32.53 15a5051 5051 0 0 0-8.03-70.97c-58.49 31.19-115.34 47.06-174.47 60.408v-.375c-46.538 12.515-126.428 1.26-156.5-30.22c18.292 40.602 81.708 65.04 149.313 65.844l8.343 18.938c-24.334 9.436-50.313 14.416-76.374 14.313l-14.22-17.97L194.72 415.5a188.3 188.3 0 0 1-37.69-11.72l-9.56-23.186l-23.876 4.656a192 192 0 0 1-20.375-16.28l-.564-24.126l-26.344-6.5a179 179 0 0 1-10.187-16.406L74.97 295l-23.657-9.47A164 164 0 0 1 47 264.72l13.875-21.564l-14.125-21.844c.057-.448.095-.894.156-1.343c.87-6.424 2.175-12.74 3.844-18.94l20.188-13.25l-6.594-20.686c3.268-6 6.91-11.757 10.906-17.25l25.688-5.406l3.093-24.625c5.754-4.555 11.836-8.695 18.19-12.313l22.718 6.78l9.812-20.124c9.6-2.523 19.57-3.957 29.78-4.187l20.845 15.25l20.063-9.376c50.687 16.437 88.647 65.967 80.53 121.594c-2.905 19.914-12.198 38.33-25.437 52.437c3.03-20.242-10.88-43.178-35.092-55c-28.472-13.902-60.35-7.162-71.188 15.03c-10.836 22.194 3.466 51.443 31.938 65.345a73 73 0 0 0 14.125 5.188c57.96 18.97 121.378-12.46 147.593-66.063c36.94-75.536-3.372-165.134-76.125-200.22c-23.733-11.444-48.587-16.803-73.186-16.968c-1.153-.007-2.317-.015-3.47 0zM396.47 348.375l-1.626 37.188l-60.53-10.657z"/></symbol>
|
||||
<symbol id="drill" viewBox="0 0 512 512"><path fill="currentColor" d="M190.72 17.688c-1.236 0-2.475 0-3.72.03c-1.184.03-2.37.096-3.563.157c-.915.047-1.83.093-2.75.156q-2.316.154-4.656.376c-39.78 3.82-84.005 22.38-113.593 53.938c-.914.975-1.807 1.97-2.687 2.97c-.63.715-1.26 1.427-1.875 2.155c-.353.418-.684.858-1.03 1.28c-.54.653-1.07 1.307-1.595 1.97c-.068.085-.15.162-.22.25a133 133 0 0 0-4.936 6.655c-.18.26-.354.52-.532.78a137 137 0 0 0-2.343 3.564c-.067.1-.124.21-.19.31a146 146 0 0 0-2.53 4.095c-.004.008.004.024 0 .03a142 142 0 0 0-2.906 5.157c-.398.744-.772 1.5-1.157 2.25c-.35.685-.69 1.374-1.03 2.063c-.048.097-.11.185-.157.28a155 155 0 0 0-3.281 7.125c-.423.985-.82 1.98-1.22 2.97a159 159 0 0 0-1.344 3.406a168 168 0 0 0-1.25 3.438c-.004.012-.027.018-.03.03c-.4 1.137-.753 2.265-1.126 3.407c-.38 1.165-.74 2.332-1.094 3.5c-.31 1.032-.647 2.06-.937 3.095c-.138.488-.244.98-.376 1.47a172 172 0 0 0-1.22 4.78c-.136.572-.275 1.146-.405 1.72c-.05.207-.11.415-.157.624a172.4 172.4 0 0 0-4.157 37.155c-.03 11.863 1.123 23.42 3.532 34.094c.023.103.038.207.062.31c.107.47.232.94.344 1.407c.19.794.388 1.593.593 2.375c.35 1.33.736 2.642 1.125 3.938c.023.073.04.146.063.22c.38 1.254.768 2.494 1.188 3.718c.384 1.12.8 2.22 1.218 3.312c.1.26.182.524.282.78c.38.972.78 1.93 1.188 2.876c.248.575.492 1.154.75 1.72c.297.65.596 1.3.906 1.936c.087.18.194.354.282.532c.545 1.1 1.102 2.19 1.687 3.25a76 76 0 0 0 1.906 3.25c.514.828 1.055 1.64 1.595 2.437c.152.225.284.467.438.69a66 66 0 0 0 2.187 2.968c.248.318.497.625.75.937c.272.336.535.67.813 1c.2.238.42.454.624.688c.44.504.89 1.012 1.344 1.5l.5.53c.486.51.97 1.013 1.47 1.5a53 53 0 0 0 1.656 1.532c.066.06.12.128.188.188c.29.257.58.498.875.75c.218.186.433.38.655.562c.402.332.807.65 1.22.97c.466.362.926.714 1.405 1.06c.193.14.4.27.594.408c.468.33.927.685 1.406 1c.108.07.235.117.344.187c.7.452 1.4.894 2.125 1.313l.124.093l.125.063l99.53 53.06c-11.733-14.704-21.437-29.708-29.125-44.905L51.25 227.187c-6.087-12.578-9.955-29.526-9.906-48.25c.02-8.307.757-16.89 2.25-25.468l63.718 44.624c-.48-5.756-.666-11.52-.593-17.28c.623-49.355 21.55-97.852 60.593-142.408a149 149 0 0 1 10.5-1.406c5.788-.556 11.42-.72 16.78-.53c-45.482 45.9-68.563 95.274-69.187 144.56a164 164 0 0 0 2.688 31.626l101.72 71.22a156 156 0 0 1-1.064-20.032c.606-49.25 24.488-97.445 68.625-140.22l-34.25-64.25c-2.42-8.248-6.81-15.287-12.5-21.093a58 58 0 0 0-2.156-2.093a57 57 0 0 0-4.282-3.562c-.125-.093-.25-.19-.375-.28a58 58 0 0 0-2.188-1.532c-.106-.072-.206-.15-.313-.22a61 61 0 0 0-2.312-1.468c-.084-.05-.166-.106-.25-.156a62 62 0 0 0-2.5-1.407c-.06-.033-.126-.062-.188-.094c-.87-.464-1.76-.89-2.656-1.314c-.033-.016-.06-.046-.094-.062c-.93-.44-1.858-.85-2.812-1.25a78 78 0 0 0-3.53-1.375c-.252-.092-.5-.194-.75-.282c-.072-.025-.15-.038-.22-.063c-.66-.23-1.33-.472-2-.688c-.104-.033-.208-.06-.313-.093a88 88 0 0 0-4.718-1.344c-.586-.15-1.19-.298-1.782-.438c-.22-.052-.436-.105-.657-.156c-.55-.126-1.097-.257-1.655-.375c-.258-.053-.523-.103-.78-.155c-.527-.106-1.065-.214-1.595-.313c-6.937-1.284-14.234-1.876-21.78-1.875zm174.905 95.624l36.938 157.282l87.062 181.03l-11.563-92.374l14.844-124.53l-27.062 54.343L436 131.03l.75 130.5zm-59.094 27.532c-38.92 39.08-58.576 81.213-59.092 123.22c-.143 11.61 1.172 23.29 4 34.967l78.125 54.75c-.123-2.452-.185-4.92-.157-7.374c.365-32.055 13.53-63.464 37.78-91.812l-60.655-113.75zm70.126 131.562c-18.83 23.913-28.277 49.114-28.562 74.22c-.082 7.153.6 14.333 2.03 21.53l60.72 42.53c.746-16.27 5.657-32.273 14.312-47.31zm-215.28 17.313c12.68 18.31 28.792 36.394 48.53 54.092l58.53 31.22c-9.277-12.315-16.937-24.87-22.967-37.594l-84.095-47.72zM54.655 318l46.657 54.094l71.874 14L54.657 318zm93.28 25.5l148.314 99.72l-148.313-15l144.75 36.217l-52.875 19.844l115-5.06l101.907 12.75zm129.408 12.03c10.717 15.65 24.408 31.098 41.22 46.22l32.623 17.375c-6.16-9.804-11.007-19.812-14.562-29.938l-59.28-33.656zm158.594 28.064c-4.23 10.11-6.4 20.418-6.5 30.72c-.194 19.67 7.015 39.694 23.03 58.81l16.438 8.75l27.063 14.438l-14.408-27.093l-45.625-85.626zm-71.625 21.312c7.277 12.51 17.08 24.897 29.593 37l25.625 13.656c-3.39-7.683-5.788-15.514-7.217-23.406z"/></symbol>
|
||||
<symbol id="eagle-head" viewBox="0 0 512 512"><path fill="currentColor" d="M242.9 20.46c6.7 19.75 19.7 41.39 4 50.44c-38.6 22.04-81.4 41.5-106.2 90.7C103.3 235.7 91.69 412 29.81 451c48.6 3.8 89.69-16.3 108.89-44.2c7.1 34.3 32.6 67 63 84.7c-5.2-29-1.8-59.4 19-92.5c16.5 22.9 31.1 59.3 73.8 75.3c-16.4-27.5-13.7-52.8-10.7-84.2c8.8 26.9 38.5 50 72.9 58.9c-16.8-18.6-23.9-45.5-21-66c14.6 24.9 43.4 38.4 67.1 39.7c-153.3-179.6-48.7-291.6 79.4-194.4c-.5-49.7-31.4-66.7-67.2-95.7c-.9-15.4-9.6-29.3-17.5-43.36c-53.7-9.99-121.5-42.01-154.6-68.78m81.2 74.88c26.1 10.86 46.2 22.56 56.4 35.46c-16.6-3.6-39.8-3.5-70-1.1c15-9.7 16.5-21.7 13.6-34.36"/></symbol>
|
||||
<symbol id="earth-crack" viewBox="0 0 512 512"><path fill="currentColor" d="m156.777 16.248l21.832 149.004l-83.165-78.248l40.946 125.732l-74.765-6.927l39.33 68.112h71.87l-11.512-38.73l28.125 15.234l1.17-49.223l44.537 51.568l14.064-78.52l17.58 60.94l57.425-39.846l-17.58 58.597l55.082-22.266l-30.912 42.248h80.834l37.17-63.13l-51.26 6.845l20.565-85.008l-70.873 48.905l48.37-111.842l-95.25 51.473l-12.266-100.908l-51.576 104.803l-32.53-87.51l-18.517 18.18l-28.693-39.482zm57.23 259.61l-48.064 34.892l20.48 27.527L59.384 355l8.42 66.527l-.506 2.99l-48.087 20.126V491.7h34.42l47.1-46.206l-18.678-74.63l46.343-6.1l3.358 54.933l1.685.75l39.685 32.815l-18.117 38.437h54.185L215 432.284l-65.352-26.19l-2.673-43.777l73.43-9.666l-28.182-37.886l53.593-38.91h-31.81zm60.124 0l49.722 58.437l-74.63 35.61l-4.355.222l52.588 62.935L266.69 491.7h72.033l-2.315-73.403L282.78 374.6l58.706-28.014l73.418 17.262l-45.13 72.213l41.003 55.112l.19.527h69.838l-78.51-55.122l14.45-35.62l77.86 30.75v-34.606l-68.937-15.215l18.912-30.26l-101.807-23.934l-44.103-51.836h-24.54z"/></symbol>
|
||||
<symbol id="eel" viewBox="0 0 512 512"><path fill="currentColor" d="M290.4 27.83C219 81.44 130.3 113.9 52.29 159.4c-5.97 3.5-7.78 6.9-8.57 12.3s.37 13.1 2.91 21.6c1.4 4.6 3.19 9.5 5.1 14.5c6.06-11.5 15.22-22.5 26.17-32.9c23.1-22.1 55.4-42.7 89.2-61.2c67.8-36.97 142.8-65.34 165.3-74.39c-15.1-2.99-29.9-6.48-42-11.48m94.1 18.23c-14.6 0-28.6 3.2-44.9 9.78c-23 9.25-97.4 37.37-163.9 73.66c-33.2 18.1-64.3 38.2-85.37 58.4c-21.14 20.2-31.24 39.2-27.8 55.8c4.07 19.6 23.14 34.1 52.97 44.7c30 10.6 69.4 16.6 108.4 20.3c39.1 3.6 77.9 5 107.6 6.9c14.9 1 27.5 2.1 37.1 3.8c4.8.9 8.9 1.9 12.7 3.4c3.6 1.5 8 3.8 9.6 9.7c3.2 11.7-1.4 23.6-9.6 32.6c-8.4 9.1-20.3 16.7-34.7 23.9c-28.7 14.5-68 26.9-108.1 38c-40.3 11-81.4 20.5-113.3 28.6c-15.9 4.1-29.51 7.7-39.36 11c-3.97 1.3-6.78 2.4-9.33 3.5c7.74 2.1 21.67 3.6 39.19 3.8c36.3.5 88.4-3.8 141.6-13.5c53.1-9.7 107.2-24.8 148-45.1c40.7-20.3 66.5-45.1 68.2-73.1c1.9-27.4-12.1-44.3-37.1-57.9c-25-13.5-60.9-21.7-97.1-26.9c-36.4-5.2-73.4-7.6-101.8-10.8c-14.4-1.7-26.4-3.4-35.6-6.1c-4.8-1.4-8.8-2.9-12.4-5.5c-3.5-2.5-7.1-6.9-7.5-12.4c-.4-8 4-14.7 9.4-20.2c5.3-5.6 12.3-10.5 20.6-15.3c16.5-9.5 38-18.3 59.4-26.1c42.7-15.5 85.5-26.4 85.5-26.4l1.5-.4l1.4.1c29.1 2.1 64.3-3.8 89.7-15.7c12.8-5.9 22.9-13.3 29-21.23c3.9-5.05 6-10.16 6.6-15.76c-36.8 12.56-48.6 18.26-78.3 19.49l-.9-18.03c24.9-.98 34.1-4.88 62-14.57c-26.1-10.97-46.5-18.01-64.3-20.85q-8.4-1.365-16.2-1.56h-2.9zm-11 17.11a16 16 0 0 1 16 16a16 16 0 0 1-16 16a16 16 0 0 1-16-16a16 16 0 0 1 16-16M214.7 206.6c-4.3 2.9-8.1 5.8-10.6 8.3c-2.6 2.7-3.4 4.5-3.6 5.7c1 .6 3.1 1.6 6.4 2.6c7.2 2.1 18.7 3.9 32.6 5.5c27.6 3.1 64.9 5.6 102.3 10.9c37.2 5.4 74.6 13.4 103.1 28.9c16.5 8.9 30 20.8 38.4 35.9c.8-10.4.1-23.4-3.7-41.2C406 230 298.9 224 214.7 206.6m157.8 140c-86.6 25.6-158.5 61.1-270.6 74.1c-44.78 5.3-56.15 28-75.05 50.3c2.59 3.2 6.97 6.1 13.79 8.4c6.07 2.2 13.65 3.7 22.06 4.8c-2.15-1.2-4.12-2.7-5.9-4.8c-1.89-2.3-3.31-5.8-3.18-9.2s1.55-6.3 3.23-8.6c2.95-3.9 6.03-5.2 9.79-7c3.77-1.7 8.28-3.4 13.61-5.2c10.65-3.4 24.55-7.2 40.55-11.2c32.2-8.2 73.2-17.7 112.9-28.6s78.2-23.3 104.9-36.7c13.3-6.6 23.5-13.6 29.3-19.9c2.2-2.4 3.7-4.5 4.6-6.4"/></symbol>
|
||||
<symbol id="emerald" viewBox="0 0 512 512"><path fill="currentColor" d="M310.375 16.75L89.405 75.72l58.126 50.905L282.563 90.28l2.032-.53zm17.063 7.844l-27.157 76.812l91.69 91.875l95.624-8.78zm-41.813 12.062l-8.594 33.657c-.28-15.516-38.03-17.018-107.56-4.376zm51.063 14.625l123.5 123.407l-58.844 7.563c16.2-21.37-32.277-91.112-64.656-130.97M74.75 87.72L15.594 308.405l79-31.47l37.28-139.155zm207.438 22l-133.032 35.81l-35.72 133.376l97.25 97.53l133.064-35.81l35.72-133.376zm-201.72 5.686l32.844 30.5l-30.156 118.97l-39.03 15.812c50.817-30.543 65.667-130.132 36.343-165.282zm195.876 14.78L359 213.377l-30.156 113.81l-44.688 11.97c119.527-107.872-34.816-238.375-131.5-140.875l9.875-37.405l113.814-30.688zM490.564 203l-92.877 8.53l-35.968 134.19l71.342 71.842L490.563 203zm-17.283 13.875L444.03 333.03c6.73-68.874-.03-90.85-30.655-111.5zm-371.155 77.188L20.22 326.688l161.75 161.468l17.31-96.72l-97.155-97.373zm.094 20l78.124 82.437l-7.438 61.375c-5.23-44.565-28.34-85.92-70.687-143.813zm246.124 44.687l-130.53 35.125l-17.564 98.188l221.688-59.157zm18.625 42.5l24.28 24.844l-115.22 32.72c61.28-26.446 83.34-37.418 90.94-57.564"/></symbol>
|
||||
<symbol id="energy-shield" viewBox="0 0 512 512"><path fill="currentColor" d="M255.31 17.707C210.816 17.31 166 23.734 127.038 36.57l-6.42 2.116v60.978l-76.463 41.822l1.54 6.866c25.895 115.578 95.61 228.743 205.44 333.564l6.473 6.178l6.452-6.2C370.945 379.166 438.893 267.838 463.43 152.12l1.388-6.552l-77.142-45.82V42.072l-6-2.3c-37.692-14.448-81.87-21.668-126.365-22.065zM244.8 36.77v63.642l-52.33 30.5l-53.163-30.644v-47.89c5.96-1.814 12.082-3.46 18.318-4.964a36 36 0 0 0-2.473 13.133c0 19.957 16.18 36.135 36.137 36.135c19.956 0 36.134-16.178 36.134-36.135a35.98 35.98 0 0 0-7.983-22.643a428 428 0 0 1 25.36-1.133zm18.69.12c8.22.235 16.405.72 24.514 1.438a35.98 35.98 0 0 0-7.65 22.22c0 19.956 16.18 36.134 36.136 36.134s36.137-16.178 36.137-36.135c0-3.88-.62-7.61-1.75-11.113c6.194 1.72 12.248 3.607 18.11 5.683v45.264l-52.454 30.237l-53.043-30.912zm88.828 94.67a39 39 0 0 0-6.41 21.47c0 21.642 17.546 39.19 39.19 39.19s39.19-17.548 39.19-39.19c0-3.705-.527-7.284-1.487-10.68l20.82 12.365c-10.61 46.76-28.647 92.954-53.91 138.265v-48.597l-63.11-36.78v-61.216l25.718-14.826zm-194.37 1.024l24.76 14.273v61.454l-61.894 36.073v44.388c-25.66-45.406-44.112-91.46-55.2-137.72l22.382-12.24a39 39 0 0 0-2.672 14.22c0 21.642 17.546 39.19 39.19 39.19s39.19-17.548 39.19-39.19a39 39 0 0 0-5.755-20.446zm65.234 2.06a36 36 0 0 0-3.48 15.48c0 19.956 16.18 36.134 36.136 36.134s36.137-16.178 36.137-36.135c0-4.878-.973-9.528-2.725-13.773l17.844 10.398v62.04l-51.832 30.208l-53.666-31.277v-60.495zM161.436 242.34a36.5 36.5 0 0 0-3.313 15.22c0 20.238 16.407 36.645 36.645 36.645c20.237 0 36.644-16.407 36.644-36.644c0-4.21-.717-8.25-2.023-12.013l16.428 9.576v56.035L188 344.814v41.61c-17.932-22.006-34.1-44.252-48.498-66.682v-64.62zm123.822.806a36.5 36.5 0 0 0-2.95 14.415c0 20.238 16.407 36.645 36.645 36.645s36.645-16.407 36.645-36.644c0-4.487-.81-8.784-2.286-12.757l17.71 10.32v69.123c-14.053 22.063-29.835 43.898-47.333 65.463v-44.896l-58.983-34.332v-55.36l20.55-11.976zm-57.56 100.184a32.94 32.94 0 0 0-3.91 15.608c0 18.27 14.813 33.082 33.083 33.082s33.083-14.812 33.083-33.082a32.9 32.9 0 0 0-3.074-13.924L305 355.56v56.323c-14.762 16.9-30.56 33.627-47.44 50.158c-18.152-17.645-35.098-35.5-50.873-53.526V355.56l21.012-12.23z"/></symbol>
|
||||
<symbol id="enrage" viewBox="0 0 512 512"><path fill="currentColor" d="M185 23.42c53.7 45.44 5.5 70.31-7.8 76.32c-40.5 18.36-87.65-24.61-99.5-67.35C63.43 75.61 30.56 164.5 88.87 200.5c-35.77 39.9-35.73 37-65.22-7.9c-8.45 41.3-2.84 112.6 15.69 164.1c2.04-22.1 5.04-43.5 11.23-60.7l1.09-3l2.78-1.7c45.86-27.2 86.06-36 117.26-40.1c-3-12.3-4.7-25.5-4.7-39.2c0-3 .1-6 .2-8.9l-82.06-40.7l35.46-14.9l-45.34-45.2l89.74 51l-29.2 12.2l32 30.6c2.5-26.4 11.2-50.1 24.3-68.5C207.8 105.5 230.5 91 256 91s48.2 14.5 63.9 36.6c13.2 18.5 21.9 42.4 24.4 68.9l40.1-41.3l-38.1-10.9l84.1-70.13l-42.9 60.53l45.2 14.3l-88.2 49.7c.4 6.3.6 11.8.4 17.5c6.2-.9 12.5-1.5 18.7-1.6c5.8 0 11.5.4 16.7 1.3c14.8 4.2 40.8 45.8 45.1 57.7c-2.9 14.3-13 25.4-22.4 34.8l44.9 95.9c5.9 12.6 2.8 28.4.5 40.3c-2.4 12-9.3 23.5-21.7 27.5c-23.7 5-40.5-7.7-54.3-23c.2 15.2 0 30.1-.4 44.9h94.4c27.4-32.1 28.2-70.2 24.9-111.1c-24.7 25.5-45.1-14.3-57.4-41.6c29.3 26.7 70.1-42.9 59.3-106.7c-5.4 15.8-42.6 27-59-27.7c50.4-13 54.4-78.4 60.5-109.96c-21.2 26.86-33.3 42.06-65.6 24.26c38.8-28.35 7.1-73.26-26.7-91c23 33.73-57.3 89.8-81.1 66.49C306.6 45.5 237.3 16 185 23.42M256 109c-18.7 0-36 10.5-49.2 29c-5.5 7.6-10.1 16.6-13.7 26.5c22.5 12.7 45.4 25.1 57.2 47.7c-1.4 5.3-2.4 10.8-3.9 16L202 217.6c7.1-12.6 27.8-11.6 39.7 3.2c-7.6-11.3-19.1-20-38.9-21l-10.4-33.4c-4.7 13.7-7.4 29.2-7.4 45.6c0 29.2 8.5 55.5 21.8 74c13.2 18.5 30.5 29 49.2 29s36-10.5 49.2-29c1.4-1.9 2.6-3.9 3.9-5.9c-2.2-17.1-3.9-39.4 3.1-52.2c3-4.7 9.9-6.1 14.5-7.6c.8-18.8-2.2-39.9-7.9-55.9l-10 35c-17.1 1.3-27.8 8.2-35.2 17.4c8.1-4.5 25.6-6.4 36.4-8.8c1.1 5.7 2.8 11.4-2.8 17.9c-13.3-4.4-29.6-1.4-40.9 2.8c-1.2-5.4-3.3-11.1-4.7-15.7c11.4-23.2 34.5-36.2 56.9-49.6c-3.5-9.5-8-18-13.3-25.4c-13.2-18.5-30.5-29-49.2-29m70.9 130.6c-2.2 6.6-3 19.7-.8 33.1c2.1 13.7 6.6 27.3 10.2 34.2c6.9 2.5 13.8 5.1 20.7 7.7l25 118c9.6 10.8 24.3 26.9 39.2 22.4c14.2-7.3 10.9-33 9.2-45.6l-48.6-103.9c7.7-11.8 20.9-21.1 24.6-31.2c-5.2-13.2-17.7-31.3-31-40.9c-15.3-.5-35.8 1.9-48.5 6.2m-110.2 4.2c7.8 0 22.1 15 39.8 13.9c15.9-.9 29.4-21.3 39.5-9.2c11 13.3-4 33.9-11.4 40c0 0-14.3-14.7-27.8-13.6c-13.3 1-25.1 16.4-25.1 16.4c-15.8-9.5-28.3-28.6-19.4-44.7c1.1-2 2.6-2.8 4.4-2.8m-39.4 24.9c-29.9 3.7-67.1 11.2-110.7 36.5c-10.07 39-10.68 79.1-14.43 114.8l-23.88 41.2c2.8 14.4 7.46 33.4 23.47 32.1c17.97-2.5 35.79-13.4 42.11-29c-2.43-10.8-6.14-19.3-10.17-28c4.28-33.2 4.25-66 13.33-91.5l2.89-7.8c29.18 6.6 47.78 2.6 70.18-13.4l-1.2 19c-3.4 51.4-12 105.7-10.1 151.4h195.1c1.5-49.8.3-100.3-10.5-154.2c-.7-3.7-1.3-7.6-2-11.8l-17.6-6.6c-3.8-6-6.9-12.9-8.9-18.5c-15.4 18.3-36 30.1-58.9 30.1c-25.5 0-48.2-14.5-63.9-36.6c-5.9-8.2-10.9-17.6-14.8-27.7m-27.7 88.2c-13 4-26.8 3.6-38.1 1.6c-7.3 24.1-7.4 48.4-9.5 74.6c5 12.5 9 21.8 10.4 33.2c-4.1 10.6-10 21-16.47 27.7h44.67c-1.8-43.5 5.1-91.6 9-137.1"/></symbol>
|
||||
<symbol id="erlenmeyer" viewBox="0 0 512 512"><path fill="currentColor" d="M196.27 22.814c-8.29 0-14.194 5.093-17.91 10.163c-3.713 5.07-6.032 10.492-6.032 16.814c0 6.323 2.16 11.873 5.668 17.433c3.51 5.56 9.108 11.85 18.275 11.85h-.202l6.63.142v122.002C150.973 267.087 50.5 395.497 50.5 395.497l-.402.513l-.325.562c-5.1 8.808-10.02 21.052-10.02 35.635c0 35.53 29.48 64.827 64.827 64.828h299.6c35.253 0 64.828-28.916 64.828-64.828c0-16.296-7.106-28.625-11.197-35.652l-.334-.576l-151.412-191.287V79.21c2.597-.066 3.062-.134 6.336-.136h.088c9.168 0 14.767-6.292 18.276-11.85c1.137-1.804 2.07-3.617 2.896-5.445l1.4-1.397h-.814c1.352-3.372 2.186-6.84 2.186-10.592c0-6.32-2.317-11.744-6.032-16.813c-3.714-5.07-9.62-10.163-17.912-10.163zm0 18.688h116.222c-.094 0 1.3.423 2.836 2.52s2.42 5.492 2.42 5.77c0 .276-1.043 4.696-2.785 7.456c-1.736 2.75-3.422 3.132-2.475 3.135c-6.836 0-12.118.197-14.302.285v-.207h-96.948v.03l-4.865-.105h-.102c.972 0-.727-.377-2.47-3.137c-1.74-2.76-2.784-7.18-2.784-7.457c0-.276.883-3.672 2.42-5.77c1.536-2.095 2.93-2.518 2.835-2.518zm25.115 37.646h65.996v75.07h-.044v52.497l.045.058v4.422L441.948 406.47c3.908 6.738 8.373 15.1 8.373 25.737c0 25.66-21.09 46.14-46.138 46.14h-299.6c-24.953-.002-46.14-21.018-46.14-46.14c0-3.557.43-6.963 1.146-10.184l.055.084a51.4 51.4 0 0 1 3.084-9.513l-.174.242a61 61 0 0 1 3.17-6.465c.668-.854 102.782-131.362 153.668-196.155l1.995-2.54v-1.155c.023-.03.07-.088.092-.118v-52.185h-.092v-75.07zm18.78 75.07v58.618l-1.99 2.537C204.078 258.886 82.596 415.245 81.714 416.38c-2.38 4.202-4.48 9.69-4.48 15.817c0 15.208 12.87 27.98 27.898 27.98h298.995c15.124 0 27.898-12.386 27.898-27.98c0-6.278-2.624-11.274-5.203-15.736L268.646 213.13v-58.913h-28.48zm-4.048 119.73c21.015 0 38.25 17.236 38.25 38.25c0 21.017-17.235 38.253-38.25 38.253s-38.252-17.236-38.252-38.25c0-21.016 17.237-38.253 38.252-38.253m0 18.69c-10.913 0-19.562 8.648-19.562 19.56c0 10.915 8.65 19.564 19.562 19.564c10.914 0 19.563-8.647 19.563-19.563c0-10.914-8.65-19.563-19.563-19.563zm-60.293 54.817c21.015 0 38.252 17.237 38.252 38.252s-17.237 38.252-38.252 38.252s-38.252-17.238-38.252-38.253s17.237-38.252 38.252-38.252zm96.623 10.71c24.664 0 44.858 20.197 44.858 44.86c0 24.665-20.195 44.858-44.858 44.858s-44.86-20.194-44.86-44.858s20.197-44.86 44.86-44.86m-96.623 7.98c-10.913 0-19.562 8.65-19.562 19.562c0 10.914 8.65 19.563 19.562 19.563c10.914 0 19.563-8.648 19.563-19.563c0-10.913-8.65-19.562-19.563-19.562m96.623 10.71c-14.562 0-26.17 11.608-26.17 26.17s11.608 26.17 26.17 26.17c14.564 0 26.17-11.605 26.17-26.17c0-14.562-11.607-26.17-26.17-26.17"/></symbol>
|
||||
<symbol id="evil-hand" viewBox="0 0 512 512"><path fill="currentColor" d="m165.98 47.744l-25.32 143.443c-28.823 29.04-82.26 43.33-117.54 49.016l.132 218.645l.006-.002C60.214 406.14 115.74 321.79 177.84 291.51c87.926-6.233 189.035 1.018 269.29 122.01l-30.286-111.768l-114.03-33.826c38.234-48.656 116.253-23.912 190.39 29.45l-63.015-80.312l-133.442-13.59c22.58-50.88 108.657-59.573 185.336-7.413L407.95 115.75c-105.138 37.836-182.9 34.318-190.315 9.33c-2.432 3.567-4.53 7.19-6.29 10.854c-13.455 27.973-8.834 59.176 11.362 86.75l-15.078 11.043c-23.475-32.05-29.726-71.382-13.128-105.893c15.343-31.902 49.35-58.73 103.26-73.768l-131.782-6.32z"/></symbol>
|
||||
<symbol id="falling-rocks" viewBox="0 0 512 512"><path fill="currentColor" d="m156.115 34.48l-36.94.586l10.02 28.995l27.258-2.324zM18 37.727V494h297.682L304 448l-64-64l-48-160l-88.055-80L96 64zM219.076 98.58L160 112l16.38 30.342l17.65 5.394l-1.223 19.672L244.664 144zm34.656 60.99l-19.56 21.05l27.508 12.61l12.855-20.804zm50.907 90.002l-34.38 25.012l-11.766 58.662l100.53 22.97l-4.163-98.218zm107.475 62.373l-20.886 3.465l-15.988 20.033l4.873 21.23l40.848-15.55zm-43.62 95.996l-44.32 5.87l6.858 29.422l36.875-1.396zm37.573 59.982l-32.103 5.375L340.113 494h79.508z"/></symbol>
|
||||
<symbol id="farmer" viewBox="0 0 512 512"><path fill="currentColor" d="m379.42 24.066l-28.059 87.407l28.268 5.941l-3.098 15.352l-52.25 47.843l-51.5-43.125l-23.404-4.093c8.217-14.33 14.683-32.77 16.404-49.594c10.02-2.28 34.92-5.675 46.094-10.059c-4.997-10.285-30.197-16.906-48.7-16.316c-1.733-20.713-8.88-29.054-34.155-27.902c-25.276 1.151-32.972 6.601-30.16 36.423c-18.866 4.127-38.097 12.616-39.74 27.084c7.87-.307 32.96-2.896 40.724-3.011c.66 14.1 4.4 27.847 9.97 36.375l-35.158-6.125L106 195.922l77.344 55.875l1.625 16.844l-34.19 215.75h38.375l38.315-169.25l47.873 169.25h37.47l-3.564-16.407l17.094 16.407l63.062-322.532c5.01-4.54 9.265-8.481 12.094-11.312c.177-10.537-2.537-18.942-5.094-24.5l.971-4.902l27.238 5.724l8.444-93.117l-22.846 68.781l-10.848-2.256l6.635-72.658l-21.568 69.55l-11.217-2.333zm26.578 3.553l.002-.01h-.002zM182.844 153.39l.344 64.095l-31.5-23.75zm88.031 21.252l50.875 45.937s22.993-19.456 44.875-38.531L309.187 467.61l-42.812-197.529z"/></symbol>
|
||||
<symbol id="feather" viewBox="0 0 512 512"><path fill="currentColor" d="M470.7 20L368.2 49.81l41.5-28.09c-26.2 5.92-59.3 17.5-100.9 36.19l-67.9 70.79L265 79.25c-23.3 12.96-48 29.95-71.8 49.85l-15.8 64.3l-3.4-47.6c-23.5 21.6-45.6 45.6-63.9 70.9c-19.23 26.5-34.26 54.5-41.79 82.4l-28.12-18.8c2.52 23.7 10.31 44.3 23.09 63.2l-33.62-10.3c7.64 23.5 20.13 38.7 41.25 51c-11.83 33.3-17.38 68.1-23.34 102.8l18.4 3.1C87.31 277.4 237.9 141.8 374 81.72l6.9 17.38c-121.7 54.5-216.3 146.5-265.8 279.1c18.1.1 35.8-2.1 52.2-6.3l4.9-60.9l13.1 55.5c10.9-4 20.9-8.8 29.8-14.4l-20.7-43.5l32.8 34.8c8-6.4 14.6-13.6 19.6-21.5c30.4-47.5 62.2-94.7 124.8-134.2l-45.7-16.2l70.1 2.1c11.4-5.8 23.4-12.9 32.5-19.6l-49.7-4l74.7-17.6c5.8-5.8 11.2-11.9 16.1-18c17.3-21.94 29-44.78 26.2-65.55c-1.3-10.39-7.5-20.16-17.6-25.63c-2.5-1.3-5.2-2.45-7.5-3.22"/></symbol>
|
||||
|
|
@ -176,6 +178,7 @@
|
|||
<symbol id="sewing-machine" viewBox="0 0 512 512"><path fill="currentColor" d="m61.67 119.8l-12.69 68.3c-5.47 29.6-1.02 60.1 12.69 86.8h51.03V119.8zm68.63 0v115.6h2.9c6.2 0 11.2-5.1 11.2-11.3V119.8zm31.8 0v104.3c0 15.9-12.9 28.8-28.9 28.8h-2.9v81.7h14.9v-59.7h19.6s7.6-15.3 13.6-27.7c4.7-9.2 14.1-15.1 24.5-15.1h80.2c15 0 27.2 12.1 27.2 27.1v91.5H25.95l10.57 68.5H455.1l10.5-63.1l-21.1-236.3zm310.1 23.4c-4.9.4-8.5 4.7-8 9.5l4.3 47.4c.4 4.8 4.7 8.4 9.5 8c4.9-.4 8.4-4.8 8-9.6l-4.2-47.3c-.4-4.5-4.2-8-8.7-8zm-86.8.1c21.7 0 39.2 17.5 39.2 39.2s-17.5 39.2-39.2 39.2s-39.2-17.5-39.2-39.2s17.5-39.2 39.2-39.2m0 17.5c-11.9 0-21.7 9.8-21.7 21.7c0 12 9.8 21.7 21.7 21.7s21.7-9.7 21.7-21.7c0-11.9-9.8-21.7-21.7-21.7m0 89.9c26.6 0 48.2 21.7 48.2 48.2c0 26.6-21.6 48.2-48.2 48.2s-48.2-21.6-48.2-48.2c0-26.5 21.6-48.2 48.2-48.2m0 17.6c-16.9 0-30.6 13.7-30.6 30.6s13.7 30.5 30.6 30.5s30.5-13.6 30.5-30.5s-13.6-30.6-30.5-30.6M92.71 287.8v17.6h20.09v-17.6zM36.52 438.9v26.5H454.9v-26.5z"/></symbol>
|
||||
<symbol id="sewing-needle" viewBox="0 0 512 512"><path fill="currentColor" d="M56.813 18.438c-.47-.005-.938.014-1.407.03q-.939.035-1.875.126c-7.468.72-14.682 4.148-22.31 11.125c-9.985 9.128-12.863 17.66-11.407 27.718c1.455 10.056 8.353 22.236 21.125 34.906c2.895 2.872 6.126 5.737 9.593 8.625c1.27 1.055 2.565 2.13 3.907 3.186a201 201 0 0 0 3.438 2.656c.232.175.454.358.688.532C74.71 119.38 95.558 131.28 120.25 142.22c159.723 161.31 288.815 297.553 374.594 352.124c-51.11-85.624-187.432-214.62-350-375.531c-10.23-23.802-21.115-43.71-32.094-59.282c35.46-10.19 82.517-2.294 125.75 40.94c40.243 40.242 52.642 90.776 41.375 128.343c4.89 4.914 9.883 9.913 14.688 14.75c19.07-46.1 4.573-108.895-42.844-156.313c-35.694-35.693-75.48-50.324-111.032-50.22c-1.147.005-2.3.03-3.438.064c-21.844.65-41.957 6.898-58.344 17.187a102 102 0 0 0-8.312 5.814c1.33 3.094 2.758 5.884 4.406 8.218c1.92 2.72 4.004 4.936 6.906 6.688c2.202-1.717 4.498-3.375 6.938-4.906a89 89 0 0 1 3.25-1.97c8.196 10.088 15.465 24.624 25.47 47.25c-23.907-10.568-38.822-18.04-49.033-27c.024-.03.04-.063.064-.093c-.433-.33-.867-.655-1.28-1q-.018-.013-.033-.03c-11.215-10.375-16.532-22.934-22.31-44.5c5.988 1.605 11.256 3.19 16 4.844a137 137 0 0 1 26.81-15.875c-9.5-7.738-18.552-11.93-26.686-12.97c-1.443-.185-2.87-.3-4.282-.313zM493.28 64.374c-77.985 0-125.587 28.886-146.124 69.406s-13.994 90.39 9.688 131.407c11.385 19.72 17.807 40.687 19.187 61.22a4330 4330 0 0 1 18.47 19.187c2.31-29.955-5.072-61.35-21.47-89.75c-21.13-36.602-26.155-80.112-9.186-113.594s55.94-59.188 129.437-59.188V64.376zM37.907 114.938c-2.894 23.925 6.18 50.21 29.532 73.562l13.218-13.188c-15.97-15.97-23.188-31.833-24.437-46.593c-6.646-4.51-12.754-9.102-18.314-13.782zM124.062 277c-24.466-.028-45.04 4.92-61.343 13.656c-24.842 13.31-39.363 35.865-40.75 60.47c-2.78 49.207 44.38 102.65 140.405 118.28c101.63 16.544 170.303-9.184 205.406-54.062q1.513-1.933 2.94-3.906c-4.552-4.232-9.148-8.52-13.783-12.875c-1.233 1.79-2.52 3.55-3.875 5.28c-29.97 38.315-90.355 62.97-187.687 47.126c-90.394-14.715-126.796-62.527-124.75-98.783c1.023-18.127 10.982-34.37 30.938-45.062c19.955-10.692 50.267-15.364 90.5-7.47c34.077 6.69 62.938 3.47 85.437-6.936c-4.72-4.65-9.49-9.33-14.28-14.064c-17.545 6.452-39.89 8.088-67.564 2.656c-14.895-2.923-28.778-4.297-41.594-4.312z"/></symbol>
|
||||
<symbol id="sewing-string" viewBox="0 0 512 512"><path fill="currentColor" d="M153.9 51.5c-34.6 0-65.87 4.45-87.79 11.3c-10.96 3.43-19.55 7.51-24.76 11.37c-5.22 3.85-6.41 6.7-6.41 8.33s1.19 4.48 6.41 8.33c5.21 3.86 13.8 7.94 24.76 11.37c21.92 6.8 53.19 11.3 87.79 11.3s65.9-4.5 87.8-11.3c11-3.43 19.6-7.51 24.8-11.37c5.2-3.85 6.4-6.7 6.4-8.33s-1.2-4.48-6.4-8.33c-5.2-3.86-13.8-7.94-24.8-11.37c-21.9-6.85-53.2-11.3-87.8-11.3M388 70.48c-2.5 0-5 0-7.5.16c-20.2 1.15-42.3 7.56-63.1 15.43c-12.5 4.72-24.5 9.98-35.3 15.03c-1.5 1.5-3.2 2.9-4.9 4.2q-7.65 5.55-18.3 9.9v17.9c13.5-7.4 38.1-20.1 64.8-30.2c19.9-7.5 40.8-13.32 57.8-14.29c17-.98 28.9 2.5 35.4 11.69c6 8.5 5 14.1-.7 23.4c-5.6 9.3-16.8 20.2-29.5 31.6c-12.6 11.3-26.5 23.2-37.4 36.4s-19.2 28.2-18.2 45.2c1.5 24.8 21.4 48.3 39.9 67.4c18.4 19 36.5 32.9 36.5 32.9l11-14.2s-17.2-13.3-34.6-31.2c-17.4-18-33.9-41.4-34.8-56v-.1c-.7-10.3 4.6-21.1 14.1-32.5c9.5-11.5 22.8-23 35.6-34.6c12.8-11.5 25.1-22.8 32.9-35.6c7.7-12.8 9.7-29.2-.2-43.11c-9.8-13.91-26.1-19.1-43.5-19.41M153.9 74.5a48 8 0 0 1 48 8a48 8 0 0 1-48 8a48 8 0 0 1-48-8a48 8 0 0 1 48-8m-87 46.7v10.6c58.3 14 116 13 174 6.9v-17.5c-23.6 6.5-53.9 10.3-87 10.3c-33.2 0-63.38-3.8-87-10.3m0 29.1v24.4c62.9 2.9 119.7-3.4 174-13.9v-4.1c-57.1 6-115 7-174-6.4m174 28.7c-54 10.3-111.1 16.5-174 13.8v18.7c64 1.5 123.2-2.5 174-15.9zm0 35.3c-52 12.9-111.1 16.6-174 15.2v3.2c61.5 8.4 120.8 13.8 174 10.8zm-174 36.5v8.9c53.3 17 112.9 17.8 174 16.9v-15.1c-54.1 2.9-113.2-2.3-174-10.7m0 27.7v31.8c52.7 3.4 102.8-3.5 152.2-15.5c-52 .4-103.9-1.7-152.2-16.3m174 29.4c-55.7 14.7-113.1 24.3-174 20.5v16c57.6.2 114.8-1 174-14.2zm221.7 19a20.5 40.99 51.48 0 0-32.8 14.1a20.5 40.99 51.48 0 0-.2.1l-.6.6a20.5 40.99 51.48 0 0-8.9 8.5L265.9 497.5l176.3-115.9a20.5 40.99 51.48 0 0 13.1-8.5a20.5 40.99 51.48 0 0 19.3-41.5a20.5 40.99 51.48 0 0-12-4.7M452.5 342a10.25 20.5 51.48 0 1 .1 0a10.25 20.5 51.48 0 1 6 2.3a10.25 20.5 51.48 0 1-9.7 20.8a10.25 20.5 51.48 0 1-22.4 4.8a10.25 20.5 51.48 0 1 9.6-20.8a10.25 20.5 51.48 0 1 16.4-7.1m-211.6 6.6c-59.9 12.8-117.3 14-173.99 13.8c0 0 .41.9 3.71 2.8c3.91 2.2 10.75 4.6 19.24 6.5c16.94 3.8 40.54 5.8 64.04 5.8s47.1-2 64-5.8c8.5-1.9 15.4-4.3 19.3-6.5c3.4-2 3.7-3 3.7-2.8zM49.17 365.5c-3.1 1.6-5.74 3.1-7.82 4.7c-5.22 3.8-6.41 6.7-6.41 8.3s1.19 4.5 6.41 8.3c5.21 3.9 13.8 8 24.76 11.4c21.92 6.8 53.19 11.3 87.79 11.3s65.9-4.5 87.8-11.3c11-3.4 19.6-7.5 24.8-11.4c5.2-3.8 6.4-6.7 6.4-8.3s-1.2-4.5-6.4-8.3c-2.1-1.6-4.7-3.2-7.9-4.7c-1.2 6.9-6.7 12-12.5 15.3c-6.6 3.8-14.7 6.4-24.3 8.5c-19 4.2-43.4 6.2-67.9 6.2s-48.9-2-67.95-6.2c-9.51-2.1-17.68-4.7-24.26-8.5c-5.79-3.2-11.27-8.4-12.52-15.3"/></symbol>
|
||||
<symbol id="shattered-glass" viewBox="0 0 512 512"><path fill="currentColor" d="m431.688 18.813l-83.594 65.75L456.5 63.03l-24.813-44.218zm-121.375.906l-21.47 91.5l79.72-91.5zm-160.657 4.405l-2.187 6.25c-13.237 37.557-21.314 79.002-21.314 118.094c0 53.795 15.034 93.182 40.22 118.874c18.918 19.3 43.185 30.496 69.78 34.875c17.132 48.134 16.18 101.07-2.47 148.78c-30.572 5.112-60.267 18.292-86.342 39.53H378.53c-26.062-21.23-55.75-34.383-86.31-39.5c-18.647-47.685-19.602-100.598-2.5-148.717c26.782-4.308 51.187-15.52 70.124-34.97c13.345-13.706 23.776-31.308 30.625-52.906l-84.75-71.656l-51.595-1.093L306.28 261.31l-50.186-73.687l-79.156 64.47l63.812-86.94l-16.28-23.905l-53.845-16.156l-9.813-2.938l3.844-9.53l36-88.5zm71.156 0l-34.968 86l48.562 14.594l32.78-33.69l-17.436-66.905h-28.938zM395.438 97.03l-81.78 28l81.906 69.25c2.706-13.91 4.156-29.165 4.156-45.81c0-16.88-1.513-34.186-4.283-51.44z"/></symbol>
|
||||
<symbol id="shears" viewBox="0 0 512 512"><path fill="currentColor" d="M94.85 31.59c-1.43 2.7-2.79 5.88-3.87 9.77c-2.55 9.22-3.8 21.14-3.66 33.46c.29 24.64 6.87 51.38 14.38 61.68l.1.2l.1.1c28.5 42.7 64 81.1 99.2 120.3c1.6-5.7 4.1-11.1 7.3-16l-79-96.9l-5.5-35.2l96.7 118.8c3.9-3.1 8.1-5.7 12.7-7.6c-49.6-58.5-99.9-126.31-138.45-188.61m322.35 0c-38.6 62.3-88.9 130.11-138.5 188.61c4.6 1.9 8.8 4.5 12.7 7.6L388.1 109l-2.9 18.9l-2.6 16.3l-79 96.9c3.2 4.9 5.7 10.2 7.3 16c35.2-39.2 70.7-77.6 99.2-120.3l.1-.1l.1-.2c7.5-10.3 14.1-37.04 14.4-61.68c.1-12.32-1.1-24.24-3.7-33.46c-1.1-3.89-2.4-7.07-3.8-9.77M256 233.4c-21.6 0-39 17.4-39 39s17.4 39 39 39s39-17.4 39-39c0-9.2-3.2-17.7-8.5-24.4c-7.2-8.9-18.1-14.6-30.5-14.6m0 14c13.7 0 25 11.3 25 25s-11.3 25-25 25s-25-11.3-25-25s11.3-25 25-25m0 18c-4 0-7 3-7 7s3 7 7 7s7-3 7-7s-3-7-7-7m-51.8 30.7c-13.8 9-27.1 18-38 27.5l23 16.9c5.6-6.7 12.7-12.5 20-17.9c3.2-2.4 6.4-4.8 9.6-7.1c-6.2-5.3-11.2-11.9-14.6-19.4m103.6 0c-3.4 7.5-8.4 14.1-14.6 19.4c3.2 2.3 6.4 4.7 9.6 7.1c7.3 5.4 14.4 11.2 20 17.9l23-16.8c-10.9-9.5-24.2-18.6-38-27.6m-174.5 25.7l-23.5 21.6l74.3 54.9l14-29zm245.4 0l-64.8 47.5l14 29l74.3-54.9zM114.2 369L53 452.3c-14.01 19.4 32.48 48.7 44.28 32.2l60.12-83.6zm283.6 0l-43.2 31.9l60.1 83.6c11.8 16.5 58.3-12.8 44.3-32.2z"/></symbol>
|
||||
<symbol id="shield" viewBox="0 0 512 512"><path fill="currentColor" d="M256 16c25 24 100 72 150 72v96c0 96-75 240-150 312c-75-72-150-216-150-312V88c50 0 125-48 150-72"/></symbol>
|
||||
<symbol id="shield-bash" viewBox="0 0 512 512"><path fill="currentColor" d="M274.663 63.02L90.792 80.26l154.193 19.273c5.063-13.339 12.952-24.341 22.541-31.828a52 52 0 0 1 7.137-4.683zm19.832 12.803c-5.092.166-10.492 2.296-15.879 6.502c-7.835 6.118-15.009 16.575-18.83 29.688s-3.477 26.099-.289 35.927c3.188 9.829 8.73 16.071 15.633 18.395s14.766.596 22.601-5.522c7.835-6.117 15.01-16.574 18.83-29.687c3.822-13.113 3.48-26.1.292-35.928s-8.73-16.07-15.633-18.394a19 19 0 0 0-6.725-.98zm166.85 9.485c-24.113 13.949-46.193 20.298-87.233 17.252L340.48 228.452c-.675 2.682-.318 6 1.922 10.87c2.243 4.876 6.355 10.89 11.836 17.607c9.99 12.242 24.527 27.16 39.573 44.238c14.56-5.5 28.23-12.828 38.972-20.19c11.841-8.113 20.234-16.95 21.965-19.939l42.027-118.22c-16.748-14.613-29.471-33.974-35.43-57.51m-288.07 51.261L23.652 158.331l89.309 12.988l2.158-5.052zm286.265 2.325l16.941 6.078l-39.123 109.037l-37.212 19.181l-8.247-15.998l30.913-15.933zm-259.842 4.394l-70.586 36.043l-29.222 68.422l19.218 8.809l24.905-57.764l59.299-22.973l-14.702 75.955l-.963 1.477c-32.725 50.18-71.654 93.41-118.464 134.28L42.722 432.98l17.021 7.245l31.875-43.989l1.38-.906c45.476-29.872 75.93-62.333 112.255-94.492l4.533-4.012l5.426 2.686c23.365 11.571 42.934 24.117 62.107 37.705l8.924 6.324l-69.006 65.643l24.649 39.794l17.67-10.308l-20.078-28.477l8.224-5.004c29.884-18.186 49.986-39.43 71.938-66.039c-23.653-35.6-42.006-49.433-71.592-71.267l9.908-7.227c34.703-25.312 38.132-54.476 41.61-79.449c-9.203 4.441-19.498 5.772-29.473 2.414c-13.488-4.54-22.924-16.472-27.465-30.473c-.17-.522-.321-1.054-.479-1.584zm116.62 45.04c-1.355 7.027-3.324 14.17-6.092 21.349l14.056 9.666l5.938-22.223zm-174.243 97.476l-126.85 17.953l99.67 14.105a599 599 0 0 0 27.18-32.058m91.781 82.73l-95.892 21.432l59.406 13.277z"/></symbol>
|
||||
|
|
@ -186,6 +189,7 @@
|
|||
<symbol id="sickle" viewBox="0 0 512 512"><path fill="currentColor" d="M257.563 25.156c353.16 276.87 16.918 408.895-87.875 293.25l-40.75 37.125l50.812 50.345c217.562 181.363 524.73-252.058 77.813-380.72M110.75 364.28c-5.525 1.065-8.975 2.957-11.313 5.25c-1.956 1.922-3.248 4.556-4.25 7.564l55.188 52.844c5.468-1.008 9.264-2.796 11.28-4.688c1.997-1.872 3.095-3.864 3.095-7.53zm-24.72 30.314L30.407 445.28c-16.737 27 14.693 61.198 51.093 44.66l51.53-50.282l-47-45.062z"/></symbol>
|
||||
<symbol id="skeleton" viewBox="0 0 512 512"><path fill="currentColor" d="M266.7 175.748v8.948h33.962a10.164 10.164 0 0 1-.094 20.329H266.7v8.948h2.672a10.165 10.165 0 0 1-.252 20.329h-2.42v8.948h1.666a10.165 10.165 0 1 1-.252 20.329h-1.425v4.789a10.175 10.175 0 0 1-10.153 9.954h-.147a10.164 10.164 0 0 1-10.018-10.31v-4.433h-1.666a10.164 10.164 0 0 1 .105-20.329h1.561v-8.948h-1.666a10.164 10.164 0 0 1 .105-20.329h1.561v-8.948h-33.95a10.164 10.164 0 0 1-6.979-17.447a10 10 0 0 1 7.23-2.871h33.71v-8.949h-33.95a10.164 10.164 0 0 1 .104-20.328h33.856v-8.95h-12.008a10.164 10.164 0 1 1 .105-20.328h11.893v-9.724a10.164 10.164 0 0 1 9.913-10.258a3 3 0 0 1 .492 0a10.154 10.154 0 0 1 9.913 10.3v9.672h11.998a10.164 10.164 0 0 1 6.978 17.457a10.08 10.08 0 0 1-7.23 2.871H266.7v8.939h33.96a10.196 10.196 0 0 1 3.825 19.584a9.4 9.4 0 0 1-3.887.744H266.7zm-46.493-61.206a10.19 10.19 0 0 0-9.137-5.742h-.126a10.15 10.15 0 0 0-4.558 1.11l-29.225 14.618a10.164 10.164 0 0 0 8.76 18.306l.304-.146l29.236-14.67a10.185 10.185 0 0 0 4.746-13.476m118.733 20.203a10.185 10.185 0 0 0-4.328-13.727l-.251-.126l-29.235-14.67a10.164 10.164 0 1 0-9.127 18.16l29.235 14.617a10.175 10.175 0 0 0 13.706-4.265zm-156.487 23.65a10.12 10.12 0 0 0-7.178-3.08h-.262a10.175 10.175 0 0 0-10.017 10.268v58.513a10.164 10.164 0 0 0 10.028 10.3h.146a10.175 10.175 0 0 0 10.154-9.913a4 4 0 0 0 0-.46v-58.398a10.1 10.1 0 0 0-2.87-7.23zm154.234-3.08a10.175 10.175 0 0 0-10.017 10.268v58.513a10.164 10.164 0 0 0 10.028 10.3h.21a9 9 0 0 0 3.447-.566a10.3 10.3 0 0 0 6.643-9.347a4 4 0 0 0 0-.46v-58.398a10.164 10.164 0 0 0-10.049-10.31zm-36.497 114.77a10.2 10.2 0 0 0-5.386 1.76l-38.278 25.526l-38.247-25.494a10.1 10.1 0 0 0-5.742-1.792a9.8 9.8 0 0 0-4.872 1.216a10.2 10.2 0 0 0-5.24 8.927a10.1 10.1 0 0 0 4.6 8.54l43.874 29.256a10.1 10.1 0 0 0 12.501-.775l42.69-28.45a10.164 10.164 0 0 0-5.9-18.714m-80.276 51.429a10.12 10.12 0 0 0-7.178-3.08h-.262a10.175 10.175 0 0 0-10.017 10.268v58.512a10.164 10.164 0 0 0 10.028 10.3h.146a10.175 10.175 0 0 0 10.154-9.912a4 4 0 0 0 0-.46v-58.387a10.1 10.1 0 0 0-2.86-7.241zm87.82 0a10.1 10.1 0 0 0-7.177-3.08h-.262a10.175 10.175 0 0 0-10.007 10.268v58.512a10.164 10.164 0 0 0 10.028 10.3h.23a8.9 8.9 0 0 0 3.438-.565a10.32 10.32 0 0 0 6.632-9.357a4 4 0 0 0 0-.44v-58.397a10.1 10.1 0 0 0-2.87-7.241zm-87.82 93.48a10.1 10.1 0 0 0-7.178-3.082h-.262a10.175 10.175 0 0 0-10.017 10.27v58.512a10.164 10.164 0 0 0 10.028 10.3h.146a10.175 10.175 0 0 0 10.154-9.913a4 4 0 0 0 0-.46v-58.398a10.1 10.1 0 0 0-2.86-7.23zm-37.46-165.426a10.1 10.1 0 0 0-7.179-3.08h-.262a10.175 10.175 0 0 0-10.017 10.268v21.806a10.164 10.164 0 0 0 10.028 10.3h.146a10.175 10.175 0 0 0 10.154-9.912a4 4 0 0 0 0-.461v-21.68a10.1 10.1 0 0 0-2.87-7.241m161.673 3.668a10.1 10.1 0 0 0-7.178-3.081h-.262a10.175 10.175 0 0 0-10.017 10.269v21.774a10.164 10.164 0 0 0 10.028 10.3h.21a9 9 0 0 0 3.447-.565a10.3 10.3 0 0 0 6.643-9.347a4 4 0 0 0 0-.461v-21.66a10.1 10.1 0 0 0-2.871-7.23zm-36.382 161.757a10.1 10.1 0 0 0-7.178-3.08h-.261a10.175 10.175 0 0 0-10.007 10.268v58.513a10.164 10.164 0 0 0 10.028 10.3h.21a9 9 0 0 0 3.447-.566a10.31 10.31 0 0 0 6.643-9.357a4 4 0 0 0 0-.44v-58.408a10.1 10.1 0 0 0-2.882-7.23M285.74 59.467c0 17.363-13.13 38.467-28.292 38.467s-30.157-21.02-30.157-38.467C227.29 34.193 242.265 21 257.448 21s28.334 13.109 28.334 38.467zm-34.086-3.51a9.095 9.095 0 1 0-9.096 9.095a9.095 9.095 0 0 0 9.137-9.095zm27.883 0a9.095 9.095 0 1 0-9.095 9.095a9.095 9.095 0 0 0 9.137-9.095z"/></symbol>
|
||||
<symbol id="sofa" viewBox="0 0 512 512"><path fill="currentColor" d="M112 73c-13.75 0-24.214 4.87-33.047 13.271s-15.755 20.6-20.414 34.575c-5.887 17.661-7.953 38.064-6.887 56.111a57 57 0 0 1 2.461-.187c8.38-.405 16.317 1.094 23.494 4.234c14.355 6.28 25.436 18.27 34.057 32.28c15.104 24.543 23.187 55.746 24.96 80.585c114.097-14.223 124.655-14.223 238.753 0c1.772-24.84 9.855-56.042 24.959-80.586c8.62-14.009 19.702-25.999 34.057-32.28c7.875-3.444 16.67-4.898 25.955-4.046c1.066-18.047-1-38.45-6.887-56.111c-4.659-13.976-11.582-26.173-20.414-34.575C424.214 77.87 413.75 73 400 73zM56.969 194.502q-1.029-.012-2.082.04c-5.621.284-11.8 1.978-18.862 5.509c-23.413 11.707-26.349 65.687 14.82 79.41l4.505 1.502L86.79 391h338.42l31.44-110.037l4.504-1.502c41.17-13.723 38.234-67.703 14.82-79.41c-14.121-7.061-24.722-6.775-34.367-2.555s-18.564 13.23-25.943 25.22C400.906 246.7 393 282 393 304v10.195l-10.117-1.265c-128-16-125.766-16-253.766 0L119 314.195V304c0-22-7.906-57.3-22.664-81.283c-7.38-11.991-16.298-21.001-25.943-25.22c-4.22-1.847-8.624-2.94-13.424-2.995M83.727 409l-23.272 23.271c11.994 3.239 33.75 3.763 42.545-1.666c3.272-4.695 6.903-14.536 10.438-21.605zm314.836 0c1.494 6.017 6.952 19.12 10.437 21.605c13.2 5.463 29.904 5.774 42.545 1.666L428.273 409z"/></symbol>
|
||||
<symbol id="sonic-shout" viewBox="0 0 512 512"><path fill="currentColor" d="m18.62 18.707l2.302 456.713c20.172 6.097 50.346 5.194 68.094-5.3c9.74-5.762 15.83-13.314 17.318-24.757c1.49-11.442-2.114-27.866-15.775-49.85c-65.15-104.838-43.09-217.272 24.12-283.156c67.208-65.883 179.805-84.665 289.812-7.214c18.312 12.892 28.41 13.426 35.156 10.308c6.748-3.117 13.086-12.55 18.036-26.186c4.95-13.637 8.515-30.532 12.306-45.967c2.094-8.527 4.108-16.49 6.856-23.647zm239.07 54.02c-8.728-.036-17.285.53-25.64 1.652l156.454 92.8l21.037-37.436c-5.072-2.376-10.346-5.476-15.806-9.32c-47.14-33.19-93.62-47.523-136.043-47.697zM127.913 125.56l-.15.143c-26.928 26.397-46.107 60.924-53.93 99.686c75.5-10.072 121.71 72.345 177.38 61.495c6.68-57.468-59.496-126.038-123.3-161.326zm227.297 47.21c-6.87 36.037-29.7 77.615-66.003 113.92c-36.093 36.095-77.76 59.255-113.646 66.27c40.94 8.506 92.248-8.67 131.747-48.17c39.643-39.646 56.63-91 47.902-132.02m66.103 4.302v.004zm0 .004c-9.198 48.248-39.766 103.918-88.374 152.528c-48.327 48.328-104.113 79.337-152.167 88.732c54.816 11.39 123.514-11.608 176.4-64.498c53.08-53.082 75.822-121.842 64.14-176.762zm67.328 10.985c-11.378 59.698-49.203 128.58-109.345 188.725c-59.794 59.798-128.82 98.17-188.28 109.79c67.825 14.094 152.828-14.364 218.264-79.804c65.677-65.678 93.815-150.757 79.36-218.71zM71.07 243.337c-4.794 44.69 5.3 93.938 35.362 142.314c7.806 12.562 13.057 24.113 16.01 34.75l36.103-21.412z"/></symbol>
|
||||
<symbol id="sparkles" viewBox="0 0 512 512"><path fill="currentColor" d="M237.4 20.73c-6.1 42.1-26.8 64.2-63.9 64c31.6 4.5 63.8 8 63.9 64.07c-.6-46.1 24.5-63.07 64.1-64.07c-38-1.5-64.9-16.3-64.1-64m127.8 11.58c-9.1 14.25-20.8 21.29-38.9 10.28c14.9 11.79 18.6 24.76 10.2 38.97c8.9-11.18 17.5-22.73 39-10.27c-17.8-10.06-18.8-23.57-10.3-38.98M59.68 41.69c-2.7 18.8-12 28.6-28.5 28.5c14.1 2 28.4 3.6 28.5 28.52c-.3-20.5 10.9-28.12 28.5-28.52c-16.9-.7-28.9-7.3-28.5-28.5M431 66.28c-2.7 18.8-12 28.6-28.5 28.5c14.1 2 28.4 3.6 28.5 28.52c-.3-20.5 10.9-28.12 28.5-28.52c-16.9-.7-28.9-7.3-28.5-28.5M120.3 116.4c-15.8 53.7-47.76 48-79.35 43.4C76.6 170 90.3 197.1 84.28 239.2c12.66-46 42.62-52.6 79.42-43.4c-37.6-12.1-56.9-35.4-43.4-79.4m187 5c-8.8 61.6-39.3 94-93.6 93.7c46.2 6.5 93.6 11.7 93.6 93.7c-.8-67.3 35.9-92.2 93.8-93.7c-55.5-2.2-94.9-23.9-93.8-93.7m136.8 38.3c-13.1 21.6-29.5 28.8-49.7 20.1c16.3 9.7 33 19.1 20.1 49.6c10.3-25.2 27.9-28.7 49.7-20c-20.3-9.7-31.6-23.9-20.1-49.7M50.7 243.2c9.16 16.7 7.63 30.1-5.61 40c12.46-6.9 24.85-14.3 39.91 5.6c-12.57-16.2-8.2-29 5.61-40c-13.92 9.7-27.47 11.6-39.91-5.6m137.2.3c11.4 26.8-.5 41.3-21.7 50.9c22.7-8.5 40.8-4.5 50.9 21.7c-12.7-31.8 4.8-41.2 21.7-50.9c-21 8.5-37.8.9-50.9-21.7m228 12.6c-26.6 64.7-68.7 91.7-127.8 76.4c48.6 19.8 98.8 38.5 76.4 127.9c17.5-73.7 64.4-90.7 127.9-76.5c-59.9-17.5-96.9-52-76.5-127.8M99.94 295.5c15.66 57.8.86 98.1-47.32 118.5c43.46-11.8 87.38-25.2 118.68 47.4c-26.4-59.3-3.4-95.4 47.3-118.8c-50 19.2-93.1 15-118.66-47.1m169.36 61c-21.8 20.6-43 23.6-63.2 7.3c15.5 16.3 31.6 32.4 7.2 63.3c19.8-25.6 41.2-24.1 63.3-7.3c-20.2-17.4-28.6-37.5-7.3-63.3M443.2 404c-2.7 18.8-12 28.6-28.5 28.5c14.1 2 28.4 3.6 28.5 28.5c-.3-20.5 10.9-28.1 28.5-28.5c-16.9-.7-28.9-7.3-28.5-28.5m-169.7 36c-2.7 18.8-12 28.6-28.5 28.5c14.1 2 28.4 3.6 28.5 28.5c-.3-20.5 10.9-28.1 28.5-28.5c-16.9-.7-28.9-7.3-28.5-28.5"/></symbol>
|
||||
<symbol id="spears" viewBox="0 0 512 512"><path fill="currentColor" d="m433.877 19.855l-78.62 158.668l47.82 53.934l-15.673 260.824h38.79l15.697-261.233l51.04-45.25l-59.053-166.942zm-130.48 20.598L216.073 152.55l28.397 49.186l-28.616 106.387l-.102-.176l-9.545 35.62l.076.134l-40.234 149.58h32.68l29.676-111.186l.026.045l9.527-35.556l-.054-.09L274.56 209.15l48.415-27.95L303.4 40.452h-.002zM111.632 153.21l14.48 116.54l44.42 11.904L193 320.648l9.555-35.66l-9.256-16.1l12.022-44.87l-93.69-70.81zm-49.357 74.31l-42.68 79.31l23.57 28.455L30.786 493.28h18.746L61.83 336.26l27.608-22.865zh-.002zm257.06 10.878l-22.354 87.248l29.686 21.932L345.04 493.28h18.84l-18.823-149.235l21.293-28.824zm175.103 28.614l-40.108 226.27h18.98l21.128-119.188zm-339.903 38.886l-55.35 71.05l18.618 32.247l-20.41 84.086h19.233l19.51-80.385l30.81-17.788l-12.408-89.21h-.002zm96.623 63.645l-9.517 35.516l50.837 88.22h29.8z"/></symbol>
|
||||
<symbol id="spectre" viewBox="0 0 512 512"><path fill="currentColor" d="M282.83 18.877c21.567 6.146 36.328 15.205 40.572 28.77a71 71 0 0 0-4.066 2.363c-25.655 5.807-53.084 18.634-81.047 34.363c20.225-4.69 36.705-4.42 47.544 3.324c-5.712 10.3-10.797 22.294-15.123 36.06c-21.343 67.922-125.678-80.577-189.065-5.255C41.47 166.24 92.702 212.342 26.5 294.29c35.583-4.14 45.11-9.47 62.416-36.21c10.44-16.13 28.52-22.1 44.668-21.45c-12.718 11.206-25.786 30.354-38.21 59.895c19.832-18.528 44.927-38.68 71.603-40.005c.912 6.354-2.408 13.74-12.008 21.418C93.21 327.327 15.58 364.185 19.83 476.504c5.558-11.267 11.646-20.31 17.574-28.617c5.98 29.582 28.2 53.8 92.99 40.482c-46.928-6.407-76.268-59.586-45.355-82.528c62.716-46.544 128.82 1.436 269.9-75.342a80 80 0 0 1 7.164-3.46c-2.246 19.6-12.367 39.84-22.362 57.14c14.26-10.38 25.415-20.147 33.928-29.262c2.14 26.14-11.748 54.65-25.393 78.268c43.26-31.49 61.19-57.976 63.207-78.422c9.334 36.678-1.895 95.547-25.03 123.492c77.553-39.433 106.608-77.127 109.76-190.664c1.662-59.824-66.23-60.536-56.435-101.344c11.945-49.756 6.768-84.69-7.565-107.947c-19.6-49.73-79.99-59.74-149.385-59.423zM89.8 47.684C54.69 47.534 45.233 83.056 55.724 117c17.146-51.504 70.414-44.24 111.17-34.367c-33.282-25.005-58.707-34.87-77.096-34.95zm254.595 30.742c18.643 21.37 29.373 43.02 10.105 65.732c-26.562-16.677-26.985-39.252-10.105-65.732m74.494 48.6c3.655-.013 7.78.35 12.473 1.09c-1.706 30.506-14.4 33-46.634 32.154c8.335-22.146 14.416-33.176 34.16-33.245zm-84.677 40.316c9.652.1 20.258 2.84 30.598 8.918c44.65 26.246 21.934 73.314-4.1 74.78c-15.174.855 1.443-23.842-16.17-38.476c-15.258-12.678-38.596 12.53-45.204-5.78c-8.218-22.693 10.21-39.696 34.877-39.442zm60.18 246.168c-24.195 27.825-58.89 60.1-99.444 31.41c16.625 45.643 68.87 70.465 99.443-31.41z"/></symbol>
|
||||
|
|
@ -224,6 +228,7 @@
|
|||
<symbol id="war-pick" viewBox="0 0 512 512"><path fill="currentColor" d="M136.48 27.746c-2.108.024-4.174.152-6.242.272c42.927 23.035 87.233 59.434 121.902 96.57c8.66 9.276 12.358 18.765 16.371 27.44c2.486 5.37 5.173 10.658 9.297 16.37l30.65-26.373c-3.067-5.031-5.213-10.567-6.044-16.386c-.919-6.432.09-13.283 3.039-19.48c-32.92-24.035-68.653-47.25-102.75-62.026c-22.779-9.871-44.547-15.843-64.1-16.371q-1.07-.029-2.123-.016m226.463 99.256c-2.825 0-5.562.505-8.092 1.293l28.91 28.91c.788-2.53 1.293-5.266 1.293-8.092c0-6.485-2.314-12.726-5.85-16.262s-9.776-5.85-16.261-5.85zm-23.088 11.754l-21.89 18.836c9.951-2.533 20.985.059 28.712 7.787s10.32 18.761 7.788 28.713l18.835-21.89zm-14.39 35.78c-3.053 0-6.104 1.189-8.485 3.57c-4.762 4.761-4.762 12.208 0 16.97s12.21 4.762 16.97 0c4.763-4.762 4.763-12.209 0-16.97c-2.38-2.381-5.433-3.57-8.485-3.57m-27.582.335l-9.846 8.47l-5.352 46.03l46.03-5.352l8.468-9.841c-10.923 4.588-24.09 2.467-32.931-6.373c-8.842-8.842-10.959-22.01-6.37-32.934zm72.148 28.727l-26.373 30.65c5.712 4.124 11 6.812 16.371 9.297c8.674 4.013 18.163 7.711 27.44 16.37c37.136 34.67 73.534 78.977 96.57 121.903c1.254-21.638-4.803-46.36-16.115-72.465c-14.776-34.097-37.992-69.829-62.026-102.75c-6.198 2.95-13.049 3.958-19.48 3.04c-5.82-.832-11.355-2.978-16.387-6.045m-103.375 7.79l-28.398 26.588L274.08 273.8l26.588-28.399l-38.489 4.477zm-41.545 38.897l-11.686 10.941l37.405 37.405l10.941-11.686zm-24.832 23.252l-90.564 84.797l44.007 44.008l84.797-90.565zM96.566 370.643l-21.91 20.515l46.242 46.242l20.516-21.91zm-28.09 39.79l-5.656 16.971l21.832 21.832l16.97-5.656zm-18.789 29.295l-18.49 9.686a106 106 0 0 0-2.746 13.676c-.608 4.548-.852 9.29-.469 12.92s1.496 5.735 1.912 6.152c.417.417 2.523 1.53 6.153 1.912c3.63.383 8.372.139 12.92-.469a106 106 0 0 0 13.675-2.746l9.686-18.49z"/></symbol>
|
||||
<symbol id="warhammer" viewBox="0 0 512 512"><path fill="currentColor" d="m351.4 31.4l-55.9 27.75l54 34.62zM143.2 67.33l20 33.47L182 71.5zm57 9.16l-7.6 11.82l100.2 64.19l-9.7 15.1l-100.2-64.1l-24.8 38.7l100.2 64.1l-9.7 15.2l-100.2-64.1l-7.5 11.7l150 96.1l37.9-7.6l44.8-70zm93.2 2.73l-16.2 25.28l38.7 24.7l16.2-25.2zM108.7 121.2l20 33.5l18.8-29.3zm280.1 76.1L346.7 263l11.8 7.5l42.1-65.7zm-224.1 8.5l.6 36l72.8 46.6l33-14.5zm250.4 9.9l-33.2 51.8c11.7 2.8 24 7.3 36.5 12.3c19.3 7.6 39.1 16.7 56.6 24c-9.5-27.2-22.2-57.9-59.9-88.1m-242.5 52.1L27.77 494h54.61L211.4 292.6z"/></symbol>
|
||||
<symbol id="wasp-sting" viewBox="0 0 512 512"><path fill="currentColor" d="M235.75 42.28c-.68.006-1.353.01-2.03.032c-3.1.11-6.195.404-9.25.907c-20.682 3.4-40.108 14.714-55.376 30.28c-18.356-6.394-37.205-10.75-56.656-12.563a78 78 0 0 0-6.844-.343a76 76 0 0 0-2.25.03c-20.82.524-38.94 9.602-52.53 23.063c-15.533 15.385-25.624 36.27-29.658 57.907c-4.033 21.638-2.052 44.237 8.125 62.906c10.178 18.67 29.117 32.835 55.532 36.094c36.828 4.544 70.415 5.794 103.72 4.594l.437.406l.374-.438c27.815-1.036 55.45-3.77 84.625-7.78c-.814 7.602.168 14.882 3.155 21.31c23.12 15.608 47.78 19.41 76.72 11.22a98.5 98.5 0 0 0 10.75-6.687c10.992-7.885 19.667-17.44 25.592-27.47c-10.102-20.476-13.347-41.67-9.656-62.53c-6.924-3.75-15.252-5.577-24.28-5.47c-.715.008-1.433.06-2.156.094c-16.59-32.33-35.69-63.26-60.063-95.563c-15.846-21-37.24-30.144-58.28-30zm2.188 18.532c14.892.16 28.934 6.522 41.156 22.72c22.482 29.797 40.225 58.18 55.75 87.812a88 88 0 0 0-8.313 3.062c-44.5-38.158-89.732-72.463-138.124-93.344c11.76-10.097 25.604-17.187 39.094-19.406c3.515-.578 7-.88 10.438-.843zm165 17.563l-6.625 17.47c30.865 11.68 52.434 36.81 64.5 67.25c-27.754-32.927-66.408-55.983-109.563-56.19l-.094 18.72c27.147.13 52.99 11.29 74.688 29.125c-10.274 5.33-19.327 12.764-27.094 22.438c-3.192 18.115.078 35.896 10.406 54.562c21.263 21.66 46.337 33.562 76.25 33.094c6.106-18.483 7.41-34.1 5.344-47.656c-2.258-58.64-30.98-117.305-87.813-138.813zm-291.782 1.188c69.776 6.655 133.8 50.255 197.97 104.593c-.27.19-.545.37-.813.563a99 99 0 0 0-3.657 2.78l-191.03-67.22l-2.47-40.718zM92.5 80.656l5.563 91.156l-59.094-23.343c.17-1.15.348-2.292.56-3.44c3.39-18.177 12.1-35.84 24.44-48.06c8.12-8.044 17.597-13.812 28.53-16.314zm22.375 59.844l109.656 38.594l-43.624 47.656c-8.55.216-17.118.268-25.78.125l-37.657-43.938zm-76.72 27.75l63.657 25.125l27.907 32.563c-13.784-.8-27.915-2.095-42.595-3.907c-21.254-2.62-33.857-12.56-41.438-26.468c-4.322-7.93-6.81-17.322-7.53-27.312zm397.5 4.813c.374-.02.75 0 1.126 0c12.082 0 21.876 9.794 21.876 21.875c0 12.082-9.794 21.875-21.875 21.875s-21.874-9.795-21.874-21.875c0-11.703 9.193-21.29 20.75-21.875zM242.782 185.53l47.032 16.532c-4.065 5.014-7.425 10.27-10 15.594c-25.21 3.637-48.96 6.376-72.468 7.906l36.094-39.437l-.657-.594zm19 85.157c-22.023 3.702-40.202 14.834-53.28 30.063c13.942 16.114 30.9 26.48 51.47 31.78c24.453 6.303 54.333 5.188 89.624-3.905c-1.118-13.518-6.158-25.815-20.72-36.125c-24.175.564-46.892-6.817-67.093-21.813zM197.595 316.5c-9.088 16.403-13.382 35.462-11.844 54.5c31.95 24.294 61.132 35.617 87.625 35.156c25.832-.45 50.003-11.76 73.594-35.875c.625-7.607 1.596-15.078 2.25-22.342c-35.35 8.516-66.727 9.7-93.94 2.687c-22.447-5.785-41.876-17.308-57.686-34.125zm149.562 78.97c-22.667 18.85-47.38 28.92-73.47 29.374c-26.433.46-53.628-8.724-81.498-26.53c10.815 26.82 35.084 50.75 75.968 63.31c22.05 3.672 40.392 3.026 54.906-1.437c16.862-5.183 28.98-15.113 37.688-31.312c-8.31-10.982-12.13-22.215-13.594-33.406zm150.406 12.655c-21.934 13.607-78.608 25.768-99.343 15.594c-4.52 19.162-15.06 21.715-23.47 18.28c-8.854 14.47-21.072 25.37-35.97 32.156c30.077 21.514 136.794 17.434 158.783-66.03"/></symbol>
|
||||
<symbol id="watchtower" viewBox="0 0 512 512"><path fill="currentColor" d="m256 32l-96 48h23v71h-32v50h30.945L155.36 440.244l-.653.477l.522.72l-4.175 37.566l-.994 8.945l17.89 1.99l.995-8.946L171.61 457h168.78l2.665 23.994l.994 8.945l17.89-1.99l-.995-8.944l-4.174-37.567l.523-.72l-.654-.476L330.054 201H361v-50h-32V80h23zm-48 64h32v48h-32zm64 0h32v48h-32zm-103 73h14v14h-14zm32 0h14v14h-14zm32 0h14v14h-14zm32 0h14v14h-14zm32 0h14v14h-14zm32 0h14v14h-14zm-113.328 32h80.656L256 236.848zm-16.65 9.283L240.33 247h-45.385l4.08-36.717zm113.955 0l4.08 36.717h-45.385zM192.945 265h31.383l-34.822 30.953l3.44-30.953zm58.477 0h9.156l51.75 46H199.672zm36.25 0h31.383l3.44 30.953L287.67 265zm-83.994 64h104.644L256 367.053zm-18.8 8.586L236.323 375h-55.6l4.157-37.414zm142.243 0L331.278 375h-55.6l51.444-37.414zM178.724 393h41.6l-45.26 32.914zm72.205 0h10.144l63.25 46H187.678l63.25-46zm40.75 0h41.6l3.658 32.914z"/></symbol>
|
||||
<symbol id="water-tank" viewBox="0 0 512 512"><path fill="currentColor" d="M160 25c-14.5 0-38 3.89-56.7 11.36c-9.29 3.73-17.41 8.37-22.69 13.25A30 30 0 0 0 75.99 55H244c-1.2-1.77-2.6-3.55-4.6-5.39c-5.3-4.88-13.4-9.52-22.7-13.25C198 28.89 174.5 25 160 25M89 73v129.9l71 42.6l71-42.6V73zm167 78v18h23v14h-30v18h51.3l29.3 29.4l12.8-12.8l-34.7-34.6H297v-14h23v-18zM84.65 221.3L39.12 494.5l17.76 3L74.3 393h171.4l17.4 104.5l17.8-3l-45.5-273.2l-16.7 10l13.7 81.8l-72.4 26.4l-72.38-26.3l13.68-81.9zm263.15 22.4s-10.2 49.6 5.2 59.7c9.8 6.4 28.8-2.9 31-15.4c3.8-19.6-36.2-44.3-36.2-44.3M377 329s-13.4 29.1-5.8 38.5c4.9 5.9 17.4 3.7 20.6-3.6C397 352.3 377 329 377 329m-292.41 2.3l48.91 17.8l-55.22 20.1zm150.81 0l6.3 37.9l-55.2-20.1zm179 7.5s1.4 32.1 12.4 36.9c7.2 3 17.2-4.7 16.8-12.7c-.8-12.6-29.2-24.2-29.2-24.2M160 358.7l44.8 16.3h-89.6zm251.7 40.5s-3.4 21.5 3.4 26.2c4.3 2.9 12.1-1 12.8-6.3c1.3-8.6-16.2-19.9-16.2-19.9"/></symbol>
|
||||
<symbol id="well" viewBox="0 0 512 512"><path fill="currentColor" d="m86.236 28.564l-41.248 110h422.024l-41.248-110zm32.764 128v176.07c76.024 28.87 197.976 28.87 274 0v-176.07h-18v137.428c-7.16-3.66-15.96-6.65-26.307-9.236c-24.208-6.052-56.74-9.64-92.693-9.64c-7.85 0-15.526.18-23 .51c-.017-12.01.002-24.02.04-36.032l24.013-60.03H238.05L215 237.194v39.566c-19.528 1.612-37.125 4.354-51.693 7.996c-10.346 2.586-19.147 5.577-26.307 9.236V156.564zm89 23v16h4.266l6.4-16zm68.44 0l-25.6 64h17.867l25.602-64zm-196.44 23v18h21v-18zm75 0v18h42v-18zm149.492 0l-16.4 41H304v-23h53v-18zm106.508 0v18h12v80h57v-18h-39v-80zm-155 90.55c34.738 0 66.208 3.575 88.326 9.105c11.06 2.763 19.795 6.09 25.035 9.186c4.377 2.587 5.365 4.425 5.576 4.71c.04-.057.064-.074.064 0c0 .073-.023.055-.064 0c-.21.283-1.2 2.12-5.575 4.706c-5.24 3.098-13.975 6.425-25.034 9.19c-22.118 5.53-53.588 9.103-88.326 9.103s-66.208-3.574-88.326-9.103c-11.06-2.765-19.795-6.092-25.035-9.19c-4.377-2.586-5.365-4.423-5.576-4.707c-.04.056-.064.074-.064 0c0-.073.023-.056.064 0c.21-.284 1.2-2.122 5.575-4.71c5.24-3.096 13.975-6.422 25.034-9.186c12.965-3.243 29.16-5.805 47.326-7.376v28.72h18v-29.91c7.456-.347 15.138-.54 23-.54zm137 58.665c-32.96 11.616-71.95 18.42-112 20.417v27.45c40.88-2.024 80.4-9.035 112-21.038zm-274 .02v26.83c9.95 3.777 20.683 7.063 32 9.852V361.16c-11.143-2.664-21.856-5.785-32-9.36zm50 13.202v27.408c29.46 5.657 61.842 8.253 94 7.81v-27.447c-31.99.444-64.16-2.134-94-7.77zm224 32.777a303 303 0 0 1-16 5.146v25.158a254 254 0 0 0 16-5.46zm-274 .01v24.827c27.454 10.426 60.89 17.082 96 19.98v-25.44c-34.372-2.842-67.45-9.302-96-19.366zm240 9.837c-38.988 9.09-83.038 12.637-126 10.67v25.445c43.22 1.968 87.805-1.613 126-10.777zM119 441.78c1.642 9.154 8.428 17.585 16 21.704v-16.558a303 303 0 0 1-16-5.147zm274 .007c-12.8 4.513-26.5 8.312-40.834 11.373v19.73c16.52-4.255 40.596-15.226 40.834-31.103m-240 9.84v19.275c33.85 9.257 73.556 12.422 103 12.534c29.185 0 56.113-2.365 78.166-6.493v-20.37c-58.66 9.767-125.146 8.115-181.166-4.946"/></symbol>
|
||||
<symbol id="wheat" viewBox="0 0 512 512"><path fill="currentColor" d="M98.344 16.688C79.692 43.785 68.498 69.01 65.5 89.56l23.938 39.157l28.624-33.47c.868-21.213-5.49-48.677-19.718-78.563zM472.5 19.625C444.04 36.055 423.112 54 411.562 71.25l4.75 45.688L456.563 99c9.89-18.777 15.938-46.29 15.938-79.375zm-91.75 27.28c-10.153 21.036-16.8 40.84-20.156 58.314l18.375 57.686l19.78-34.25l-6.5-62.22h.03a277 277 0 0 0-11.53-19.53zM27.25 80.782c-.125 23.364 2.393 44.102 6.875 61.314L75.5 186.25l3.125-39.406L46 93.47l.03-.032a280 280 0 0 0-18.78-12.657zm132.844 10.532c-8.415 3.504-16.29 7.213-23.594 11.094l-39.25 45.97l-3.094 39.374l50.438-39.094c6.712-15.904 12.09-35.263 15.5-57.344m177.22 21.626c-24.024 58.09-16.16 97.86 7.873 108.5l21.157-36.625l-19.594-61.438a274 274 0 0 0-9.438-10.438zm146.03.218c-4.55-.028-8.97.084-13.28.28L414.935 138l-19.78 34.28l62.343-13.655c12.897-11.47 26.09-26.626 38.656-45.094c-4.358-.216-8.64-.348-12.812-.374zm-226.094 8.72c-23.24 23.238-38.832 46.003-45.53 65.655l16.436 42.907l34.22-27.75c4.695-20.704 3.436-48.856-5.126-80.812M16.406 159.06c3.28 62.77 27.482 95.31 53.75 94.594l3.344-42.22l-44.063-47a279 279 0 0 0-13.03-5.374zm143.22 11.375a272 272 0 0 0-18.5 4.563l-48.97 37.938l-3.312 41.75c26.492 7.51 57.16-20.567 70.78-84.25zm16.06 1.563c-4.36 22.935-5.65 43.762-4.374 61.5l32.688 51l10.22-38.188l-22.407-58.437h.03a277 277 0 0 0-16.155-15.875zm267.408 8.938l-60.563 13.218l-20.936 36.25c20.682 18.195 60.438 6.035 100.125-45.625a275 275 0 0 0-18.626-3.843m-138.688 25.53c-8.912 1.92-17.304 4.16-25.187 6.657l-46.97 38.03l-10.22 38.19l56.69-29.283c9.493-14.424 18.323-32.49 25.686-53.593zm155.125 25.063c-25.85 20.324-44.046 41.06-53.03 59.782l11.22 44.532l37.28-23.47c7.126-19.99 9.236-48.088 4.53-80.843zm-123.342 8.595c-34.435 77.573-59.394 159.06-62.97 253.03h18.72c3.558-90.792 27.573-169.428 61.312-245.436l-17.063-7.595zm-185.375 6.906c-8.173 62.347 9.714 98.713 35.687 102.75l10.97-40.874l-34.814-54.25a279 279 0 0 0-11.844-7.625zm221.75 24.532c-7.053 22.243-10.817 42.77-11.657 60.532l26.406 54.594L402 349.967l-15.28-60.687h.06c-4.3-5.848-9.033-11.76-14.217-17.717zm-302.47 1.532c-8.664 74.584-8.13 147.835 12.188 220.062h19.44c-20.877-70.772-21.764-143.02-13.064-217.906l-18.562-2.156zm219.47 11.094c-6.613.16-12.953.54-19.032 1.125L215.5 313.78l-10.844 40.408c24.69 12.23 59.938-9.82 84.906-70zm206.718 36.937c-9.072.844-17.664 2.052-25.78 3.594l-51.156 32.217l-14.688 36.657l59.75-22.313c11.14-13.193 22.055-30.075 31.875-50.155zm-157.31 22c-15.528 60.938-2.096 99.19 23.217 106.28l15.72-39.28l-28.094-58.03c-3.43-3-7.053-5.985-10.844-8.97zM183.25 368.72c-12.674 41.233-22.26 82.547-26.844 124.436h18.813c4.507-39.722 13.69-79.23 25.905-118.97l-17.875-5.467zm270 26.655l-58 21.688l-15.563 38.875c23.056 15.098 60.673-2.606 92.625-59.407a273 273 0 0 0-19.062-1.155zM356.5 469.03c-1.874 7.713-3.185 15.757-3.656 24.126h18.687c.45-6.686 1.55-13.206 3.126-19.687l-18.156-4.44z"/></symbol>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 379 KiB After Width: | Height: | Size: 384 KiB |
|
|
@ -559,6 +559,7 @@ button.sheet-skill:hover { background: var(--panel-2); }
|
|||
/* Packs, casts and abilities */
|
||||
.pack { display: grid; gap: 12px; }
|
||||
.fighter.out { opacity: .4; }
|
||||
.fighter[hidden] { display: none; }
|
||||
.traits-inline { display: inline-flex; gap: 2px; margin-left: 6px; vertical-align: middle; }
|
||||
.traits-inline .v-res { display: inline-flex; gap: 2px; margin-left: 4px; }
|
||||
.traits-inline .dmg .gi { width: 13px; height: 13px; }
|
||||
|
|
@ -570,10 +571,22 @@ button.sheet-skill:hover { background: var(--panel-2); }
|
|||
.foe-unit .cast { color: var(--bad); }
|
||||
.foe-unit .cast .bar > span { background: var(--bad); }
|
||||
.ability-bar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
||||
.ab { position: relative; width: 38px; height: 38px; border-radius: 9px; display: grid; place-items: center; font-size: 20px;
|
||||
background: var(--panel-2); border: 1px solid var(--line); overflow: hidden; }
|
||||
.ab { position: relative; width: 42px; height: 52px; display: grid; grid-template-rows: 42px 10px; gap: 0; }
|
||||
.ab-press { position: relative; width: 42px; height: 42px; border-radius: 9px; display: grid; place-items: center; padding: 0;
|
||||
font-size: 20px; color: inherit; background: var(--panel-2); border: 1px solid var(--line); overflow: hidden; cursor: pointer; }
|
||||
.ab.auto .ab-press { cursor: default; }
|
||||
.ab.manual .ab-press { border-color: var(--gold); }
|
||||
.ab.queued .ab-press { box-shadow: 0 0 0 2px var(--good); }
|
||||
.ab-mode { justify-self: center; margin-top: -6px; z-index: 1; font-size: 8.5px; font-weight: 800; letter-spacing: .04em; line-height: 1;
|
||||
padding: 2px 4px; border-radius: 5px; border: 1px solid var(--line); background: var(--panel); color: var(--muted); cursor: pointer; }
|
||||
.ab.manual .ab-mode { color: #1a1408; background: var(--gold); border-color: var(--gold); }
|
||||
.ab.potion { width: 42px; height: 42px; display: grid; place-items: center; border-radius: 9px; background: var(--panel-2);
|
||||
border: 1px solid var(--line); align-self: start; }
|
||||
.fighter.aimable { cursor: pointer; border-radius: 8px; }
|
||||
.fighter.aimed { outline: 2px solid var(--gold); outline-offset: 4px; }
|
||||
.ab-lv .gi { width: 10px; height: 10px; }
|
||||
.ab.locked { opacity: .35; filter: grayscale(1); }
|
||||
.ab.fired { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold); }
|
||||
.ab.fired .ab-press { box-shadow: 0 0 0 2px var(--gold); }
|
||||
.ab.spent { opacity: .35; }
|
||||
.ab .gi { width: 26px; height: 26px; }
|
||||
.fight-log .gi, .cast .gi, [data-u-status] .gi { width: 15px; height: 15px; vertical-align: -3px; }
|
||||
|
|
@ -581,6 +594,8 @@ button.sheet-skill:hover { background: var(--panel-2); }
|
|||
.ab-lv { position: absolute; bottom: 0; right: 2px; font-size: 10px; font-weight: 700; color: var(--muted); filter: none; }
|
||||
.ab-gap { width: 6px; }
|
||||
.fight-log .warn { color: var(--gold); }
|
||||
.run-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
|
||||
.run-head .btn { margin-left: auto; }
|
||||
.pill { padding: 3px 10px 3px 3px; }
|
||||
.pill .dmg { border-radius: 50%; }
|
||||
|
||||
|
|
|
|||
|
|
@ -736,6 +736,178 @@ class AbilityTest(unittest.TestCase):
|
|||
self.assertIsNone(world["parties"]["1"]["fight"])
|
||||
|
||||
|
||||
class ActivePlayTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.world = world_with(("ash", "miner"))
|
||||
self.p = self.world["players"]["ash"]
|
||||
self.p["xp"]["combat"] = game.XP_TABLE[30]
|
||||
self.p["equip"].update(weapon="steel_warhammer", helmet="steel_helmet", body="steel_body", legs="steel_legs")
|
||||
self.p["hp"] = game.max_hp(self.p)
|
||||
self.p["last_active"] = T0
|
||||
game.start_action(self.world, self.p, "fight_stone_guardians", T0)
|
||||
game.process_player(self.world, self.p, T0, random.Random(3)) # two stone guardians
|
||||
self.fight = self.p["action"]["fight"]
|
||||
|
||||
def at(self, t):
|
||||
"""The real moment `t` seconds into the fight."""
|
||||
return T0 + (self.fight["rest"] + t) / self.fight["pace"]
|
||||
|
||||
def mine(self, label, kinds=("cast", "use", "hit", "crit")):
|
||||
return [e for e in self.fight["events"] if e[1] == "ash" and e[6] == label and e[3] in kinds]
|
||||
|
||||
def test_switching_and_pressing(self):
|
||||
self.assertEqual(game.press(self.world, self.p, "crushing_blow", self.at(5)), "That one's on auto")
|
||||
self.assertIsNone(game.set_manual(self.world, self.p, "crushing_blow", True, T0)) # yours from the start
|
||||
self.assertEqual(self.p["manual"], ["crushing_blow"])
|
||||
watch = (self.fight["rest"] + game.member_of(self.fight, "ash")["watch"]) / self.fight["pace"]
|
||||
self.assertTrue(all(e[0] >= watch for e in self.mine("crushing_blow", ("cast",)))) # back on auto once you look away
|
||||
before = [e for e in self.fight["events"] if e[0] < self.at(5) - T0]
|
||||
self.assertIsNone(game.press(self.world, self.p, "crushing_blow", self.at(5)))
|
||||
self.assertEqual([e for e in self.fight["events"] if e[0] < self.at(5) - T0], before) # the past stands
|
||||
casts = [e[0] for e in self.mine("crushing_blow", ("cast",)) if e[0] >= self.at(5) - T0]
|
||||
self.assertTrue(casts)
|
||||
self.assertLessEqual(casts[0], self.at(5 + combat_queue()) - T0) # as soon as it's ready
|
||||
|
||||
def test_while_watching_auto_leaves_your_abilities_alone(self):
|
||||
game.set_manual(self.world, self.p, "crushing_blow", True, self.at(1))
|
||||
watch = game.member_of(self.fight, "ash")["watch"]
|
||||
uses = [e[0] for e in self.mine("crushing_blow", ("cast",))]
|
||||
self.assertTrue(all(u >= (self.fight["rest"] + watch) / self.fight["pace"] for u in uses if u > self.at(1) - T0))
|
||||
# Look again and again: still nothing on auto until you look away.
|
||||
for t in (5, 10, 15):
|
||||
game.watch_fight(self.world, self.p, self.at(t))
|
||||
self.assertGreater(game.member_of(self.fight, "ash")["watch"], 25)
|
||||
|
||||
def test_tapping_an_enemy_turns_you_on_it(self):
|
||||
self.assertIsNone(game.set_fight_target(self.world, self.p, "e1", self.at(2)))
|
||||
swings = [e for e in self.fight["events"] if e[1] == "ash" and e[3] in ("hit", "crit", "miss")
|
||||
and e[6] in game.DAMAGE_TYPES and e[0] > self.at(2.5) - T0]
|
||||
dead = {e[2]: e[0] for e in self.fight["events"] if e[3] == "die"}
|
||||
self.assertTrue(all(e[2] == "e1" for e in swings if e[0] < dead.get("e1", 1e9)))
|
||||
|
||||
def test_no_presses_while_resting_or_out_of_a_fight(self):
|
||||
game.set_manual(self.world, self.p, "crushing_blow", True, T0)
|
||||
self.assertEqual(game.press(self.world, self.p, "crushing_blow", T0), "Still resting")
|
||||
later = T0 + self.fight["duration"] + 100
|
||||
self.p["action"] = None
|
||||
self.assertEqual(game.press(self.world, self.p, "crushing_blow", later), "You're not fighting")
|
||||
|
||||
def test_old_worlds_get_manual(self):
|
||||
del self.p["manual"]
|
||||
self.world["version"] = 27
|
||||
game.migrate(self.world, T0)
|
||||
self.assertEqual(self.p["manual"], [])
|
||||
|
||||
|
||||
def combat_queue():
|
||||
return game.combat.QUEUE + game.combat.GCD
|
||||
|
||||
|
||||
class DungeonTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.world = world_with(("wil", "builder"), ("kay", "homesteader"), ("ann", "miner"))
|
||||
gear = {"builder": dict(weapon="rune_sword", shield="rune_shield", helmet="rune_helmet", body="rune_body",
|
||||
legs="rune_legs"),
|
||||
"homesteader": dict(weapon="magic_staff", helmet="rune_helmet", body="rune_body", legs="rune_legs"),
|
||||
"miner": dict(weapon="rune_warhammer", helmet="rune_helmet", body="rune_body", legs="rune_legs")}
|
||||
for p in self.world["players"].values():
|
||||
p["xp"]["combat"] = game.XP_TABLE[60] # well past it: these are about the run, not the fights
|
||||
p["equip"].update(gear[p["cls"]])
|
||||
p["hp"] = game.max_hp(p)
|
||||
p["last_active"] = T0
|
||||
self.wil, self.kay, self.ann = (self.world["players"][uid] for uid in ("wil", "kay", "ann"))
|
||||
|
||||
def online(self, now, players=None):
|
||||
for p in players or self.world["players"].values():
|
||||
p["last_active"] = now
|
||||
game.catch_up(self.world, now, random.Random(1))
|
||||
|
||||
def party(self):
|
||||
return game.party_of(self.world, self.wil)
|
||||
|
||||
def test_a_run_goes_pull_by_pull_and_pays_on_the_way_out(self):
|
||||
self.assertIsNone(game.start_action(self.world, self.wil, "dungeon_watchtower", T0))
|
||||
self.online(T0 + 600)
|
||||
self.assertNotIn("fight", self.wil["action"]) # alone, it waits
|
||||
self.assertIsNone(game.join_fight(self.world, self.kay, "wil", T0 + 600))
|
||||
names = []
|
||||
now = T0 + 600
|
||||
while game.party_of(self.world, self.wil):
|
||||
fight = self.party()["fight"] or {}
|
||||
if fight.get("stage"):
|
||||
names.append(fight["enemy"])
|
||||
now += 2
|
||||
self.online(now)
|
||||
self.assertLess(now, T0 + 6 * 3600)
|
||||
pulls = game.ACTION_BY_ID["dungeon_watchtower"]["pulls"]
|
||||
self.assertEqual(list(dict.fromkeys(names)), pulls)
|
||||
for p in (self.wil, self.kay):
|
||||
self.assertIsNone(p["action"])
|
||||
self.assertEqual(p["stopped"], "Cleared Watchtower Depths")
|
||||
self.assertGreaterEqual(p["gold"], game.DUNGEONS["dungeon_watchtower"]["bonus_gold"])
|
||||
self.assertIn("cleared Watchtower Depths", self.world["ledger"][-1]["text"])
|
||||
|
||||
def test_joining_only_before_the_first_pull_and_up_to_four(self):
|
||||
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||
self.assertIsNone(game.join_fight(self.world, self.kay, "wil", T0))
|
||||
self.online(T0 + 1)
|
||||
self.assertIsNone(game.join_fight(self.world, self.ann, "wil", T0 + 1)) # still resting: in time
|
||||
self.party()["run"]["pull"] = 1
|
||||
extra = game.new_player("bo", "Bo", "ranger", T0)
|
||||
extra["xp"]["combat"] = game.XP_TABLE[60]
|
||||
self.world["players"]["bo"] = extra
|
||||
self.assertEqual(game.join_fight(self.world, extra, "wil", T0 + 2), "The run has started")
|
||||
self.assertFalse(game.joinable(self.world, self.wil))
|
||||
|
||||
def test_a_run_waits_for_whoever_looked_away(self):
|
||||
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||
game.join_fight(self.world, self.kay, "wil", T0)
|
||||
self.online(T0 + 300, [self.wil]) # kay went to make tea
|
||||
self.online(T0 + 1200, [self.wil])
|
||||
self.assertIsNone(self.party()["fight"])
|
||||
self.assertEqual(game.run_of(self.world, self.wil)["away"], ["Kay"])
|
||||
self.online(T0 + 1210)
|
||||
self.assertIsNotNone(self.party()["fight"])
|
||||
|
||||
def test_three_wipes_on_a_pull_end_the_run(self):
|
||||
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||
game.join_fight(self.world, self.kay, "wil", T0)
|
||||
for p in (self.wil, self.kay):
|
||||
p["equip"] = {slot: None for slot in p["equip"]}
|
||||
p["xp"]["combat"] = game.XP_TABLE[20]
|
||||
self.party()["run"] = {"pull": 7, "wipes": 0} # the ogre, naked
|
||||
now = T0
|
||||
while game.party_of(self.world, self.wil):
|
||||
now += 30
|
||||
self.online(now)
|
||||
self.assertLess(now, T0 + 3 * 3600)
|
||||
self.assertEqual(self.wil["stopped"], "Wiped 3 times at Watchtower ogre")
|
||||
|
||||
def test_the_leader_can_pull_early(self):
|
||||
game.start_action(self.world, self.wil, "dungeon_watchtower", T0)
|
||||
game.join_fight(self.world, self.kay, "wil", T0)
|
||||
self.kay["hp"] = 1
|
||||
self.online(T0 + 0.5)
|
||||
fight = self.party()["fight"]
|
||||
self.assertGreater(fight["rest"], 10)
|
||||
self.assertEqual(game.pull_now(self.world, self.kay, T0 + 3), "Only the leader pulls")
|
||||
self.assertIsNone(game.pull_now(self.world, self.wil, T0 + 3))
|
||||
self.assertLessEqual(fight["rest"], 3)
|
||||
self.assertLess(game.member_of(fight, "kay")["hp_in"], game.max_hp(self.kay) / 2)
|
||||
|
||||
def test_adds_join_halfway_and_never_count_if_they_dont(self):
|
||||
foes = game.DUNGEONS["dungeon_watchtower"]["pulls"][7][1]
|
||||
self.assertTrue(foes[1]["dormant"] and foes[2]["dormant"])
|
||||
players = [self.wil, self.kay]
|
||||
fight = game.draw_pull(self.world, players, game.ACTION_BY_ID["dungeon_watchtower"], {"pull": 7, "wipes": 0},
|
||||
random.Random(2), T0)
|
||||
joined = [e for e in fight["events"] if e[3] == "join"]
|
||||
yell = next(e for e in fight["events"] if e[3] == "yell")
|
||||
self.assertEqual(yell[6], "Crush them!")
|
||||
self.assertEqual({e[1] for e in joined}, {"e1", "e2"})
|
||||
self.assertGreaterEqual(joined[0][0], yell[0])
|
||||
|
||||
|
||||
class NewSkillsTest(unittest.TestCase):
|
||||
def test_tools_speed_up_gathering(self):
|
||||
world = world_with(("pia", "commoner"))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue