Classes become trades; building costs draw on every trade

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 22:45:32 +01:00
parent f810e16eb8
commit f4b2bb1320
6 changed files with 305 additions and 137 deletions

View file

@ -299,6 +299,7 @@ function eventWhen(ev, short = false) {
}
function pageHtml() {
if (S.me.repick) return repickPage();
if (page.startsWith("skill:")) {
const id = page.slice(6);
const skill = skillOf(id);
@ -450,7 +451,7 @@ function combatPanel() {
</div>
<div class="panel"><h2>Equipment</h2>
<div class="gear">${slot("weapon", "weapon")}${slot("shield", "shield")}${slot("helmet", "helmet")}${slot("body", "body armour")}${slot("legs", "leg armour")}</div>
<p class="small muted">Power ${me.power} (level + weapon + shield + half your armour) · armour ${me.armour} takes ${Math.round(me.reduction * 100)}% off the damage you take${me.cls === "warrior" ? " · as a warrior you count ×1.5 in the garrison" : ""}. Equip gear from the bank.</p>
<p class="small muted">Power ${me.power} (level + weapon + shield + half your armour) · armour ${me.armour} takes ${Math.round(me.reduction * 100)}% off the damage you take. Equip gear from the bank.</p>
</div></div>`;
}
@ -822,9 +823,9 @@ function council() {
${ev.range ? `<span class="k">Strength</span><span>${ev.strength ?? `${ev.range[0]}–${ev.range[1]}`}</span>` : ""}
${ev.trait ? `<span class="k">Scouts say</span><span>${esc(ev.trait)}</span>` : ""}
${ev.camp ? `<span class="k">Their camp</span><span>Located. Fighters can sabotage it from the combat page${ev.sabotage ? ` (weakened ${ev.sabotage}% so far)` : ""}.</span>` : ""}
${me.cls === "scout" || ev.recon ? `<span class="k">Reconnaissance</span><span>${reconText(ev)}</span>` : ""}
<span class="k">Reconnaissance</span><span>${reconText(ev)}</span>
</div>
${ev.intel < 3 ? `<p class="small muted">Better intel comes from the spymaster's guild and scouts' reconnaissance, and the sharpest only as the threat draws near.</p>` : ""}`;
${ev.intel < 3 ? `<p class="small muted">Better intel comes from the spymaster's guild and anyone's scouting (rangers are best at it), and the sharpest only as the threat draws near.</p>` : ""}`;
if (threat) {
const strength = ev.strength ?? (ev.range ? `${ev.range[0]}–${ev.range[1]}` : "?");
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>`;
@ -863,7 +864,7 @@ function favourSpends() {
};
return `<div class="panel"><h2>Spend favour on the city · you have ${FAVOUR} ${fmt(me.favour)}</h2>
${option("builders", "🏗️", "Hire a master builder", f.builders.why ? esc(f.builders.why) + "." : `The next ${f.builders.labour} labour anyone puts into the site counts double, with double Construction XP${f.builders.boost ? ` (${fmt(f.builders.boost)} still to use)` : ""}. Costs more each time on the same site.`)}
${option("informant", "🕵️", "Pay an informant", f.informant.why ? esc(f.informant.why) + "." : `Leads for the scouts: the next ${f.informant.recon} reconnaissance on the coming threat counts double, with double XP${f.informant.boost ? ` (${fmt(f.informant.boost)} still to use)` : ""}. Costs more each time for the same threat.`)}
${option("informant", "🕵️", "Pay an informant", f.informant.why ? esc(f.informant.why) + "." : `Leads to follow up: the next ${f.informant.recon} reconnaissance on the coming threat counts double, with double XP${f.informant.boost ? ` (${fmt(f.informant.boost)} still to use)` : ""}. Costs more each time for the same threat.`)}
${option("sergeant", "📯", "Hire a drill sergeant", f.sergeant.why ? esc(f.sergeant.why) + "." : `The next ${f.sergeant.drill} drill points fighters put into the militia count double, with double combat XP${f.sergeant.boost ? ` (${fmt(f.sergeant.boost)} still to use)` : ""}. Costs more each time for the same threat.`)}
</div>`;
}
@ -886,7 +887,7 @@ function raidPrep(ev) {
<h2 style="margin-top:16px">Garrison</h2>
<p class="small">${d.garrison.length ? d.garrison.map((g) => `${esc(g.name)} (${g.power})`).join(", ") : `<span class="muted">Nobody has signed up yet.</span>`}</p>
${me.garrison ? `<button class="btn" data-garrison="0">Leave the garrison</button>`
: `<button class="btn primary" data-garrison="1">Stand guard (+${me.cls === "warrior" ? me.power * 1.5 : me.power})</button>`}
: `<button class="btn primary" data-garrison="1">Stand guard (+${me.power})</button>`}
<p class="small muted">You can keep working while you're signed up. Victory earns the garrison favour and combat XP.</p>
</div></div>`;
}
@ -932,7 +933,7 @@ function characterSheet(uid) {
const skills = C.skills.filter((s) => !s.class || s.class === p.cls);
const total = skills.reduce((sum, s) => sum + p.levels[s.id], 0);
const days = Math.max(1, Math.ceil((serverNow() - p.joined) / 86400));
const guard = Math.round(p.power * (p.cls === "warrior" ? 1.5 : 1));
const guard = Math.round(p.power);
const gearSlot = (slot, label, empty) => {
const id = p.equip[slot];
@ -983,6 +984,7 @@ function characterSheet(uid) {
<div class="panel"><h2>Class</h2>
<p><b>${cls.icon} ${cls.name}</b></p>
<p class="small">${esc(cls.bonus)}.</p>
<p class="small">${esc(cls.masterwork)}.</p>
<p class="small">${esc(cls.signature)}.</p>
</div>
<div class="panel"><h2>Equipment</h2>
@ -998,11 +1000,28 @@ function characterSheet(uid) {
// --- registration ---------------------------------------------------------
function register() {
const cards = Object.entries(C.classes).map(([id, c]) => `
function classCards() {
return Object.entries(C.classes).map(([id, c]) => `
<button class="class-card ${pickedClass === id ? "on" : ""}" data-class="${id}">
<span class="ico">${c.icon}</span><span><b>${c.name}</b><p>${esc(c.bonus)}.</p><p>${esc(c.signature)}.</p></span>
<span class="ico">${c.icon}</span><span><b>${c.name}</b><p>${esc(c.bonus)}.</p><p>${esc(c.masterwork)}.</p><p>${esc(c.signature)}.</p></span>
</button>`).join("");
}
// After the classes became trades, everyone picks once more. Nothing is lost.
function repickPage() {
return `<div class="welcome">
<h2>Pick your trade</h2>
<p class="muted">Classes are now whole trades, from gathering through to the finished goods, so you can play
the quiet gathering side and the busier crafting side yourself. You keep every level and item. This choice
is free, once.</p>
<div class="classes">${classCards()}</div>
<div class="btns"><button class="btn primary" data-repick ${pickedClass ? "" : "disabled"}>Take up this trade</button></div>
<p class="small muted">Every class can still train every skill, scout and drill. A trade makes its skills faster and better.</p>
</div>`;
}
function register() {
const cards = classCards();
return `<div class="welcome">
<h2>A new city on the frontier</h2>
<p class="muted">You're one of a handful of adventurers settling a young city at the edge of the kingdom.
@ -1013,7 +1032,7 @@ function register() {
<input type="text" data-keep="name" maxlength="20" placeholder="Your adventurer's name" style="flex:1;min-width:200px">
<button class="btn primary" ${pickedClass ? "" : "disabled"}>Enter the city</button>
</form>
<p class="small muted">Every class can train every skill. Your class makes some things faster and gives you one thing only you can do.</p>
<p class="small muted">Every class can train every skill. Your class is a trade: its skills are faster, its crafts sometimes make double, and it has one bonus of its own.</p>
</div>`;
}
@ -1093,6 +1112,7 @@ document.addEventListener("click", (e) => {
}
if (d.overflow) return act("/api/settings", { overflow: d.overflow });
if (d.class) { pickedClass = d.class; render(); return; }
if ("repick" in d) return act("/api/class", { cls: pickedClass });
if (d.tab) { tabs[d.tab] = d.group; remember(`tab.${d.tab}`, d.group); render(); return; }
if (d.devAdvance) return act("/api/dev/advance", { hours: Number(d.devAdvance) });
if ("devEvent" in d) return act("/api/dev/event", {});