Boost bars for the informant and drill sergeant too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-20 12:14:26 +01:00
parent 76778a660a
commit 35491c6f4d
2 changed files with 21 additions and 17 deletions

View file

@ -617,18 +617,19 @@ function actionCard(a) {
if (a.special === "drill") { if (a.special === "drill") {
const s = S.favours.sergeant, boost = s.boost; const s = S.favours.sergeant, boost = s.boost;
extra = !s.needed ? `<div class="small muted">Only while a raid is coming.</div>` extra = !s.needed ? `<div class="small muted">Only while a raid is coming.</div>`
: `<div class="small makes">${skillOf("combat").icon} +${+me.drill_per_action.toFixed(2)} drill · ${fmt(s.points)} / ${fmt(s.needed)} points, worth ${fmt(s.defence)} of ${fmt(s.cap)} defence${boost ? ` <b class="boosted">×2 drill and XP for the next ${fmt(boost)}</b>` : ""}</div>`; : `<div class="small makes">${skillOf("combat").icon} +${+me.drill_per_action.toFixed(2)} drill · ${fmt(s.points)} / ${fmt(s.needed)} points, worth ${fmt(s.defence)} of ${fmt(s.cap)} defence</div>
${boostBar(s.points, s.needed, boost, "Drill sergeant: this stretch drills twice as fast")}`;
} }
if (a.special === "recon") { if (a.special === "recon") {
const boost = S.favours.informant.boost; const boost = S.favours.informant.boost;
const ev = S.upcoming, full = ev && ev.recon >= ev.recon_full; const ev = S.upcoming, full = ev && ev.recon >= ev.recon_full;
extra = full ? `<div class="small muted">Fully scouted: XP only.</div>` extra = full ? `<div class="small muted">Fully scouted: XP only.</div>`
: `<div class="small makes">${skillOf("reconnaissance").icon} +${+me.recon_gain[a.id].toFixed(2)} recon${boost ? ` <b class="boosted">×2 recon and XP for the next ${fmt(boost)}</b>` : ""}</div>`; : `<div class="small makes">${skillOf("reconnaissance").icon} +${+me.recon_gain[a.id].toFixed(2)} recon</div>
${ev?.recon_full ? boostBar(ev.recon, ev.recon_full, boost, "Informant: this stretch scouts twice as fast") : ""}`;
} }
const temple = S.city.buildings.temple?.level || 0; const temple = S.city.buildings.temple?.level || 0;
if (a.special === "labour") { if (a.special === "labour") {
const boost = S.favours.builders.boost; extra = `<div class="small makes">${skillOf("construction").icon} +${me.labour_per_action} labour per action</div>`;
extra = `<div class="small makes">${skillOf("construction").icon} +${me.labour_per_action} labour per action${boost ? ` <b class="boosted">×2 labour and XP for the next ${fmt(boost)}</b>` : ""}</div>`;
} }
if (a.special === "bury") extra = `<div class="small makes">${skillOf("prayer").icon} +${a.pp + temple} prayer points</div>`; if (a.special === "bury") extra = `<div class="small makes">${skillOf("prayer").icon} +${a.pp + temple} prayer points</div>`;
if (a.special === "offer") extra = `<div class="small makes">${C.buildings.temple.icon} +${+(a.devotion * (1 + 0.25 * temple)).toFixed(1)} devotion</div>`; if (a.special === "offer") extra = `<div class="small makes">${C.buildings.temple.icon} +${+(a.devotion * (1 + 0.25 * temple)).toFixed(1)} devotion</div>`;
@ -782,6 +783,14 @@ function town() {
return html; return html;
} }
// Progress towards a goal favour can boost. Favour doubles the next `boost` points players put
// in, so the striped stretch behind the bar (twice the boost) is where work counts double.
function boostBar(done, total, boost, tip) {
const pct = (x) => Math.min(100, x / total * 100);
return `<div class="bar gold thick boost-bar"${boost ? ` title="${esc(tip)}"` : ""}>${boost
? `<span class="boost" style="width:${pct(done + 2 * boost)}%"></span>` : ""}<span style="width:${pct(done)}%"></span></div>`;
}
// The building site: labour so far, who is helping, and ways to speed it up. // The building site: labour so far, who is helping, and ways to speed it up.
function siteBanner() { function siteBanner() {
const city = S.city, work = city.construction, me = S.me; const city = S.city, work = city.construction, me = S.me;
@ -790,9 +799,6 @@ function siteBanner() {
const hire = S.favours.builders; const hire = S.favours.builders;
const working = me.action?.id === "labour"; const working = me.action?.id === "labour";
const builders = S.players.filter((p) => p.skill === "construction" && p.doing === "Work on the building site").map((p) => esc(p.name)); const builders = S.players.filter((p) => p.skill === "construction" && p.doing === "Work on the building site").map((p) => esc(p.name));
// A master builder doubles the next `boost` labour players put in: that stretch of the bar fills twice as fast.
const done = Math.min(100, work.labour / work.needed * 100);
const boosted = Math.min(100, (work.labour + 2 * hire.boost) / work.needed * 100);
return `<div class="banner"><span class="banner-ico">${skillOf("construction").icon}</span><div class="grow"> return `<div class="banner"><span class="banner-ico">${skillOf("construction").icon}</span><div class="grow">
<div class="site-head"><b>${work.repair ? "Repairing" : "Building"} ${esc(spec.name)}</b> <div class="site-head"><b>${work.repair ? "Repairing" : "Building"} ${esc(spec.name)}</b>
<span class="muted small">${fmt(work.labour)} / ${fmt(work.needed)} labour</span> <span class="muted small">${fmt(work.labour)} / ${fmt(work.needed)} labour</span>
@ -800,8 +806,7 @@ function siteBanner() {
${working ? `<span class="btn sm working">Working</span>` : `<button class="btn sm primary" data-start="labour">Work on it</button>`} ${working ? `<span class="btn sm working">Working</span>` : `<button class="btn sm primary" data-start="labour">Work on it</button>`}
${hire.why ? "" : `<button class="btn sm gold" data-favour="builders" ${me.favour < hire.cost ? "disabled" : ""} title="×2 labour and XP for the next ${hire.labour}">Master builder ${FAVOUR} ${fmt(hire.cost)}</button>`} ${hire.why ? "" : `<button class="btn sm gold" data-favour="builders" ${me.favour < hire.cost ? "disabled" : ""} title="×2 labour and XP for the next ${hire.labour}">Master builder ${FAVOUR} ${fmt(hire.cost)}</button>`}
</span></div> </span></div>
<div class="bar gold thick site-bar" style="margin-top:6px"${hire.boost ? ` title="Master builder: this stretch builds twice as fast"` : ""}> ${boostBar(work.labour, work.needed, hire.boost, "Master builder: this stretch builds twice as fast")}
${hire.boost ? `<span class="boost" style="width:${boosted}%"></span>` : ""}<span style="width:${done}%"></span></div>
<div class="small muted" style="margin-top:6px">${builders.length ? `On site: ${builders.join(", ")}. ` : ""}Townsfolk +${fmt(city.townsfolk_labour)}/h.${queued ? ` Next: ${queued}.` : ""}</div> <div class="small muted" style="margin-top:6px">${builders.length ? `On site: ${builders.join(", ")}. ` : ""}Townsfolk +${fmt(city.townsfolk_labour)}/h.${queued ? ` Next: ${queued}.` : ""}</div>
</div></div>`; </div></div>`;
} }
@ -929,9 +934,8 @@ function pips(level) {
function reconText(ev) { function reconText(ev) {
const points = `${fmt(ev.recon)} / ${fmt(ev.recon_full)} points`; const points = `${fmt(ev.recon)} / ${fmt(ev.recon_full)} points`;
if (ev.intel >= 3) return `${points} · complete`; if (ev.intel >= 3) return `${points} · complete`;
const boost = S.favours.informant.boost ? ` · <b class="boosted">informant: ×2 for the next ${fmt(S.favours.informant.boost)}</b>` : ""; if (ev.recon_next <= 0) return `${points} · next level opens closer to the threat`;
if (ev.recon_next <= 0) return `${points} · next level opens closer to the threat${boost}`; return `${points} · ${fmt(ev.recon_next)} to the next level`;
return `${points} · ${fmt(ev.recon_next)} to the next level${boost}`;
} }
function council() { function council() {
@ -950,7 +954,8 @@ function council() {
${ev.trait ? `<span class="k">Scouts say</span><span>${esc(ev.trait)}</span>` : ""} ${ev.trait ? `<span class="k">Scouts say</span><span>${esc(ev.trait)}</span>` : ""}
${ev.camp ? `<span class="k">Their camp</span><span>Found · sabotage it in Combat${ev.sabotage ? ` (-${ev.sabotage}% so far)` : ""}</span>` : ""} ${ev.camp ? `<span class="k">Their camp</span><span>Found · sabotage it in Combat${ev.sabotage ? ` (-${ev.sabotage}% so far)` : ""}</span>` : ""}
<span class="k">Reconnaissance</span><span>${reconText(ev)}</span> <span class="k">Reconnaissance</span><span>${reconText(ev)}</span>
</div>`; </div>
${ev.recon_full && ev.intel < 3 ? boostBar(ev.recon, ev.recon_full, S.favours.informant.boost, "Informant: this stretch scouts twice as fast") : ""}`;
if (threat) { if (threat) {
const strength = ev.strength ?? (ev.range ? `${ev.range[0]}–${ev.range[1]}` : "?"); 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>`; 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>`;

View file

@ -142,9 +142,9 @@ main { padding: 24px; max-width: 1200px; width: 100%; }
.bar.xp > span { background: var(--xp); } .bar.xp > span { background: var(--xp); }
.bar.gold > span { background: var(--gold); } .bar.gold > span { background: var(--gold); }
.bar.thick { height: 12px; } .bar.thick { height: 12px; }
.site-bar { position: relative; } .boost-bar { position: relative; margin-top: 8px; }
.site-bar > span { position: relative; z-index: 1; } .boost-bar > span { position: relative; z-index: 1; }
.site-bar > span.boost { position: absolute; left: 0; top: 0; z-index: 0; .boost-bar > span.boost { position: absolute; left: 0; top: 0; z-index: 0;
background: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--gold) 45%, transparent) 0 6px, color-mix(in srgb, var(--gold) 20%, transparent) 6px 12px); } background: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--gold) 45%, transparent) 0 6px, color-mix(in srgb, var(--gold) 20%, transparent) 6px 12px); }
.btn { .btn {
@ -432,4 +432,3 @@ button.sheet-skill:hover { background: var(--panel-2); }
.credit a { color: var(--muted); } .credit a { color: var(--muted); }
.btn.working { color: var(--good); border-color: rgba(92, 191, 106, .5); background: rgba(92, 191, 106, .1); cursor: default; } .btn.working { color: var(--good); border-color: rgba(92, 191, 106, .5); background: rgba(92, 191, 106, .1); cursor: default; }
.banner-ico { font-size: 22px; display: flex; } .banner-ico { font-size: 22px; display: flex; }
.boosted { color: var(--gold); font-weight: 600; }