City needs in profession menus
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2579be2475
commit
472609ac70
2 changed files with 62 additions and 3 deletions
|
|
@ -478,6 +478,56 @@ function costChip(icon, name, need, have) {
|
|||
return `<span class="item-chip ${have < need ? "short" : ""}" title="${esc(`${name}: ${have < need ? `${fmt(need - have)} more needed` : "done"}`)}">${icon} ${fmt(have)} <span class="have">/ ${fmt(need)}</span></span>`;
|
||||
}
|
||||
|
||||
// What the city still needs of each item from building funds, fittings and happenings, after the stockpile.
|
||||
// Recomputed once per state.
|
||||
let needsState = null, needsCache = {};
|
||||
function fundNeeds() {
|
||||
if (needsState === S) return needsCache;
|
||||
const city = S.city, missing = {}, why = {};
|
||||
const add = (id, short, line) => { missing[id] = (missing[id] || 0) + short; (why[id] ||= []).push(line); };
|
||||
for (const [key, b] of Object.entries(city.buildings)) {
|
||||
const spec = C.buildings[key];
|
||||
if (b.status === "funding") for (const [id, need] of Object.entries(b.target)) {
|
||||
const short = b.missing[id] || 0;
|
||||
if (id !== "gold" && short) add(id, short, `${spec.name} · ${fmt(need - short)} / ${fmt(need)}`);
|
||||
}
|
||||
for (const f of C.fittings) {
|
||||
if (f.building === key && b.level >= f.level && !b.fittings.includes(f.id)) add(f.id, 1, `${spec.name} · 0 / 1`);
|
||||
}
|
||||
}
|
||||
for (const p of S.happenings.problems) for (const [id, need] of Object.entries(p.need || {})) {
|
||||
const short = p.missing[id] || 0;
|
||||
if (short) add(id, short, `${p.name} · ${fmt(need - short)} / ${fmt(need)}`);
|
||||
}
|
||||
needsCache = {};
|
||||
for (const [id, short] of Object.entries(missing)) {
|
||||
const stock = city.stockpile[id] || 0;
|
||||
if (short > stock) needsCache[id] = { qty: short - stock, why: stock ? [...why[id], `Stockpile ${fmt(stock)}`] : why[id] };
|
||||
}
|
||||
needsState = S;
|
||||
return needsCache;
|
||||
}
|
||||
|
||||
// How many of an item the city still needs, with a have / need line per reason, or null.
|
||||
function cityNeed(id) {
|
||||
const it = item(id), fund = fundNeeds()[id];
|
||||
let qty = fund?.qty || 0;
|
||||
const why = [...(fund?.why || [])];
|
||||
for (const r of S.upcoming?.requests || []) {
|
||||
const short = r.want - r.have;
|
||||
const counts = r.any === "food" ? it.food : r.any ? it.slot === r.any : id === r.item;
|
||||
if (short <= 0 || !counts) continue;
|
||||
qty += r.any === "food" ? Math.ceil(short / it.food) : short;
|
||||
why.push(`Council: ${r.label} · ${fmt(r.have)} / ${fmt(r.want)}`);
|
||||
}
|
||||
return qty ? { qty, why } : null;
|
||||
}
|
||||
|
||||
function needTag(id) {
|
||||
const n = cityNeed(id);
|
||||
return n ? `<span class="tag gold" title="${esc(`City needs ${fmt(n.qty)} ${item(id).name}\n${n.why.join("\n")}`)}">🏘️ ${fmt(n.qty)}</span>` : "";
|
||||
}
|
||||
|
||||
// --- skill pages ----------------------------------------------------------
|
||||
|
||||
function skillPage(id) {
|
||||
|
|
@ -523,7 +573,8 @@ function skillPage(id) {
|
|||
// Tier ladders open on the highest tier you can work; categories open on the first.
|
||||
const ladder = ["smithing", "leatherworking", "tailoring", "herblore", "combat"].includes(id);
|
||||
if (!groups.includes(current)) current = (ladder && [...groups].reverse().find(open)) || groups[0];
|
||||
const bar = groups.map((g) => `<button class="tab ${g === current ? "on" : ""} ${open(g) ? "" : "dim"}" data-tab="${id}" data-group="${esc(g)}">${esc(g)}</button>`).join("");
|
||||
const needed = (g) => actions.some((a) => a.group === g && !isLocked(a) && Object.keys(a.outputs).some(cityNeed));
|
||||
const bar = groups.map((g) => `<button class="tab ${g === current ? "on" : ""} ${open(g) ? "" : "dim"}" data-tab="${id}" data-group="${esc(g)}">${esc(g)}${needed(g) ? ` <span title="City needs">🏘️</span>` : ""}</button>`).join("");
|
||||
// Ungrouped actions (the building site) stay above the tabs.
|
||||
const loose = actions.filter((a) => !a.group);
|
||||
if (loose.length) html += `<div class="grid" style="margin-bottom:14px">${loose.map(actionCard).join("")}</div>`;
|
||||
|
|
@ -1077,7 +1128,7 @@ function actionCard(a) {
|
|||
return `
|
||||
<div class="action ${active || second ? "active" : ""} ${locked ? "locked" : ""}">
|
||||
<div class="top"><div class="ico">${icon}</div><div>
|
||||
<div class="name">${esc(a.name)}</div>
|
||||
<div class="name">${esc(a.name)} ${Object.keys(a.outputs).map(needTag).join("")}</div>
|
||||
<div class="sub">Level ${a.level} · ${dur(time)} · ${a.xp} XP</div>
|
||||
</div>${Object.keys(a.inputs).length ? `<button class="pin ${me.tracked === a.id ? "on" : ""}" data-track="${a.id}" title="${me.tracked === a.id ? "Untrack" : "Track"}">📌</button>` : ""}</div>
|
||||
${io ? `<div class="io">${io}</div>` : ""}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue