diff --git a/game.py b/game.py index 23adb15..5822d6c 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", +UI_GLYPHS = ["two-coins", "sparkles", "hearts", "prayer", "checked-shield", "magic-shield", "stopwatch", *(kind["glyph"] for kind in DAMAGE_TYPES.values())] diff --git a/static/app.js b/static/app.js index 6f2e9f9..c6a484f 100644 --- a/static/app.js +++ b/static/app.js @@ -664,6 +664,39 @@ function gearStats(it) { return stats.filter(Boolean).join(", ") + (it.weight ? ` (${C.weights[it.weight].name.toLowerCase()})` : ""); } +const GEAR_SLOTS = ["weapon", "shield", "helmet", "body", "legs"]; + +// A weapon's or armour piece's stats in a small grid. Weapons: damage type and power | 1H or 2H | swing +// time; armour: armour | weight | ward. Below: what it needs to wear, and any bonus for you. +function gearChips(it) { + const cell = (icon, text, tip, cls = "") => `${icon}${text === "" ? "" : `${text}`}`; + const hands = (two) => `${two ? "2H" : "1H"}`; + let row = ["", "", ""]; + if (it.style) { + const st = C.styles[it.style], dt = C.damage_types[st.damage]; + row = [cell(glyph(dt.glyph, dt.tint), it.power, `${dt.name} damage, +${it.power} power`), + cell(hands(it.two_handed), "", it.two_handed ? "Two-handed: no shield" : "One-handed: room for a shield"), + cell(glyph("stopwatch", "#9fb3c8"), `${st.interval}s`, `A swing every ${st.interval}s`)]; + } else if (it.slot === "shield") { + row = [cell(glyph("checked-shield", "#d4dae2"), it.armour, `+${it.armour} armour, +${it.power} power`), + cell(hands(false), "", "Held in one hand"), + cell(glyph("checked-shield", "#e0b04a"), `${Math.round(C.block_chance * 100)}%`, "Blocks this share of hits")]; + } else if (it.weight) { + row = [cell(glyph("checked-shield", "#d4dae2"), it.armour, `+${it.armour} armour: less damage from blows`), + cell("", C.weights[it.weight].name, `${C.weights[it.weight].name} armour`, "muted"), + cell(glyph("magic-shield", "#a77be8"), it.ward, `+${it.ward} ward: less damage from magic`)]; + } + let req = "", bonus = ""; + if (it.req) { + const [skill, level] = it.req; + req = cell(skillOf(skill).icon, level, `Needs ${skillOf(skill).name.toLowerCase()} ${level}`, S.me.levels[skill] < level ? "short" : ""); + } + if (it.style && C.class_style[S.me.cls] === it.style) { + bonus = cell(C.classes[S.me.cls].icon, "+15%", "Your trade's weapon: +15% damage", "trade"); + } + return `
${row.join("")}${req}${bonus || ""}
`; +} + // What an item does once made: healing, defence, tool speed, and who can use it. function itemStats(id) { const it = item(id); @@ -733,7 +766,9 @@ function actionCard(a) { } if (a.special === "bury") extra = `
${skillOf("prayer").icon} +${a.pp + temple} prayer points
`; if (a.special === "offer") extra = `
${C.buildings.temple.icon} +${+(a.devotion * (1 + 0.25 * temple)).toFixed(1)} devotion
`; - const made = Object.keys(a.outputs).map(itemStats).filter(Boolean); + const gear = Object.keys(a.outputs).filter((id) => GEAR_SLOTS.includes(item(id).slot)); + const made = Object.keys(a.outputs).filter((id) => !gear.includes(id)).map(itemStats).filter(Boolean); + extra += gear.map((id) => gearChips(item(id))).join(""); if (made.length) extra += `
${made.join(" · ")}
`; return `
@@ -803,6 +838,7 @@ function itemPanel(id) {
${it.icon}

${esc(it.name)}

You have ${fmt(have)} · worth ${price % 1 ? price.toFixed(1) : price} gold each${merchant ? " (merchant in town!)" : ""}
+ ${GEAR_SLOTS.includes(it.slot) ? gearChips(it) : ""} ${it.slot ? equipRow(id, it) : ""} ${it.potion ? `
${esc(it.potion.text)} for ${C.potion_charges} ${it.potion.skill === "combat" ? "fights" : "actions"}.
@@ -833,8 +869,7 @@ function equipRow(id, it) { function equipLabel(it) { if (it.tool) return `Use as ${skillOf(it.tool).name.toLowerCase()} tool (${Math.round(it.speed * 100)}% faster)`; - const stats = gearStats(it); - return `Equip ${it.slot === "body" ? "body armour" : it.slot === "legs" ? "leg armour" : it.slot} (${stats})`; + return "Equip"; } function goldPanel() { diff --git a/static/icons.svg b/static/icons.svg index 928af53..c983e02 100644 --- a/static/icons.svg +++ b/static/icons.svg @@ -139,6 +139,7 @@ + diff --git a/static/styles.css b/static/styles.css index 79be475..38cb634 100644 --- a/static/styles.css +++ b/static/styles.css @@ -456,3 +456,17 @@ button.sheet-skill:hover { background: var(--panel-2); } .v-attack, .v-res { display: inline-flex; gap: 2px; } .v-hp { font-size: 12px; } .fighter-name .v-res { margin-left: 6px; } + +/* Gear stats: left, middle and right columns */ +.stat-grid { display: grid; grid-template-columns: 1fr auto 1fr; gap: 4px 10px; align-items: center; margin: 4px 0 2px; + padding: 6px 10px; border-radius: 8px; background: var(--bg); font-size: 13px; } +.stat-grid > :nth-child(3n + 1) { justify-self: start; } +.stat-grid > :nth-child(3n + 2) { justify-self: center; } +.stat-grid > :nth-child(3n) { justify-self: end; } +.stat-grid .gstat { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; } +.stat-grid .gi { width: 16px; height: 16px; } +.stat-grid .gstat.short { color: var(--warn); } +.stat-grid .gstat.trade { color: var(--gold); } +.stat-grid .gstat.muted { font-weight: 500; } +.hands { font-size: 11px; font-weight: 700; letter-spacing: .04em; color: #d6b48a; + border: 1px solid rgba(214, 180, 138, .5); border-radius: 4px; padding: 0 4px; }