Spend favour on the city: builders, informants, militia

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-18 19:48:22 +01:00
parent 34dc85284d
commit 67a5db86ff
5 changed files with 142 additions and 3 deletions

View file

@ -469,7 +469,7 @@ function favourPanel() {
return `<div class="panel"><h2>Favour with the council</h2>
<p class="favour">✨ ${fmt(me.favour)} favour · ${fmt(me.donated)} given in total</p>
<div class="btns"><button class="btn" data-perk="bank_slot" ${me.favour < me.perk_costs.bank_slot ? "disabled" : ""}>+1 bank slot (${fmt(me.perk_costs.bank_slot)} favour)</button></div>
<p class="small muted">More perks to come. The council remembers who helped.</p></div>`;
<p class="small muted">Favour can also hire builders, pay informants and rally the militia: see <button class="linkish" data-nav="council">Council &amp; threats</button>.</p></div>`;
}
// --- town -----------------------------------------------------------------
@ -494,7 +494,9 @@ function town() {
if (work) {
const spec = C.buildings[work.building];
const queued = city.queue.map((key) => esc(C.buildings[key].name)).join(", ");
const hire = S.favours.builders;
html += `<div class="banner">🏗️<div class="grow"><b>${work.repair ? "Repairing" : "Building"} ${esc(spec.name)}</b> · done in ${countdown(work.done_at)}
${hire.why ? "" : `<button class="btn sm gold" data-favour="builders" ${S.me.favour < hire.cost ? "disabled" : ""} title="Halve the time left">Hire builders ✨ ${fmt(hire.cost)}</button>`}
<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>`;
}
@ -566,8 +568,9 @@ function council() {
html += `<div class="vs"><div class="side"><span class="muted small">Our defence</span><b>${fmt(city.defence.total)}</b></div><span class="muted">vs</span><div class="side"><span class="muted small">Raiders</span><b>${strength}</b></div></div>`;
}
html += `</div>`;
if (ev.kind === "raid") html += raidPrep(ev);
}
html += favourSpends();
if (ev?.kind === "raid") html += raidPrep(ev);
html += `<div class="section-title">Past events</div>`;
if (!S.history.length) html += `<p class="muted">Nothing has happened yet.</p>`;
for (const h of S.history) {
@ -582,9 +585,28 @@ function council() {
return html;
}
function favourSpends() {
const f = S.favours, me = S.me;
const option = (kind, icon, title, text) => {
const o = f[kind];
const short = !o.why && me.favour < o.cost;
return `<div class="favour-option">
<span class="ico">${icon}</span>
<span class="grow"><b>${title}</b><span class="small muted">${text}</span></span>
<span class="btns"><button class="btn sm gold" data-favour="${kind}" ${o.why || short ? "disabled" : ""}
title="${esc(o.why || (short ? "Not enough favour" : ""))}">✨ ${fmt(o.cost)}</button></span>
</div>`;
};
return `<div class="panel"><h2>Spend favour on the city · you have ✨ ${fmt(me.favour)}</h2>
${option("builders", "🏗️", "Hire extra builders", f.builders.why ? esc(f.builders.why) + "." : `Halves the time left on the current building (saves about ${dur(Math.round(f.builders.saves * 60) * 60)}).`)}
${option("informant", "🕵️", "Pay an informant", f.informant.why ? esc(f.informant.why) + "." : "One more level of intel on what's coming.")}
${option("militia", "📯", "Rally the militia", f.militia.why ? esc(f.militia.why) + "." : `+10 defence for the coming raid${f.militia.rallied ? ` (+${f.militia.rallied} so far)` : ""}. Costs more each time.`)}
</div>`;
}
function raidPrep(ev) {
const city = S.city, me = S.me, d = city.defence;
const names = { townsfolk: "Townsfolk with pitchforks", walls: "Walls", militia: "Militia (barracks)", weapons: "Swords in the armoury", shields: "Shields in the armoury", arrows: "Arrows", garrison: "Garrison" };
const names = { rallied: "Rallied militia (favour)", townsfolk: "Townsfolk with pitchforks", walls: "Walls", militia: "Militia (barracks)", weapons: "Swords in the armoury", shields: "Shields in the armoury", arrows: "Arrows", garrison: "Garrison" };
const reqs = (ev.requests || []).map((r) => {
const mine = me.bank[r.item] || 0;
return `<div class="req"><span>${item(r.item).icon}</span><span>${r.item === "bread" ? "Food (any)" : esc(item(r.item).name)} <span class="muted small">${fmt(r.have)} / ${fmt(r.want)}</span></span>
@ -786,6 +808,7 @@ document.addEventListener("click", (e) => {
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.favour) return act("/api/favour", { kind: d.favour });
if (d.food) return act("/api/food", { item: d.food });
if (d.overflow) return act("/api/settings", { overflow: d.overflow });
if (d.class) { pickedClass = d.class; render(); return; }

View file

@ -366,3 +366,7 @@ button.sheet-skill:hover { background: var(--panel-2); }
.btn.sm { padding: 6px 11px; }
}
.linkish { background: none; border: 0; padding: 0; color: var(--xp); text-decoration: underline; font: inherit; }
.favour-option { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-top: 1px solid var(--line); }
.favour-option:first-of-type { border-top: 0; }
.favour-option .ico { font-size: 24px; width: 30px; text-align: center; }
.favour-option .grow { display: flex; flex-direction: column; }