diff --git a/game.py b/game.py
index fae30c5..deb8f73 100644
--- a/game.py
+++ b/game.py
@@ -3562,7 +3562,7 @@ for _index, (_area, _level, _prefix, _races) in enumerate(AREAS):
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", "biceps", "cancel", "striking-diamonds",
"archery-target", "dodging", "shield-reflect",
- "cape", "gem-pendant", "gauntlet", "ring", "quiver", "leather-boot", "crystal-ball", "hot-meal", "visored-helm", "breastplate",
+ "cape", "gem-pendant", "gauntlet", "ring", "quiver", "leather-boot", "crystal-ball", "hot-meal", "round-bottom-flask", "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 a270d63..8f9bb76 100644
--- a/static/app.js
+++ b/static/app.js
@@ -426,8 +426,7 @@ function skillPage(id) {
if (id === "construction") html += S.city.construction ? siteBanner()
: `
Nothing under construction.
`;
if (id === "farming" && me.cls === "homesteader") html += plotsPanel();
- html += potionPanel(id);
- if (`tool_${id}` in me.equip) html += toolPanel(id);
+ html += skillGear(id);
if (id === "reconnaissance") html += `Raises intel on the coming threat. Higher levels open as it nears.
`;
html += skillHappenings(id);
const live = new Set(S.happenings.problems.map((p) => p.kind));
@@ -473,19 +472,32 @@ function plotsPanel() {
}
// The skill's active potion, or the ones in the bank that could be drunk for it.
-function potionPanel(skill) {
- const me = S.me;
+// A skill's potion and tool side by side, each a slot with what you could switch to beside it.
+function skillGear(skill) {
+ const me = S.me, cards = [];
const kinds = Object.entries(C.items).filter(([, it]) => it.potion?.skill === skill);
- if (!kinds.length) return "";
- const active = me.potions[skill];
- const owned = kinds.filter(([id]) => me.bank[id]);
- const drink = owned.map(([id, it]) => `${it.icon} ${esc(it.name)} (${fmt(me.bank[id])}) `).join("");
- return `${active
- ? `${item(active.item).icon} ${esc(item(active.item).name)} ${esc(item(active.item).potion.text)} · ${fmt(active.charges)} uses left${me.bank[active.item] ? `, ${fmt(me.bank[active.item])} more in the bank` : ""} `
- : `No potion. `}
- ${!active && drink ? `Drink: ${drink} ` : ""}
- ${!active && !drink ? `Brewed in Herblore. ` : ""}
-
`;
+ if (kinds.length) {
+ const active = me.potions[skill], it = active && item(active.item);
+ const others = kinds.filter(([id]) => me.bank[id] && id !== active?.item).map(([id, p]) =>
+ `${p.icon}${fmt(me.bank[id])} `).join("");
+ cards.push(`
+
${it ? it.icon : glyph("round-bottom-flask", "#98a1b4")}
+
${it ? `${esc(it.name)} ${esc(it.potion.text)} · ${fmt(active.charges)} uses `
+ : `No potion `}
+ ${others ? `
${others}
` : ""}
`);
+ }
+ if (`tool_${skill}` in me.equip) {
+ const id = me.equip[`tool_${skill}`], it = id && item(id);
+ const others = Object.keys(me.bank).filter((x) => item(x).tool === skill).sort((a, b) => item(b).speed - item(a).speed).map((x) => {
+ const [s, level] = item(x).req || [], why = s && me.levels[s] < level ? `Needs ${skillOf(s).name.toLowerCase()} ${level}` : "";
+ return `${item(x).icon} `;
+ }).join("");
+ cards.push(`
+
${it ? it.icon : skillOf(skill).icon}
+
${it ? `${esc(it.name)} ${Math.round(it.speed * 100)}% faster ` : `No tool `}
+ ${others || it ? `
${others}${it ? `${glyph("cancel", "#98a1b4")} ` : ""}
` : ""}
`);
+ }
+ return cards.length ? `${cards.join("")}
` : "";
}
function prayerPanel() {
@@ -515,14 +527,6 @@ function prayerPanel() {
`;
}
-function toolPanel(skill) {
- const tool = S.me.equip[`tool_${skill}`];
- return `${tool
- ? `${item(tool).icon} ${esc(item(tool).name)} ${Math.round(item(tool).speed * 100)}% faster
- Unequip `
- : `No tool. `}
`;
-}
-
// A damage type's icon: what an enemy attacks with, or (tinted) what it's vulnerable to or resists.
function dmgIcon(type, how = "") {
const t = C.damage_types[type], name = t.name.toLowerCase();
@@ -975,17 +979,22 @@ function happeningsPanel() {
// What's speeding up, slowing down or blocking this skill right now.
function skillHappenings(skill) {
- const h = S.happenings, lines = [];
- const note = (icon, name, text) => lines.push(`${icon} ${esc(name)} ${esc(text)} `);
+ const h = S.happenings, banners = [];
const touches = (src) => ["speed", "yield", "xp"].some((k) => src[k] && skill in src[k]) || (src.block && skill in src.block);
- if (touches(h.weather)) note(h.weather.icon, h.weather.name, h.weather.text);
- for (const e of h.effects) if (touches(e)) note(e.icon, e.name, e.text);
+ const good = (src) => (src.speed?.[skill] ?? 1) < 1 || (src.yield?.[skill] ?? 1) > 1 || (src.xp?.[skill] ?? 1) > 1;
+ const banner = (tone, icon, name, text, button = "") => banners.push(``);
+ if (touches(h.weather)) banner(good(h.weather) ? "good" : "bad", h.weather.icon, h.weather.name, h.weather.text);
+ for (const e of h.effects) if (touches(e)) banner(good(e) ? "good" : "bad", e.icon, e.name, e.text);
for (const p of h.problems) {
if (!C.problems[p.kind] || !touches(C.problems[p.kind])) continue;
const fix = p.action && C.actions.find((x) => x.id === p.action);
- note(p.icon, p.name, !fix ? p.text : fix.skill === skill ? "Clear it below." : `${skillOf(fix.skill).name} clears it (Town square).`);
+ const button = fix ? (fix.skill === skill ? `${esc(fix.name)} `
+ : `${skillOf(fix.skill).icon} ${esc(fix.name)} `)
+ : `Help on the Town square `;
+ banner("bad", p.icon, p.name, p.text, button);
}
- return lines.length ? `${lines.join("")}
` : "";
+ return banners.join("");
}
function buildingCard(key, b) {
diff --git a/static/styles.css b/static/styles.css
index 234b77b..42f9fd2 100644
--- a/static/styles.css
+++ b/static/styles.css
@@ -555,3 +555,19 @@ button.sheet-skill:hover { background: var(--panel-2); }
.doll-slot.starving { border-color: rgba(226, 100, 92, .7); background: rgba(226, 100, 92, .12); }
.doll-slot.starving .gi { opacity: .9; }
@media (max-width: 520px) { .doll-col { justify-items: center; } }
+
+/* A skill's potion and tool, side by side */
+.gear-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 14px; margin-bottom: 14px; }
+.gear-row .panel { margin: 0; }
+.gear-card { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 14px; }
+.gear-card > .doll-slot { cursor: default; flex: none; }
+.gear-text { display: grid; gap: 2px; min-width: 0; flex: 1; }
+.gear-card .swap-grid { margin-left: auto; }
+/* Happenings that touch this skill */
+.happening-banner { margin-bottom: 14px; align-items: center; }
+.happening-banner .banner-ico { font-size: 30px; }
+.happening-banner b { font-size: 15px; }
+.banner.good { border-color: rgba(95, 174, 90, .55); background: rgba(95, 174, 90, .08); }
+.happening-banner { flex-wrap: wrap; }
+.happening-banner .grow { min-width: min(260px, 70%); }
+.happening-banner > .btn { margin-left: auto; }