Fires: everyone keeps their own pile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-21 13:14:03 +01:00
parent a02a81dffd
commit b4ecacf96c
4 changed files with 91 additions and 54 deletions

View file

@ -571,8 +571,9 @@ function skillGear(skill) {
}
// --- fires ----------------------------------------------------------------
// The guild's shared fires. Between polls the client burns them down itself, hottest
// fuel first, the same way the server does.
// The guild's shared fires. Between polls the client burns them down itself, the same
// way the server does: the burn shared evenly between everyone's piles, each pile's
// hottest fuel first.
const ROMAN = ["I", "II", "III", "IV", "V", "VI"];
// Core to edge of each flame colour, for the animated fires.
@ -590,16 +591,20 @@ const tendLevel = (tier) => C.actions.find((a) => a.id === `tend_forge_${C.firew
function fireNow(id) {
const f = S.city.fires[id], fuel = f.fuel.map((c) => ({ ...c }));
const hottest = (by) => fuel.filter((c) => c.by === by).reduce((best, c) => (c.tier > best.tier ? c : best));
let burn = Math.max(0, serverNow() - f.at) * f.speed;
while (burn > 0 && fuel.length) {
const top = fuel.reduce((best, c) => (c.tier > best.tier ? c : best));
const used = Math.min(burn, top.left);
top.left -= used;
burn -= used;
if (top.left <= 1e-6) fuel.splice(fuel.indexOf(top), 1);
while (burn > 1e-9 && fuel.length) {
const heads = [...new Set(fuel.map((c) => c.by))].map(hottest);
const step = Math.min(burn, Math.min(...heads.map((c) => c.left)) * heads.length);
for (const c of heads) {
c.left -= step / heads.length;
if (c.left <= 1e-6) fuel.splice(fuel.indexOf(c), 1);
}
burn -= step;
}
const heat = fuel.reduce((h, c) => Math.max(h, c.tier), -1), left = fuel.reduce((sum, c) => sum + c.left, 0);
return { fuel, heat, left, low: heat >= 0 && left < C.fire_low, speed: f.speed };
const mine = fuel.filter((c) => c.by === S.uid);
return { fuel, mine, heat, left, low: heat >= 0 && left < C.fire_low, speed: f.speed };
}
function pickFirewood() {
@ -628,6 +633,8 @@ function fireCard(id, pick) {
const start = `tend_${id}_${C.firewood[pick]}`, why = mine ? "" : me.blocked[start];
const crafting = f.users.filter((u) => !u.action.startsWith("tend_"));
const doing = (u) => C.actions.find((a) => a.id === u.action);
const piles = [...new Set(f.fuel.map((c) => c.by))];
const keepers = piles.length ? `<span title="${esc(piles.map(playerName).join("\n"))}">🔥 ${piles.length}</span>` : "";
const who = crafting.length
? `<span title="${esc(crafting.map((u) => `${u.name}: ${doing(u).name}`).join("\n"))}">👤 ${crafting.length} ${crafting.map((u) => doing(u).icon).join("")}</span>`
: `<span class="muted">👤 0</span>`;
@ -635,9 +642,10 @@ function fireCard(id, pick) {
<div class="fire-stage"><canvas data-flame="${id}"></canvas><span class="fire-state" data-fire-state="${id}"></span></div>
<div class="fire-body">
<div class="fire-head">${glyph(spec.glyph, "#e7e9f0")}<b>${esc(spec.name)}</b><span class="muted small">${skill.icon} ${esc(skill.name)}</span></div>
<div class="fuel" data-fuel="${id}"></div>
<div class="fire-line"><span data-fire-left="${id}"></span><span data-fire-covers="${id}"></span></div>
<div class="fire-line small">${who}<span title="Logs this fire has paid for">🪵 ${fmt(f.saved || 0)}</span></div>
<div class="fire-line"><span data-fire-left="${id}" title="Until it's out"></span><span data-fire-covers="${id}"></span></div>
<div class="fuel" data-fuel="${id}" title="Your pile"></div>
<div class="fire-line small"><span data-fire-mine="${id}" title="Your pile / the most you can bank"></span><span title="Logs this fire has paid for">🪵 ${fmt(f.saved || 0)}</span></div>
<div class="fire-line small">${who}${keepers}</div>
${mine ? `<div class="bar thick"><span data-progress></span></div>` : ""}
<div class="foot"><span class="why">${esc(why || "")}</span>${mine
? `<button class="btn danger" data-stop>Stop</button>`
@ -670,10 +678,11 @@ function animateFires() {
if (canvas) drawFlame(canvas, id, n);
if (!text) continue;
const set = (sel, html) => { for (const el of document.querySelectorAll(sel)) if (el.dataset.v !== html) { el.innerHTML = html; el.dataset.v = html; } };
const bank = Math.max(S.me.firekeeping.bank * 60, n.left);
const tiers = C.flames.map((_, t) => n.fuel.filter((c) => c.tier === t).reduce((sum, c) => sum + c.left, 0));
set(`[data-fuel="${id}"]`, tiers.map((left, t) => left ? `<i style="--c:${flameTint(t)};width:${(left / bank * 100).toFixed(2)}%"></i>` : "").join(""));
set(`[data-fire-left="${id}"]`, `<b>${dur(n.left / n.speed)}</b> / ${dur(S.me.firekeeping.bank * 60 / n.speed)}`);
const bank = S.me.firekeeping.bank * 60, mine = n.mine.reduce((sum, c) => sum + c.left, 0);
const tiers = C.flames.map((_, t) => n.mine.filter((c) => c.tier === t).reduce((sum, c) => sum + c.left, 0));
set(`[data-fuel="${id}"]`, tiers.map((left, t) => left ? `<i style="--c:${flameTint(t)};width:${(left / Math.max(bank, mine) * 100).toFixed(2)}%"></i>` : "").join(""));
set(`[data-fire-left="${id}"]`, n.heat >= 0 ? `🔥 <b>${dur(n.left / n.speed)}</b>` : "");
set(`[data-fire-mine="${id}"]`, `You ${dur(mine / n.speed)} / ${dur(bank / n.speed)}`);
set(`[data-fire-time="${id}"]`, dur(n.left / n.speed));
set(`[data-fire-covers="${id}"]`, n.heat >= 0 ? `<span class="tag flame" style="--c:${flameTint(n.heat)}" title="Pays the log for tier ${ROMAN[n.heat]} and below">🪵 ≤ ${ROMAN[n.heat]}</span>` : "");
set(`[data-fire-state="${id}"]`, n.heat < 0 ? "Out" : n.low ? "Low" : C.flames[n.heat][0]);