From 2f2681ba176ebb5b16e6f3b9d48ddde3bf9b888d Mon Sep 17 00:00:00 2001 From: rope Date: Tue, 20 Oct 2026 15:20:56 +0100 Subject: [PATCH] Equipment: pick a slot to swap gear from the bank; plain totals Co-Authored-By: Claude Opus 5.5 --- game.py | 2 +- static/app.js | 56 +++++++++++++++++++++++++++++------------------ static/icons.svg | 1 + static/styles.css | 16 +++++++++++++- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/game.py b/game.py index 2e7879a..8fd30f9 100644 --- a/game.py +++ b/game.py @@ -3560,7 +3560,7 @@ ACTION_GLYPHS.update({ for _index, (_area, _level, _prefix, _races) in enumerate(AREAS): for _race, *_rest in _races: ACTION_GLYPHS[f"fight_{_slug(_race)}"] = (RACE_GLYPHS[_race], TIER_TINTS[_index]) -UI_GLYPHS = ["two-coins", "sparkles", "hearts", "prayer", "checked-shield", "magic-shield", "stopwatch", "swordman", +UI_GLYPHS = ["two-coins", "sparkles", "hearts", "prayer", "checked-shield", "magic-shield", "stopwatch", "swordman", "biceps", "cancel", "cape", "gem-pendant", "gauntlet", "ring", "quiver", "leather-boot", "crystal-ball", "visored-helm", "breastplate", "leg-armor", "pointy-sword", "round-shield", *(kind["glyph"] for kind in DAMAGE_TYPES.values())] diff --git a/static/app.js b/static/app.js index cba657f..ebf7142 100644 --- a/static/app.js +++ b/static/app.js @@ -1244,32 +1244,46 @@ function dollSlot(p, slot, label, outline, picked) { } function paperDoll(p, mine) { - const worn = DOLL.filter(([slot]) => p.equip[slot]).map(([slot]) => slot); - const pick = (dollPick && slot2(dollPick, p)) || (worn.includes("weapon") ? "weapon" : worn[0]) || "weapon"; - const label = DOLL.find(([slot]) => slot === pick)[1]; - const two = pick === "shield" && p.equip.weapon && item(p.equip.weapon).two_handed; - const id = two ? p.equip.weapon : p.equip[pick], it = id && item(id); - const detail = it ? `
${it.icon}${esc(it.name)} - ${label}${two ? " (two-handed weapon)" : ""} - ${mine && !two ? `` : ""}
- ${gearChips(it, p)}` - : `
${label}: nothing worn.${mine ? ` Equip from the .` : ""}
`; - const st = C.styles[(p.equip.weapon && item(p.equip.weapon).style) || "sword"], dt = C.damage_types[st.damage]; + const pick = dollPick && slot2(dollPick, p); + const totals = `
+ ${glyph("biceps", "#e06a6a")} Power${p.power} + ${glyph("checked-shield", "#d4dae2")} Armour${p.armour} + ${glyph("magic-shield", "#a77be8")} Ward${p.ward} +
`; + let side = totals; + if (pick) { + const label = DOLL.find(([slot]) => slot === pick)[1]; + const two = pick === "shield" && p.equip.weapon && item(p.equip.weapon).two_handed; + const id = two ? p.equip.weapon : p.equip[pick], it = id && item(id); + side = `
${it ? `${it.icon}${esc(it.name)}` : `${label}empty`}
+ ${it ? gearChips(it, p) : ""}${mine ? swapGrid(p, pick, two) : ""}${totals}`; + } return `
${glyph("swordman", "#e6e8ee")} ${DOLL.map(([slot, name, outline]) => dollSlot(p, slot, name, outline, slot === pick)).join("")}
-
-
${detail}
-
- ${glyph(dt.glyph, dt.tint)} ${dt.name}, a swing every ${st.interval}s - Power${p.power} - ${glyph("checked-shield", "#d4dae2")} Armour${p.armour} -${Math.round(p.reduction * 100)}% physical - ${glyph("magic-shield", "#a77be8")} Ward${p.ward} -${Math.round(p.ward_reduction * 100)}% magic -
-
+
${side}
`; } +// What else in the bank fits the picked slot, best first: click one to put it on. For weapons the +// corner shows the damage type, so you can pick one for what you're about to fight. +function swapGrid(p, slot, two) { + const fits = Object.keys(p.bank).filter((id) => item(id).slot === slot) + .sort((a, b) => (item(b).tier ?? 0) - (item(a).tier ?? 0) || (item(b).power ?? 0) - (item(a).power ?? 0)); + const tiles = fits.map((id) => { + const it = item(id), [skill, level] = it.req || []; + const why = two ? `Your ${item(p.equip.weapon).name.toLowerCase()} takes both hands` + : skill && p.levels[skill] < level ? `Needs ${skillOf(skill).name.toLowerCase()} ${level}` : ""; + const corner = it.style ? dmgIcon(C.styles[it.style].damage) : ""; + const tip = [it.name, gearStats(it), it.style ? (it.two_handed ? "two-handed" : "one-handed") : "", why].filter(Boolean).join(" · "); + return ``; + }); + const off = p.equip[slot] && !two ? `` : ""; + if (!tiles.length && !off) return `
Nothing else in your bank fits here.
`; + return `
Swap for
${tiles.join("")}${off}
`; +} + // A picked slot only counts if it's one this character has. function slot2(slot, p) { return slot in p.equip ? slot : null; @@ -1380,7 +1394,7 @@ document.addEventListener("click", (e) => { if (d.once) return act("/api/action", { id: d.once, once: true }); if ("stop" in d) return act("/api/stop", {}); if (d.doll) { - dollPick = d.doll; + dollPick = dollPick === d.doll ? null : d.doll; return render(); } if ("select" in d) { diff --git a/static/icons.svg b/static/icons.svg index b750888..9631a34 100644 --- a/static/icons.svg +++ b/static/icons.svg @@ -23,6 +23,7 @@ + diff --git a/static/styles.css b/static/styles.css index 62da4c2..af1bbfd 100644 --- a/static/styles.css +++ b/static/styles.css @@ -487,7 +487,6 @@ button.sheet-skill:hover { background: var(--panel-2); } .doll-slot.small .gi { width: 26px; height: 26px; } .doll-side { display: grid; gap: 12px; min-width: 0; } .doll-stats { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 13px; align-items: center; } -.doll-stats > span:first-child { grid-column: 1 / -1; margin-bottom: 2px; } .doll-stats b { justify-self: end; } .doll-stats .gi { width: 14px; height: 14px; } .worn-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } @@ -496,3 +495,18 @@ button.sheet-skill:hover { background: var(--panel-2); } .worn-head .btn { margin-left: auto; } .tool-slots { display: flex; flex-wrap: wrap; gap: 8px; } @media (max-width: 520px) { .doll-wrap { grid-template-columns: 1fr; justify-items: center; } .doll-side { width: 100%; } } + +/* Swapping gear from the doll */ +.swap-head { margin-top: 2px; } +.swap-grid { display: flex; flex-wrap: wrap; gap: 6px; } +.doll-slot.swap { width: 44px; height: 44px; } +.doll-slot.swap .gi { width: 28px; height: 28px; } +.doll-slot.swap:hover:not(:disabled) { border-color: var(--gold); } +.doll-slot.swap:disabled { opacity: .4; cursor: not-allowed; } +.doll-slot.swap.off { border-style: dashed; background: transparent; } +.doll-slot.swap.off .gi { width: 18px; height: 18px; } +.swap-type { position: absolute; bottom: -4px; right: -4px; } +.swap-type .dmg { width: 16px; height: 16px; background: var(--panel); border-radius: 4px; } +.swap-qty { position: absolute; top: 1px; left: 3px; font-size: 10px; font-weight: 700; color: var(--muted); } +.doll-slot.swap .swap-type .gi { width: 11px; height: 11px; } +.doll-slot.swap .hands { top: 1px; right: 1px; font-size: 8px; padding: 0 2px; }