Every crafting skill gets a tool and a station

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-20 21:20:25 +01:00
parent a86594594a
commit 91a5263acf
6 changed files with 90 additions and 29 deletions

View file

@ -523,16 +523,18 @@ function skillGear(skill) {
: `<span class="muted">No potion</span>`}</div>
${others ? `<div class="swap-grid">${others}</div>` : ""}</div>`);
}
if (`tool_${skill}` in me.equip) {
const id = me.equip[`tool_${skill}`], it = id && item(id), noTool = C.stations.includes(skill) ? "No station" : "No tool";
const others = Object.keys(me.bank).filter((x) => item(x).tool === skill).sort((a, b) => item(b).speed - item(a).speed).map((x) => {
for (const [kind, none] of [["tool", "No tool"], ["station", "No station"]]) {
const slot = `${kind}_${skill}`;
if (!(slot in me.equip)) continue;
const id = me.equip[slot], it = id && item(id);
const others = Object.keys(me.bank).filter((x) => item(x)[kind] === 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 `<button class="doll-slot swap" data-equip="${x}" ${why ? "disabled" : ""} title="${esc([`${item(x).name}: ${Math.round(item(x).speed * 100)}% faster`, why].filter(Boolean).join(" · "))}">${item(x).icon}</button>`;
}).join("");
cards.push(`<div class="panel gear-card">
<span class="doll-slot ${it ? "" : "empty"}" title="${esc(it ? it.name : noTool)}">${it ? it.icon : skillOf(skill).icon}</span>
<div class="gear-text">${it ? `<b>${esc(it.name)}</b><span class="muted small">${Math.round(it.speed * 100)}% faster</span>` : `<span class="muted">${noTool}</span>`}</div>
${others || it ? `<div class="swap-grid">${others}${it ? `<button class="doll-slot swap off" data-unequip="tool_${skill}" title="Take it off">${glyph("cancel", "#98a1b4")}</button>` : ""}</div>` : ""}</div>`);
<span class="doll-slot ${it ? "" : "empty"}" title="${esc(it ? it.name : none)}">${it ? it.icon : glyph(kind === "tool" ? "toolbox" : "anvil", "#98a1b4")}</span>
<div class="gear-text">${it ? `<b>${esc(it.name)}</b><span class="muted small">${Math.round(it.speed * 100)}% faster</span>` : `<span class="muted">${none}</span>`}</div>
${others || it ? `<div class="swap-grid">${others}${it ? `<button class="doll-slot swap off" data-unequip="${slot}" title="Take it off">${glyph("cancel", "#98a1b4")}</button>` : ""}</div>` : ""}</div>`);
}
return cards.length ? `<div class="gear-row">${cards.join("")}</div>` : "";
}
@ -744,7 +746,7 @@ function itemStats(id) {
if (it.armour || it.ward) parts.push(gearStats({ ...it, power: 0 }));
if (it.reagent) parts.push(it.reagent === "jewel" ? "Reagent for jewellery" : `Reagent for ${C.weights[it.reagent].name.toLowerCase()} armour`);
if (it.jewel && !["power", "ward"].includes(it.jewel)) parts.push(jewelText(it));
if (it.tool) parts.push(`${Math.round(it.speed * 100)}% faster ${skillOf(it.tool).name.toLowerCase()}`);
if (it.tool || it.station) parts.push(`${Math.round(it.speed * 100)}% faster ${skillOf(it.tool || it.station).name.toLowerCase()}`);
if (it.slots) parts.push(storageText(it));
if (it.stack) parts.push(storageText(it));
if (it.req && it.req[1] > 1) parts.push(`needs ${skillOf(it.req[0]).name.toLowerCase()} ${it.req[1]}`);
@ -927,7 +929,7 @@ function equipRow(id, it) {
}
function equipLabel(it) {
if (it.tool) return `Use for ${skillOf(it.tool).name.toLowerCase()} (${Math.round(it.speed * 100)}% faster)`;
if (it.tool || it.station) return `Use for ${skillOf(it.tool || it.station).name.toLowerCase()} (${Math.round(it.speed * 100)}% faster)`;
return "Equip";
}
@ -1432,9 +1434,9 @@ function characterSheet(uid) {
const level = p.levels[s.id], xp = p.xp[s.id] || 0;
const floor = C.xp_table[level], ceil = C.xp_table[level + 1];
const pct = ceil ? (xp - floor) / (ceil - floor) * 100 : 100;
const tool = p.equip[`tool_${s.id}`];
const gear = [p.equip[`tool_${s.id}`], p.equip[`station_${s.id}`]].filter(Boolean);
const marks = (cls.fast.includes(s.id) ? `<span title="${cls.name}: ${Math.round((1 - C.class_speed) * 100)}% faster">${cls.icon}</span>` : "")
+ (tool ? `<span title="${esc(`${item(tool).name}: ${Math.round(item(tool).speed * 100)}% faster`)}">${item(tool).icon}</span>` : "");
+ gear.map((id) => `<span title="${esc(`${item(id).name}: ${Math.round(item(id).speed * 100)}% faster`)}">${item(id).icon}</span>`).join("");
return `<${mine ? `button data-nav="skill:${s.id}"` : "div"} class="sheet-skill">
<span class="ico">${s.icon}</span>
<span class="grow"><span class="row1"><b>${s.name}</b><span class="marks">${marks}</span><span class="lv">${level}</span></span>
@ -1586,12 +1588,17 @@ function equipmentPanel(p, mine, tools = true) {
}
// Tools: one small slot per gathering skill.
// Gathering tools in a row; under them each crafting skill's tool, with its station below it.
function toolSlots(p) {
return `<div class="tool-slots">${C.skills.filter((s) => `tool_${s.id}` in p.equip).map((s) => {
const id = p.equip[`tool_${s.id}`], it = id && item(id);
return `<span class="doll-slot small ${id ? "" : "empty"}" title="${esc(it ? `${it.name}: ${Math.round(it.speed * 100)}% faster ${s.name.toLowerCase()}` : `${s.name}: no tool`)}">
const slot = (kind, s) => {
const id = p.equip[`${kind}_${s.id}`], it = id && item(id);
return `<span class="doll-slot small ${id ? "" : "empty"}" title="${esc(it ? `${it.name}: ${Math.round(it.speed * 100)}% faster ${s.name.toLowerCase()}` : `${s.name}: no ${kind}`)}">
${it ? it.icon : s.icon}</span>`;
}).join("")}</div>`;
};
const gathering = C.skills.filter((s) => `tool_${s.id}` in p.equip && !(`station_${s.id}` in p.equip));
const crafting = C.skills.filter((s) => `station_${s.id}` in p.equip);
return `<div class="tool-slots">${gathering.map((s) => slot("tool", s)).join("")}</div>
<div class="tool-grid">${crafting.map((s) => slot("tool", s) + slot("station", s)).join("")}</div>`;
}
// --- registration ---------------------------------------------------------