Building funds safe from raids; settlement tiers; population and food on the town square

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-18 19:20:52 +01:00
parent e3b70c1e3a
commit 3716df7493
6 changed files with 358 additions and 91 deletions

View file

@ -425,46 +425,68 @@ function favourPanel() {
function town() {
const city = S.city;
const growth = city.starving ? `<span class="bad">Hungry: ${city.growth} a day</span>`
: city.growth > 0 ? `+${city.growth} a day`
: city.growth < 0 ? `<span class="bad">Overcrowded: ${city.growth} a day</span>`
: `<span class="warn">Full. Build more houses to grow</span>`;
const next = city.next_tier ? `${city.next_tier.name} at ${city.next_tier.pop} people` : "The largest a settlement can be";
let html = `<div class="stats">
<div class="stat"><div class="k">Day</div><div class="v">${city.day}</div></div>
<div class="stat"><div class="k">Population</div><div class="v">${city.population} <small>/ ${city.housing} homes</small></div></div>
<div class="stat"><div class="k">Treasury</div><div class="v gold">${fmt(city.treasury)}</div></div>
<div class="stat"><div class="k">Defence</div><div class="v">${fmt(city.defence.total)}</div></div>
<div class="stat"><div class="k">Raids repelled</div><div class="v">${city.repelled}</div></div>
<div class="stat"><div class="k">Settlement</div><div class="v">${city.tier_name}</div><div class="note">${next}</div></div>
<div class="stat"><div class="k">Population</div><div class="v">${city.population} <small>/ ${city.housing} homes</small></div><div class="note">${growth}</div></div>
<div class="stat"><div class="k">Food</div><div class="v ${city.food_days < 1 ? "bad" : ""}">${city.food_days ?? 0} <small>days</small></div><div class="note">${fmt(city.food)} meals in the granary</div></div>
<div class="stat"><div class="k">Treasury</div><div class="v gold">${fmt(city.treasury)}</div><div class="note">Day ${city.day}</div></div>
<div class="stat"><div class="k">Defence</div><div class="v">${fmt(city.defence.total)}</div><div class="note">${city.repelled} raid${city.repelled === 1 ? "" : "s"} repelled</div></div>
</div>`;
if (city.starving) html += `<div class="banner bad">🍞<div class="grow"><b>The granary is empty.</b> People are leaving and the city defends worse. Donate bread, cooked meat or stew.</div></div>`;
if (city.merchant_until > serverNow()) html += `<div class="banner">🐪<div class="grow"><b>A merchant caravan is in town.</b> Everything sells for 50% more for another ${countdown(city.merchant_until)}.</div></div>`;
const work = city.construction;
if (work) {
const spec = C.buildings[work.building];
const queued = city.queue.map((key) => esc(C.buildings[key].name)).join(", ");
html += `<div class="banner">🏗️<div class="grow"><b>${work.repair ? "Repairing" : "Building"} ${esc(spec.name)}</b> · done in ${countdown(work.done_at)}
<div class="bar gold" style="margin-top:6px"><span data-span="${work.start},${work.done_at}"></span></div></div></div>`;
<div class="bar gold" style="margin-top:6px"><span data-span="${work.start},${work.done_at}"></span></div>
${queued ? `<div class="small muted" style="margin-top:6px">Next: ${queued}</div>` : ""}</div></div>`;
}
html += `<div class="section-title">Buildings</div><div class="grid wide">`;
for (const [key, b] of Object.entries(city.buildings)) {
const spec = C.buildings[key];
const cost = b.cost ? Object.entries(b.cost).map(([id, q]) => id === "gold"
? costChip("🪙", "Gold", q, city.treasury)
: costChip(item(id).icon, item(id).name, q, city.stockpile[id] || 0)).join("") : "";
const label = b.damaged ? "Repair" : `Build level ${b.level + 1}`;
html += `<div class="action building ${b.damaged ? "damaged" : ""}">
<div class="top"><div class="ico">${spec.icon}</div><div>
<div class="name">${esc(spec.name)} <span class="lv muted">${b.level} / ${spec.max}</span> ${b.damaged ? `<span class="tag bad">Damaged</span>` : ""}</div>
<div class="sub">${esc(spec.effect)}</div>
</div></div>
${b.cost ? `<div class="cost">${cost}</div>
<div class="foot"><span class="why">${work ? "Builders busy" : ""}</span>
<button class="btn ${b.damaged ? "danger" : "primary"}" data-build="${key}" ${b.missing.length || work ? "disabled" : ""}>${label}</button></div>`
: `<div class="tag good">Fully built</div>`}
</div>`;
}
html += `</div><div class="section-title">City stockpile</div><div class="panel">`;
html += `<div class="section-title">Buildings</div>
<p class="small muted">Each building collects its own fund. Materials in a fund are safe from raiders, and building starts on its own once the fund is complete.</p>
<div class="grid wide">${Object.entries(city.buildings).map(([key, b]) => buildingCard(key, b)).join("")}</div>`;
html += `<div class="section-title">City stockpile</div><div class="panel">`;
const stock = Object.entries(city.stockpile);
html += stock.length ? `<div class="stock">${stock.map(([id, q]) => itemChip(id, q)).join("")}</div>` : `<p class="muted">Empty. Donate from your bank.</p>`;
html += `<p class="small muted">Anyone can start a building once the stockpile and treasury cover it. Until then, the stockpile is what raiders come for.</p></div>`;
html += stock.length ? `<div class="stock">${stock.map(([id, q]) => itemChip(id, q)).join("")}</div>` : `<p class="muted">Empty.</p>`;
html += `<p class="small muted">Food, weapons and anything not yet in a fund. The granary and armoury draw from here, and so do raiders.</p></div>`;
return html;
}
function buildingCard(key, b) {
const spec = C.buildings[key], city = S.city, me = S.me;
const tags = {
building: `<span class="tag gold">Under construction</span>`,
queued: `<span class="tag good">Funded · waiting for builders</span>`,
max: `<span class="tag good">Fully built</span>`,
capped: `<span class="tag">Next level when the settlement grows</span>`,
};
let body = tags[b.status] ? `<div>${tags[b.status]}</div>` : "";
if (b.status === "funding" || b.status === "queued") {
body += `<div class="fund">${Object.entries(b.target).map(([id, need]) => {
const missing = b.missing[id] || 0;
const gold = id === "gold";
const mine = Math.min(missing, gold ? me.gold : me.bank[id] || 0);
const stores = Math.min(missing, gold ? city.treasury : city.stockpile[id] || 0);
const chip = costChip(gold ? "🪙" : item(id).icon, gold ? "Gold" : item(id).name, need, need - missing);
return `<div class="fund-row">${chip}<span class="btns">
${mine ? `<button class="btn sm gold" data-fund="${key}" data-item="${id}" data-source="bank" title="From your ${gold ? "purse" : "bank"}, earns favour">Give ${fmt(mine)}</button>` : ""}
${stores ? `<button class="btn sm" data-fund="${key}" data-item="${id}" data-source="city" title="From the city's ${gold ? "treasury" : "stockpile"}">${gold ? "Treasury" : "Stockpile"} ${fmt(stores)}</button>` : ""}
</span></div>`;
}).join("")}</div>`;
}
const levelText = b.cap < spec.max ? `Lv ${b.level} · max ${b.cap} for a ${city.tier_name.toLowerCase()}` : `Lv ${b.level} / ${spec.max}`;
return `<div class="action building ${b.damaged ? "damaged" : ""}">
<div class="top"><div class="ico">${spec.icon}</div><div>
<div class="name">${esc(spec.name)} <span class="lv muted">${levelText}</span> ${b.damaged ? `<span class="tag bad">Damaged: repairs needed</span>` : ""}</div>
<div class="sub">${esc(spec.effect)}</div>
</div></div>${body}</div>`;
}
// --- council --------------------------------------------------------------
function pips(level) {
@ -633,7 +655,7 @@ document.addEventListener("click", (e) => {
if (d.give) return act("/api/give", { item: d.give, qty, to: $("[data-keep=give-to]").value });
if (d.equip) return act("/api/equip", { item: d.equip });
if (d.unequip) return act("/api/equip", { slot: d.unequip });
if (d.build) return act("/api/build", { building: d.build });
if (d.fund) return act("/api/fund", { building: d.fund, item: d.item, source: d.source, qty: "all" });
if (d.garrison) return act("/api/garrison", { join: d.garrison === "1" });
if (d.perk) return act("/api/perk", { perk: d.perk });
if (d.overflow) return act("/api/settings", { overflow: d.overflow });