Achievements page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f6951b0f12
commit
a05530b802
5 changed files with 152 additions and 4 deletions
73
game.py
73
game.py
|
|
@ -1524,7 +1524,7 @@ RAID_TRAITS = ["lightly armoured, heavy on archers", "mounted and fast, few bows
|
|||
|
||||
# --- world ---------------------------------------------------------------
|
||||
|
||||
WORLD_VERSION = 30
|
||||
WORLD_VERSION = 31
|
||||
|
||||
|
||||
def new_world(now: float, speed: float = 1.0) -> dict:
|
||||
|
|
@ -1764,6 +1764,12 @@ def migrate(world: dict, now: float | None = None) -> dict:
|
|||
for player in world["players"].values():
|
||||
player.setdefault("patterns", [])
|
||||
world["version"] = 30
|
||||
if world["version"] < 31:
|
||||
# Uniques ever found, for achievements: whatever's held now.
|
||||
for player in world["players"].values():
|
||||
held = [*player["bank"], *player["equip"].values()]
|
||||
player.setdefault("found", [item for item in UNIQUES if item in held])
|
||||
world["version"] = 31
|
||||
return world
|
||||
|
||||
|
||||
|
|
@ -2020,7 +2026,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}, "manual": [], "patterns": [], "eat_below": EAT_BELOW,
|
||||
"perks": {"bank_slot": 0}, "manual": [], "patterns": [], "found": [], "eat_below": EAT_BELOW,
|
||||
"rented": 0, "boosts": [], "bought": None, "deeds": {}, "owned": [], "look": dict(DEFAULT_LOOK),
|
||||
"garrison": False,
|
||||
"donated": 0,
|
||||
|
|
@ -2082,6 +2088,8 @@ def city_uses(item: str) -> list[str]:
|
|||
|
||||
def bank_add(world: dict, player: dict, item: str, qty: int, now: float, summary: dict) -> int:
|
||||
"""Store what fits; the rest goes to the city if it has a use, else is sold. Returns qty kept."""
|
||||
if ITEMS[item].get("unique") and item not in player["found"]:
|
||||
player["found"].append(item) # for achievements: once found, always found
|
||||
keep = min(qty, bank_room(world, player, item))
|
||||
if keep:
|
||||
player["bank"][item] = player["bank"].get(item, 0) + keep
|
||||
|
|
@ -5626,6 +5634,65 @@ DRILL_SERGEANT_POINTS = 30 # boosted drilling per hire: doubled points and
|
|||
DRILL_SERGEANT_FAVOUR = 150 # +50% for every further hire against the same threat
|
||||
|
||||
|
||||
# --- achievements ----------------------------------------------------------------
|
||||
# What's left to unlock. Read off what you already have, never a source of anything:
|
||||
# patterns learned, uniques found, titles, looks, your home.
|
||||
|
||||
def _got(name: str, done, glyph: str, tint: str, tip: str = "", colour: str | None = None) -> dict:
|
||||
return {"name": name, "done": bool(done), "glyph": glyph, "tint": tint, "tip": tip, **({"colour": colour} if colour else {})}
|
||||
|
||||
|
||||
def achievements(player: dict) -> list[dict]:
|
||||
"""Sections of cards, each card a set of things to collect."""
|
||||
skill_name = {s["id"]: s["name"] for s in SKILLS}
|
||||
dungeons = sorted(DUNGEON_LOOT, key=lambda key: DUNGEONS[key]["level"])
|
||||
|
||||
patterns = []
|
||||
for key in dungeons:
|
||||
cards = []
|
||||
for pattern in ACTION_BY_ID[key]["loot"]["patterns"]:
|
||||
spec, made = ITEMS[pattern], ITEMS[ITEMS[pattern]["pattern"]]
|
||||
held = pattern not in player["patterns"] and player["bank"].get(pattern)
|
||||
tip = f"{skill_name[spec['skill']]} {spec['level']}" + (" · 📜 in bank" if held else "")
|
||||
cards.append({**_got(made["name"], pattern in player["patterns"], made["glyph"], made["tint"], tip), "held": bool(held)})
|
||||
patterns.append({"name": DUNGEONS[key]["name"], "entries": cards})
|
||||
found = player.get("found", [])
|
||||
uniques = [_got(ITEMS[unique]["name"], unique in found, ITEMS[unique]["glyph"], ITEMS[unique]["tint"],
|
||||
DUNGEONS[key]["name"]) for key in dungeons for unique in [DUNGEON_LOOT[key][2]]]
|
||||
|
||||
def level_title(need: int, rank: str) -> list[dict]:
|
||||
return [_got(f"{rank} {noun}", skill_level(player, skill) >= need, *SKILL_GLYPHS[skill],
|
||||
f"{skill_name[skill]} {skill_level(player, skill)} / {need}") for skill, noun in SKILL_TITLES.items()]
|
||||
deeds = {"defended": ("checked-shield", "#d4dae2", "Raids won on guard"), "dragons": ("dragon-head", "#e05a5a", "Dragons slain on guard"),
|
||||
"bosses": ("crossed-swords", "#e0b04a", "Bosses beaten")}
|
||||
feats = [_got(title, skill_level(player, skill) >= need, *SKILL_GLYPHS[skill], f"{skill_name[skill]} {skill_level(player, skill)} / {need}")
|
||||
for skill, need, title in SKILL_EXTRA_TITLES]
|
||||
feats += [_got(title, player["deeds"].get(key, 0) >= need, *deeds[key][:2], f"{deeds[key][2]} {player['deeds'].get(key, 0)} / {need}")
|
||||
for key, need, title in DEED_TITLES]
|
||||
feats.append(_got("Patron of the City", player["donated"] >= 100_000, "sparkles", "#b98ce8",
|
||||
f"Given {player['donated']:,} / 100,000"))
|
||||
|
||||
def bought(kind: str) -> list[dict]:
|
||||
return [_got(spec["value"] if kind == "title" else spec["name"], key in player["owned"],
|
||||
"scroll-unfurled" if kind == "title" else spec.get("value", ""), "#e0b04a",
|
||||
f"Caravan · {spec['price']:,} gold", spec["value"] if kind == "colour" else None)
|
||||
for key, spec in COSMETICS.items() if spec["kind"] == kind and spec.get("price")]
|
||||
bosses = [_got(spec["name"], key in player["owned"], ACTION_BY_ID[spec["boss"]]["glyph"], ACTION_BY_ID[spec["boss"]]["tint"],
|
||||
ACTION_BY_ID[spec["boss"]]["name"]) for key, spec in COSMETICS.items() if spec.get("boss")]
|
||||
homes = [_got(name, player.get("home", 0) >= level, "castle", "#d4dae2", f"{cost:,} gold")
|
||||
for level, (name, cost) in enumerate(HOMES) if level]
|
||||
|
||||
return [
|
||||
{"name": "Masterwork patterns", "cards": patterns},
|
||||
{"name": "Dungeon loot", "cards": [{"name": "Uniques", "entries": uniques}]},
|
||||
{"name": "Titles", "cards": [{"name": "Masters", "entries": level_title(50, "Master")},
|
||||
{"name": "Grandmasters", "entries": level_title(99, "Grandmaster")},
|
||||
{"name": "Deeds", "entries": feats}, {"name": "Caravan titles", "entries": bought("title")}]},
|
||||
{"name": "Looks", "cards": [{"name": "Boss emblems", "entries": bosses}, {"name": "Caravan emblems", "entries": bought("emblem")},
|
||||
{"name": "Dyes", "entries": bought("colour")}, {"name": "Home", "entries": homes}]},
|
||||
]
|
||||
|
||||
|
||||
def drill_per_action(player: dict) -> float:
|
||||
return DRILL_PER_ACTION * (1 + 0.25 * (skill_level(player, "combat") // 20)) * ware(player, "drill", "combat")
|
||||
|
||||
|
|
@ -6014,6 +6081,8 @@ PATCH_NOTES = [
|
|||
"lens, Wyrmscale quiver. Abilities it changes get a ✦."),
|
||||
("new", "Choose when you eat between fights: below 25 / 50 / 75% of your HP, always, or never (the food slot, "
|
||||
"under \"Eat below\"). Default 50%: above it you just rest, so a scratch doesn't cost food."),
|
||||
("new", "🏆 Achievements (under You): 127 to collect. Masterwork patterns per dungeon (7 each), the 7 uniques, titles, "
|
||||
"boss emblems, caravan looks, home. Tap an icon for what it is and how to get it. Nothing is awarded there, it only keeps track."),
|
||||
("new", "Tapping an enemy or ally switches at once, and your next press goes to them: tap, then interrupt, "
|
||||
"without waiting."),
|
||||
("new", "HP bars show what was just lost, with each fighter's last hit or heal by their HP."),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue