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
10
game.py
10
game.py
|
|
@ -3691,7 +3691,12 @@ def council_requests(world: dict, event: dict) -> list[dict]:
|
||||||
have = sum(stock.get(food, 0) * ITEMS[food]["food"] for food in FOOD_ORDER)
|
have = sum(stock.get(food, 0) * ITEMS[food]["food"] for food in FOOD_ORDER)
|
||||||
label = {"copper_sword": "Swords (any)", "iron_shield": "Shields (any)", "bread": "Food (any)"}[item] \
|
label = {"copper_sword": "Swords (any)", "iron_shield": "Shields (any)", "bread": "Food (any)"}[item] \
|
||||||
if item in ("copper_sword", "iron_shield", "bread") else ITEMS[item]["name"]
|
if item in ("copper_sword", "iron_shield", "bread") else ITEMS[item]["name"]
|
||||||
requests.append({"item": item, "label": label, "want": qty, "have": have})
|
request = {"item": item, "label": label, "want": qty, "have": have}
|
||||||
|
if item in ("copper_sword", "iron_shield"):
|
||||||
|
request["any"] = ITEMS[item]["slot"] # any item in this slot counts
|
||||||
|
if item == "bread":
|
||||||
|
request["any"] = "food" # counted in meals
|
||||||
|
requests.append(request)
|
||||||
return requests
|
return requests
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4747,6 +4752,9 @@ ORDER_RECIPES, ORDER_RAW = _order_sources()
|
||||||
# What changed, shown on the Patch notes page. Newest first; every shipped change adds a
|
# What changed, shown on the Patch notes page. Newest first; every shipped change adds a
|
||||||
# line here (new / balance / fix), and a new release a new entry on top.
|
# line here (new / balance / fix), and a new release a new entry on top.
|
||||||
PATCH_NOTES = [
|
PATCH_NOTES = [
|
||||||
|
{"date": "2026-10-23", "title": "City needs", "notes": [
|
||||||
|
("new", "City needs are now indicated in profession menus."),
|
||||||
|
]},
|
||||||
{"date": "2026-10-22", "title": "Bosses", "notes": [
|
{"date": "2026-10-22", "title": "Bosses", "notes": [
|
||||||
("new", "Bosses and dragons: everyone in the fight gets their own loot."),
|
("new", "Bosses and dragons: everyone in the fight gets their own loot."),
|
||||||
("balance", "Level requirements for area boss reduced to be area level +5."),
|
("balance", "Level requirements for area boss reduced to be area level +5."),
|
||||||
|
|
|
||||||
|
|
@ -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>`;
|
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 ----------------------------------------------------------
|
// --- skill pages ----------------------------------------------------------
|
||||||
|
|
||||||
function skillPage(id) {
|
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.
|
// Tier ladders open on the highest tier you can work; categories open on the first.
|
||||||
const ladder = ["smithing", "leatherworking", "tailoring", "herblore", "combat"].includes(id);
|
const ladder = ["smithing", "leatherworking", "tailoring", "herblore", "combat"].includes(id);
|
||||||
if (!groups.includes(current)) current = (ladder && [...groups].reverse().find(open)) || groups[0];
|
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.
|
// Ungrouped actions (the building site) stay above the tabs.
|
||||||
const loose = actions.filter((a) => !a.group);
|
const loose = actions.filter((a) => !a.group);
|
||||||
if (loose.length) html += `<div class="grid" style="margin-bottom:14px">${loose.map(actionCard).join("")}</div>`;
|
if (loose.length) html += `<div class="grid" style="margin-bottom:14px">${loose.map(actionCard).join("")}</div>`;
|
||||||
|
|
@ -1077,7 +1128,7 @@ function actionCard(a) {
|
||||||
return `
|
return `
|
||||||
<div class="action ${active || second ? "active" : ""} ${locked ? "locked" : ""}">
|
<div class="action ${active || second ? "active" : ""} ${locked ? "locked" : ""}">
|
||||||
<div class="top"><div class="ico">${icon}</div><div>
|
<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 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>
|
</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>` : ""}
|
${io ? `<div class="io">${io}</div>` : ""}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue