Fewer, smaller messages: mold percentages, markers for machine heat, short status lines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 23:03:45 -04:00
co-authored by Claude Opus 5.5
parent 43eb4e23fd
commit 4d4d94cbd8
25 changed files with 164 additions and 106 deletions
+14 -16
View File
@@ -30,7 +30,7 @@ function doInteract() {
openFilterPicker(Object.assign(filterInfo(fb), { dir: blockV[fb] & 128 ? 1 : 0 }), f => {
applyFilterRun(run, f);
GAME.filterCfg = { m: f.m.slice(), inv: f.inv }; GAME.pieceDir.filter = f.dir;
setStatus(`${run.length === 1 ? 'This filter' : `These ${run.length} filters`} now ${filterSentence(f)}, ${f.dir ? 'right' : 'left'}.`, 'info');
setStatus(`${run.length} ${run.length === 1 ? 'filter' : 'filters'} updated, carrying ${f.dir ? 'right' : 'left'}.`, 'info');
}, run.length > 1 ? `Filter (${run.length} in this row)` : 'Filter');
GAME.editRun = run;
return;
@@ -43,7 +43,7 @@ function doInteract() {
if (msg) break outer;
}
if (msg) { setStatus(msg, 'info'); if (!/^New (drill bit|bag)/.test(msg)) toast(msg); }
else { setStatus('Nothing to use there. F works on gates, molds, fans and filters.', 'info'); }
else { setStatus('Nothing to use there.', 'info'); }
}
// Pick (middle mouse): the piece under the cursor becomes the one you build, with its
// direction, wall material and filter settings. On loose material it picks that to blow out.
@@ -51,24 +51,22 @@ function pickUnderCursor() {
const ax = Math.floor(GAME.aim.x), ay = Math.floor(GAME.aim.y);
if (ax < 0 || ay < 0 || ax >= W || ay >= H) return;
const i = ay * W + ax, m = mat[i], bi = ((ay / BS) | 0) * BW + ((ax / BS) | 0);
let pid = null, note = '';
let pid = null;
const ghost = ghostBlocks.get(bi);
if (mach[i] && machines[mach[i]]) { const mm = machines[mach[i]]; pid = mm.type; if (PIECE_BY_ID[pid].dirs) GAME.pieceDir[pid] = mm.dir; }
else if (moldAt(ax, ay)) { const mo = moldAt(ax, ay); pid = mo.kind + 'mold'; GAME.wallMat = mo.wall; note = MD[WALL_OF[mo.wall]].name.toLowerCase(); }
else if (moldAt(ax, ay)) { const mo = moldAt(ax, ay); pid = mo.kind + 'mold'; GAME.wallMat = mo.wall; }
else if (blockP[bi] || ghost) {
const code = blockP[bi] || ghost[0], v = blockP[bi] ? blockV[bi] : ghost[1];
const P = PIECE_BY_CODE[code]; pid = P.id;
if (P.code === BP_WALL) { GAME.wallMat = v; note = MD[WALL_OF[v]].name.toLowerCase(); }
else if (P.filter) { GAME.filterCfg = blockP[bi] ? filterInfo(bi) : cleanFilterCfg(ghost[2]); GAME.pieceDir[P.id] = v >> 7; note = filterSentence(GAME.filterCfg); }
if (P.code === BP_WALL) { GAME.wallMat = v; }
else if (P.filter) { GAME.filterCfg = blockP[bi] ? filterInfo(bi) : cleanFilterCfg(ghost[2]); GAME.pieceDir[P.id] = v >> 7; }
else if (P.dirs) GAME.pieceDir[P.id] = v & 3;
if (P.dirs && !P.filter) note = P.dirs[GAME.pieceDir[P.id] || 0];
else if (P.filter) note = `${P.dirs[GAME.pieceDir[P.id] || 0]}, ${note}`;
}
if (pid) {
const P = PIECE_BY_ID[pid];
if (P.hidden) { setStatus(`${P.name} cannot be built.`); playSfx('error'); return; }
GAME.pieceIdx = P.index; GAME.buildCat = catOf(P.id); GAME.paste = false; selectTool(2); hudDirty = true; playSfx('ui');
setStatus(`Picked: ${P.name}${note ? ` (${note})` : ''}.`, 'info');
setStatus(`Picked: ${P.name}.`, 'info');
return;
}
const t = PH[m] === PH_POWDER || PH[m] === PH_LIQUID ? m : 0;
@@ -81,7 +79,7 @@ function stampOrigin(bw, bh) { const { bx, by } = aimBlock(); return { bx: bx -
// Copy and cut: hold C or X and drag a box with the left button; letting go does it.
function selModeHeld() { return GAME.running && !anyModal() ? (keys.has('x') ? 'cut' : keys.has('c') ? 'copy' : null) : null; }
function startPaste() {
if (!GAME.clip) { setStatus('Nothing copied yet. Hold C and drag a box over something you built.', 'info'); playSfx('error'); return; }
if (!GAME.clip) { setStatus('Hold C and drag over built pieces to copy.', 'info'); playSfx('error'); return; }
GAME.paste = true; GAME.drag = null; hudDirty = true; playSfx('ui');
}
function stopSelectAndPaste() { GAME.selDrag = null; GAME.paste = false; hudDirty = true; playSfx('ui'); }
@@ -145,7 +143,7 @@ function primaryPress(shift) {
}
if (!err) err = place();
});
if (err) { setStatus(err + (/already/.test(err) ? `${/[.!]$/.test(err) ? '' : '.'} Hold G to replace it.` : '')); playSfx('error'); } else { playSfx('place'); hudDirty = true; }
if (err) { setStatus(err + (/already/.test(err) ? `; hold G to replace it.` : '')); playSfx('error'); } else { playSfx('place'); hudDirty = true; }
}
}
}
@@ -156,11 +154,11 @@ function primaryRelease(shift) {
if (sd.mode === 'cut') {
let bp = null;
undoRecord(rec => `cutting ${plural(piecesIn(rec, 'block-') + piecesIn(rec, 'mach-') + piecesIn(rec, 'mold-'), 'piece')}`, () => { bp = cutRegion(sd.x, sd.y, ax, ay); });
if (bp) { GAME.clip = bp; GAME.paste = true; playSfx('decon'); setStatus(`Cut ${bp.bw}×${bp.bh} blocks into your bag. Click to paste it somewhere; Ctrl+Z puts it back.`, 'info'); }
if (bp) { GAME.clip = bp; GAME.paste = true; playSfx('decon'); setStatus(`Cut ${bp.bw}×${bp.bh} blocks; click to paste.`, 'info'); }
else { setStatus('Nothing you built is inside that box.'); playSfx('error'); }
} else {
const bp = copyRegion(sd.x, sd.y, ax, ay);
if (bp) { GAME.clip = bp; GAME.paste = true; playSfx('cast'); setStatus(`Copied ${bp.bw}×${bp.bh} blocks. Click to place it.`, 'info'); }
if (bp) { GAME.clip = bp; GAME.paste = true; playSfx('cast'); setStatus(`Copied ${bp.bw}×${bp.bh} blocks; click to place.`, 'info'); }
else { setStatus('Nothing you built is inside that box.'); playSfx('error'); }
}
return;
@@ -185,8 +183,8 @@ function primaryRelease(shift) {
if (ghosts) undoNote(g);
return res;
});
const hint = /already built/.test(r.status) ? `${/[.!]$/.test(r.status) ? '' : '.'} Hold G to replace it.` : '';
const gtxt = ghosts ? `${r.n ? `Placed ${plural(r.n, P.name.toLowerCase())}; ` : ''}${plural(ghosts, 'more')} out of reach ${ghosts === 1 ? 'is a ghost' : 'are ghosts'} your bot builds when it gets close.` : '';
const hint = /already built/.test(r.status) ? `; hold G to replace it.` : '';
const gtxt = ghosts ? `${r.n} placed; ${ghosts} ghosts waiting for the bot.` : '';
if (r.n || ghosts) { playSfx('place'); setStatus(gtxt || (r.status ? r.status + hint : ''), 'info'); } else { setStatus((r.status || 'Nothing placed.') + hint); playSfx('error'); }
GAME.drag = null; GAME.dragDir = -1; hudDirty = true;
}
@@ -219,7 +217,7 @@ function toolTick() {
if (r.status) setStatus(r.status);
}
} else if (t === 'ignite') {
if (inReach(ax, ay)) { const r = toolIgnite(ax, ay); playSfx('spark'); if (Math.random() < 0.6) spawnSpark(ax + 0.5, ay + 0.5); if (!r.fuel) setStatus('Nothing here burns. Aim at coal or wood that touches air.'); }
if (inReach(ax, ay)) { const r = toolIgnite(ax, ay); playSfx('spark'); if (Math.random() < 0.6) spawnSpark(ax + 0.5, ay + 0.5); if (!r.fuel) setStatus('Aim at coal or wood touching air to light it.'); }
else setStatus('Out of reach.');
}
}
+1 -9
View File
@@ -11,16 +11,7 @@ function drainEvents() {
const m = MILESTONES.find(x => x.id === e.id);
if (m && !['bit1', 'bit2', 'bit3', 'tank'].includes(m.id)) { showCard({ kind: 'Milestone', title: m.title, body: [m.msg, m.reward], accent: 'milestone' }); playSfx('milestone'); }
} else if (e.t === 'cast_ready') {
const metal = MD[e.metal] ? MD[e.metal].name : 'Metal';
const body = `The ${e.kind} casting has set. Press F on the mold to knock it out.`;
showCard({ kind: 'Casting ready', title: `${metal} ${e.kind}`, swatch: e.metal > 0 ? e.metal : 0, body: [body], accent: 'milestone' });
playSfx('cast');
} else if (e.t === 'ingot_drop') {
const metal = MD[e.metal] ? MD[e.metal].name : 'Metal', body = `${e.bars} ${barWord(e.metal, e.bars)} dropped out of the mold.`;
if (e.first) showCard({ kind: 'Casting', title: e.metal === GLASS ? 'Glass bricks' : `${metal} bars`, swatch: e.metal, body: [body], accent: 'milestone' });
else toast(body);
} else if (e.t === 'ingot_blocked') {
toast('Bars are ready in a mold but there is no room under it. Leave one open block under the mold: bars drop out of the bottom.');
} else if (e.t === 'upgrade') {
playSfx('upgrade'); if (SET.shake) R.shake = 1.4;
showCard({ kind: e.kind === 'bit' ? 'Drill bit upgraded' : 'Bag upgraded', title: e.kind === 'bit' ? BIT_TIERS[e.tier].name : TANK_TIERS[e.tier].name, accent: 'milestone',
@@ -28,6 +19,7 @@ function drainEvents() {
hudDirty = true;
} else if (e.t === 'debris') spawnDebris(e.x, e.y, e.m);
else if (e.t === 'suck') spawnSuck(e.x, e.y, e.m);
else if (e.t === 'warn') spawnFloater(e.x, e.y, e.text, '#ff8a72');
else if (e.t === 'sold') spawnFloater(e.x, e.y, '+' + fmtMoney(e.v));
else if (e.t === 'order_new') {
const o = e.o;
+5 -2
View File
@@ -88,7 +88,7 @@ function hoverInfo(ax, ay) {
const mm = machines[id], P = PIECE_BY_ID[mm.type]; t1 = P.name;
if (mm.type === 'dock') { t2.push('sells what drops through its grate', `${orders.length} open ${orders.length === 1 ? 'order' : 'orders'}`); }
if (mm.type === 'wheel' || mm.type === 'engine') t2.push(`making ${mm.out.toFixed(1)} power`);
if (mm.type === 'fan') t2.push(mm.on ? 'on' : 'off · press F');
if (mm.type === 'fan') { t2.push(mm.on ? 'on' : 'off · press F'); if (mm.hot > 760) { t2.push(`${mm.hot}°C · melts at 1085°C`); warn = 'too hot'; } }
if (mm.type === 'silo') t2.push(mm.sn ? `holds ${mm.sn.toLocaleString('en-US')} / ${SILO_CAP.toLocaleString('en-US')} ${MD[mm.sm].name.toLowerCase()}` : `empty (holds ${SILO_CAP.toLocaleString('en-US')} grains of one material)`, mm.open ? 'gate open · F shuts it' : 'gate shut · F opens it');
if (mm.type === 'cutter') t2.push(mm.blocked ? 'stopped: clear the space under it' : tick - (mm.busyT || -999) < 30 ? 'cutting' : 'idle: drop stone rubble in', `${(stats.bricks || 0).toLocaleString('en-US')} bricks cut`);
if (mm.type === 'refiner') t2.push(mm.blocked ? 'stopped: clear the space under its chute' : tick - (mm.busyT || -999) < 30 ? 'grinding' : 'idle: drop rubble, gravel, sand or dirt in', `${(stats.refined || 0).toLocaleString('en-US')} ground, ${(stats.refinedOre || 0).toLocaleString('en-US')} ore found`);
@@ -123,7 +123,10 @@ function hoverInfo(ax, ay) {
const mo = moldAt(ax, ay);
if (mo) {
t1 = MOLDS[mo.kind].name;
t2 = [mo.state === 'ready' ? (mo.kind === 'ingot' ? `${mo.ready.length} ${mo.ready.length === 1 ? 'bar' : 'bars'} set · drop when there is room below` : `${MD[mo.metal].name} casting set · press F`) : mo.state === 'flawed' || mo.state === 'short' ? mo.flaw : mo.state === 'filling' ? `filling ${Math.round(mo.fill * 100)}%` : 'empty'];
t2 = [mo.state === 'ready' ? (mo.kind === 'ingot' ? `${mo.ready.length} ${mo.ready.length === 1 ? 'bar' : 'bars'} set` : '100% set · press F') : mo.state === 'empty' ? 'empty' : `${mo.state === 'short' ? 'not full' : mo.state} ${Math.round(mo.fill * 100)}%`];
if (mo.blocked) t2.push('blocked: leave one open block under it');
if (mo.flaw) t2.push(mo.flaw);
const hint = furnaceJunkHint(mo).trim(); if (hint) t2.push(hint);
if (player.lens) t2.push(fmtT(temp[i]));
}
const reachOk = toolId() === 'drill' ? inDrillReach(ax, ay, GAME.drillW) : inReach(ax, ay);
+4 -4
View File
@@ -4,7 +4,7 @@ function setDrillWidth(w) {
const n = Math.max(0, Math.min(PULSE_W.length - 1, w));
if (n === GAME.drillW) return;
GAME.drillW = n; drillWidth = n; hudDirty = true; playSfx('ui');
const what = ['a beam as wide as the bot: bores a tunnel fast and reaches furthest', 'a cone: the all-rounder', 'a broad fan: clears the most rock, but only close to the bot'][n];
const what = ['long beam', 'balanced cone', 'broad fan'][n];
setStatus(`${PULSE_W[n].name[0].toUpperCase() + PULSE_W[n].name.slice(1)} pulse, ${what} (reach ${drillReach(n)}).`, 'info');
}
// R with the drill: tune it to the material under the cursor, or back to everything
@@ -15,7 +15,7 @@ function tuneUnderCursor() {
const to = PH[m] === PH_SOLID ? YLD[m] : PH[m] === PH_POWDER ? m : 0;
GAME.drillTune = !to || to === GAME.drillTune ? 0 : to;
hudDirty = true; playSfx('ui');
setStatus(GAME.drillTune ? `Tuned to ${MD[GAME.drillTune].name.toLowerCase()}: only that shatters, and the pulses pass through everything else.` : 'Untuned: the drill shatters any rock it can.', 'info');
setStatus(GAME.drillTune ? `Tuned to ${MD[GAME.drillTune].name.toLowerCase()}: only it shatters.` : 'Untuned: the drill shatters any rock it can.', 'info');
}
function cycleItem(d) {
const t = toolId();
@@ -74,13 +74,13 @@ function toggleFilterMat(m) {
if (k >= 0) f.m.splice(k, 1); else f.m.push(m);
f.m.sort((a, b) => a - b);
hudDirty = true; lastBagKey = ''; playSfx('ui');
setStatus(`New filters ${filterSentence(f)}.`, 'info');
setStatus(`New filter selection updated.`, 'info');
}
function toggleCollectUnderCursor() {
const m = grainAt(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
if (!m) { setStatus('Aim at something loose to tick it.', 'info'); return; }
toggleCollect(m);
setStatus(`${MD[m].name} ${GAME.collect.has(m) ? 'ticked. While anything is ticked, the vacuum takes only ticked materials' : GAME.collect.size ? 'unticked' : 'unticked. Nothing is ticked, so the vacuum takes what you start on'}.`, 'info');
setStatus(`${MD[m].name} ${GAME.collect.has(m) ? 'ticked for vacuuming' : 'unticked'}.`, 'info');
}
function setBrush(d) {
const t = toolId();