Guard kit: halberds, pavises, gambesons, salves; Raid readiness

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-28 10:18:15 +00:00
parent 3d93fabbfd
commit 002e990dc4
4 changed files with 175 additions and 46 deletions

View file

@ -246,7 +246,7 @@ function replayBattle(id) {
// strong as its share of the total. Theirs: the raiders' strength split into bands, or the one dragon.
// `every` is how often each swings, in seconds of the replay; walls and the ram only take blows.
const SQUADS = [
{ id: "guards", parts: ["militia", "weapons", "shields", "jerkins"], name: "Guards", glyph: "swordman", dmg: "slash", every: 2 },
{ id: "guards", parts: ["militia", "weapons", "shields", "jerkins", "salves"], name: "Guards", glyph: "swordman", dmg: "slash", every: 2 },
{ id: "archers", parts: ["arrows"], name: "Archers", glyph: "bow-arrow", dmg: "pierce", every: 1.8 },
{ id: "walls", parts: ["walls"], name: "Walls", glyph: "stone-wall", dmg: "blunt", still: true },
{ id: "folk", parts: ["townsfolk", "drilled", "rallied"], name: "Townsfolk", glyph: "farmer", dmg: "blunt", every: 2.8 },
@ -936,10 +936,10 @@ function cityNeed(id) {
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;
const counts = r.any === "food" ? it.food : r.any ? (it.slot || it.kit) === 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)}`);
why.push(`Raid readiness: ${r.label} · ${fmt(r.have)} / ${fmt(r.want)}`);
}
return qty ? { qty, why } : null;
}
@ -1716,7 +1716,10 @@ function itemStats(id) {
const st = C.styles[it.style], trade = Object.entries(C.class_style).find(([, s]) => s === it.style)?.[0];
parts.push(`${it.two_handed ? "two-handed " : ""}${st.name}, ${st.interval}s${trade ? ` (${C.classes[trade].name.toLowerCase()})` : ""}`);
}
if (it.kit) parts.push(`+${Math.round(it.armour * C.jerkin_defence * 10) / 10} city defence per paid guard`);
if (it.kit) {
const [stat, rate] = C.kit_defence[it.kit];
parts.push(`+${Math.round(it[stat] * rate * 10) / 10} city defence per paid guard${it.kit === "salve" ? `, used up · max ${C.salve_cap}` : ""}`);
}
else 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));
@ -2284,7 +2287,7 @@ function favourSpends() {
function raidPrep(ev) {
const city = S.city, me = S.me, d = city.defence;
const names = { mercenaries: "Hired sellswords", ward: "Divine ward (temple)", rallied: "Rallied militia (favour)", drilled: "Drilled militia", townsfolk: "Townsfolk with pitchforks", walls: "Walls", militia: "Paid guards (barracks)", weapons: "Swords the guards hold", shields: "Shields the guards hold", jerkins: "Jerkins the guards wear", arrows: "Arrows", garrison: "Garrison" };
const names = { mercenaries: "Hired sellswords", ward: "Divine ward (temple)", rallied: "Rallied militia (favour)", drilled: "Drilled militia", townsfolk: "Townsfolk with pitchforks", walls: "Walls", militia: "Paid guards (barracks)", weapons: "Weapons the guards hold", shields: "Shields the guards hold", jerkins: "Body armour the guards wear", salves: "Salves", arrows: "Arrows", garrison: "Garrison" };
const full = d.posts * d.wage, owed = Math.max(0, full - d.wages);
const reqs = (ev.requests || []).map((r) => {
const mine = me.bank[r.item] || 0;
@ -2293,7 +2296,7 @@ function raidPrep(ev) {
<div class="bar"><span style="width:${Math.min(100, r.have / r.want * 100)}%"></span></div></div>`;
}).join("");
return `<div class="cols">
<div class="panel"><h2>Council requests</h2>${reqs}
<div class="panel"><h2>Raid readiness</h2>${reqs}
<p class="small muted">Stockpile gear arms the defenders, best first. Fights use up arrows and break some gear.</p></div>
<div class="panel"><h2>Guards · ${d.guards} / ${d.posts} paid</h2>
<p class="small">Wage chest ${COIN} ${fmt(d.wages)} / ${fmt(full)} · ${d.wage} gold a guard per threat</p>
@ -2301,7 +2304,7 @@ function raidPrep(ev) {
<div class="btns" style="margin-top:8px">
<button class="btn sm gold" data-wages="bank" ${!owed || !me.gold ? "disabled" : ""}>Pay ${COIN} ${fmt(Math.min(owed, me.gold))}</button>
<button class="btn sm" data-wages="city" ${!owed || city.treasury < 1 ? "disabled" : ""}>From the treasury (${fmt(city.treasury)})</button></div>
<p class="small muted">Only paid guards stand, and only they get the armoury's swords, shields and jerkins. Paying earns favour.</p>
<p class="small muted">Only paid guards stand, and only they get the armoury's kit. Paying earns favour.</p>
<h2 style="margin-top:16px">Defence</h2>
<table class="parts">${Object.entries(d.parts).filter(([k, v]) => k !== "rallied" || v).map(([k, v]) => `<tr><td>${names[k]}</td><td>${fmt(v)}</td></tr>`).join("")}
${d.starving ? `<tr><td class="bad">Hungry (×0.8)</td><td></td></tr>` : ""}</table>