Silo contents at a glance (t38); drill aimed past its reach stays calm (t41)
Silos show a wide fill window in the material's colour in both looks, and the cursor label names the contents anywhere over the outline. Aiming the drill beyond its reach keeps normal colours, marks where the pulse stops, and drops the red circle and the out-of-reach toast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+29
-15
@@ -119,7 +119,7 @@ function drawProcBlocks() {
|
||||
// glass that shows how full it is, a roof with the loading hatch. All in grain units from its corner.
|
||||
function drawSilo(ctx, mm, sx, sy, zp) {
|
||||
if (!R.smoothOn) { drawSiloPixel(ctx, mm, sx, sy, zp); return; }
|
||||
const X = u => sx + u * zp, Y = u => sy + u * zp, full = Math.min(1, (mm.sn || 0) / SILO_CAP);
|
||||
const X = u => sx + u * zp, Y = u => sy + u * zp;
|
||||
const steel = (x0, x1) => { const g = ctx.createLinearGradient(X(x0), 0, X(x1), 0); g.addColorStop(0, '#3b4249'); g.addColorStop(0.22, '#a9b3bb'); g.addColorStop(0.5, '#87919a'); g.addColorStop(1, '#353c43'); return g; };
|
||||
ctx.save();
|
||||
// legs, with short struts to the cone
|
||||
@@ -146,22 +146,16 @@ function drawSilo(ctx, mm, sx, sy, zp) {
|
||||
ctx.beginPath(); ctx.moveTo(X(1.3), Y(2)); ctx.lineTo(X(1.3), Y(19.8)); ctx.moveTo(X(2.3), Y(2)); ctx.lineTo(X(2.3), Y(19.8));
|
||||
for (let yy = 2.6; yy < 19.8; yy += 1.1) { ctx.moveTo(X(1.3), Y(yy)); ctx.lineTo(X(2.3), Y(yy)); }
|
||||
ctx.stroke();
|
||||
// the sight glass: fills from the bottom in the colour of what is inside
|
||||
const gx = 9.1, gy0 = 3, gy1 = 18.8, gh = gy1 - gy0;
|
||||
ctx.fillStyle = '#14181c'; ctx.fillRect(X(gx), Y(gy0), zp * 1.1, zp * gh);
|
||||
if (mm.sn > 0) { ctx.fillStyle = `rgb(${COLR[mm.sm]},${COLG[mm.sm]},${COLB[mm.sm]})`; ctx.fillRect(X(gx + 0.15), Y(gy1 - gh * full), zp * 0.8, zp * gh * full); }
|
||||
ctx.strokeStyle = '#c9d1d8'; ctx.lineWidth = Math.max(1, zp * 0.1);
|
||||
ctx.strokeRect(X(gx), Y(gy0), zp * 1.1, zp * gh);
|
||||
for (let k = 1; k < 4; k++) { const yy = Y(gy1 - gh * k / 4); ctx.beginPath(); ctx.moveTo(X(gx - 0.35), yy); ctx.lineTo(X(gx), yy); ctx.stroke(); }
|
||||
drawSiloWindow(ctx, mm, sx, sy, zp, false);
|
||||
// what it holds, stencilled on the drum
|
||||
if (zp >= 3) {
|
||||
renderMonoFont();
|
||||
const fs = Math.max(8, Math.round(zp * 1.15));
|
||||
ctx.font = `700 ${fs}px ${R.monoFont}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = 'rgba(20,24,28,0.75)';
|
||||
ctx.fillText('SILO', X(5.6), Y(5));
|
||||
ctx.fillText('SILO', X(4.5), Y(5), 5 * zp);
|
||||
ctx.font = `600 ${Math.max(8, Math.round(zp * 0.95))}px ${R.monoFont}`;
|
||||
ctx.fillText(mm.sn ? mm.sn.toLocaleString('en-US') : 'EMPTY', X(5.6), Y(16.8));
|
||||
ctx.fillText(mm.sn ? mm.sn.toLocaleString('en-US') : 'EMPTY', X(4.5), Y(16.8), 5 * zp);
|
||||
if (mm.sn) { ctx.fillStyle = `rgb(${COLR[mm.sm]},${COLG[mm.sm]},${COLB[mm.sm]})`; ctx.fillRect(X(4.4), Y(17.9), zp * 2.4, zp * 0.55); }
|
||||
}
|
||||
// roof with a rim, and the loading hatch (its lid stands open)
|
||||
@@ -173,7 +167,7 @@ function drawSilo(ctx, mm, sx, sy, zp) {
|
||||
ctx.restore();
|
||||
}
|
||||
function drawSiloPixel(ctx, mm, sx, sy, zp) {
|
||||
const full = Math.min(1, (mm.sn || 0) / SILO_CAP), col = `rgb(${COLR[mm.sm]},${COLG[mm.sm]},${COLB[mm.sm]})`;
|
||||
const col = `rgb(${COLR[mm.sm]},${COLG[mm.sm]},${COLB[mm.sm]})`;
|
||||
const grain = (x, y, c) => { ctx.fillStyle = c; ctx.fillRect(sx + x * zp, sy + y * zp, zp, zp); };
|
||||
// Steel shaded by column (light on the left, dark at the right edge), darker rib rows, and fixed
|
||||
// grain noise so it never shimmers. The drum is rows 2-19, the cone 20-22 narrowing to the gate.
|
||||
@@ -191,8 +185,7 @@ function drawSiloPixel(ctx, mm, sx, sy, zp) {
|
||||
grain(1, 21, '#2f353b'); grain(10, 21, '#2f353b');
|
||||
for (let x = 4; x < 8; x++) grain(x, 23, '#1c1f23');
|
||||
if (mm.open) for (let x = 8; x < 11; x++) grain(x, 23, '#c99a3c'); else for (let x = 3; x < 9; x++) grain(x, 23, '#6b737b');
|
||||
// the sight glass fills from the bottom in the colour of what is inside
|
||||
for (let y = 3; y < 19; y++) grain(9, y, mm.sn > 0 && y >= 19 - Math.max(1, Math.round(16 * full)) ? col : '#14181c');
|
||||
drawSiloWindow(ctx, mm, sx, sy, zp, true);
|
||||
// roof: a rim, shoulders, the dark hatch and its lid standing open
|
||||
for (let x = 0; x < 12; x++) {
|
||||
grain(x, 1, x >= 3 && x < 9 ? '#15181b' : '#5b646c');
|
||||
@@ -204,14 +197,35 @@ function drawSiloPixel(ctx, mm, sx, sy, zp) {
|
||||
renderMonoFont();
|
||||
ctx.save(); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = 'rgba(20,24,28,0.75)';
|
||||
ctx.font = `700 ${Math.max(8, Math.round(zp * 1.15))}px ${R.monoFont}`;
|
||||
ctx.fillText('SILO', sx + 5.6 * zp, sy + 5 * zp);
|
||||
ctx.fillText('SILO', sx + 4.5 * zp, sy + 5 * zp, 5 * zp);
|
||||
ctx.font = `600 ${Math.max(8, Math.round(zp * 0.95))}px ${R.monoFont}`;
|
||||
ctx.fillText(mm.sn ? mm.sn.toLocaleString('en-US') : 'EMPTY', sx + 5.6 * zp, sy + 16.8 * zp);
|
||||
ctx.fillText(mm.sn ? mm.sn.toLocaleString('en-US') : 'EMPTY', sx + 4.5 * zp, sy + 16.8 * zp, 5 * zp);
|
||||
if (mm.sn) { grain(4, 18, col); grain(5, 18, col); grain(6, 18, col); }
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// The same framed, three-grain window in both looks; one grain remains visible near empty.
|
||||
function drawSiloWindow(ctx, mm, sx, sy, zp, pixel) {
|
||||
const height = mm.sn > 0 ? Math.max(1, Math.round(16 * Math.min(1, mm.sn / SILO_CAP))) : 0;
|
||||
ctx.fillStyle = '#c9d1d8'; ctx.fillRect(sx + 7 * zp, sy + 2 * zp, 5 * zp, 18 * zp);
|
||||
ctx.fillStyle = '#14181c'; ctx.fillRect(sx + 8 * zp, sy + 3 * zp, 3 * zp, 16 * zp);
|
||||
if (!height) return;
|
||||
// Lift dark fuel slightly so coal stays distinct from the empty glass.
|
||||
const lift = Math.max(COLR[mm.sm], COLG[mm.sm], COLB[mm.sm]) < 80 ? 32 : 0;
|
||||
if (pixel) {
|
||||
for (let y = 19 - height; y < 19; y++) for (let x = 8; x < 11; x++) {
|
||||
const h = Math.imul(mm.x + x, 374761393) ^ Math.imul(mm.y + y, 668265263);
|
||||
const n = lift + (((h ^ h >>> 13) >>> 0) % 3 - 1) * 8;
|
||||
ctx.fillStyle = `rgb(${COLR[mm.sm] + n},${COLG[mm.sm] + n},${COLB[mm.sm] + n})`;
|
||||
ctx.fillRect(sx + x * zp, sy + y * zp, zp, zp);
|
||||
}
|
||||
} else {
|
||||
ctx.fillStyle = `rgb(${COLR[mm.sm] + lift},${COLG[mm.sm] + lift},${COLB[mm.sm] + lift})`;
|
||||
ctx.fillRect(sx + 8 * zp, sy + (19 - height) * zp, 3 * zp, height * zp);
|
||||
}
|
||||
}
|
||||
|
||||
// Shelves show each block's fill and materials; the bottom gate belongs to the column.
|
||||
function drawRackBlocks() {
|
||||
const ctx = R.ctx;
|
||||
|
||||
+1
-2
@@ -43,7 +43,7 @@ function toolPulse(ax, ay, width, tune) {
|
||||
// every cell in the cone is hit, with no untouched stripes between the rays
|
||||
drillPulses.push({ ox: o.x, oy: o.y, ang, w: drillWidth, jit: rnd(), n: Wd.rays, damp: Wd.damp, en: new Float32Array(Wd.rays).fill(Wd.en), s: new Float32Array(Wd.rays), r: 0, range, tune: tune || 0 });
|
||||
lastPulseTick = tick;
|
||||
// say why nothing breaks: the first solid on the centre line is too hard, or out of reach
|
||||
// say why nothing breaks: the first solid on the centre line is too hard
|
||||
let status = '';
|
||||
for (let k = 3; k < reach; k++) {
|
||||
const x = Math.floor(o.x + dx / d * k), y = Math.floor(o.y + dy / d * k);
|
||||
@@ -57,7 +57,6 @@ function toolPulse(ax, ay, width, tune) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!status && d > reach) status = `Out of reach; a ${Wd.name} pulse reaches ${reach} grains.`;
|
||||
return { fired: true, status };
|
||||
}
|
||||
const THIN_BONUS = 0.75, CRUMBLE_MAX = 16;
|
||||
|
||||
+17
-11
@@ -81,17 +81,23 @@ function hoverInfo(ax, ay) {
|
||||
if (ax < 0 || ay < 0 || ax >= W || ay >= H) return { t1: '', t2: ['Outside the world'], warn: '', air: true };
|
||||
const i = ay * W + ax, m = mat[i];
|
||||
let warn = '';
|
||||
let t1 = MD[m].name, t2 = [];
|
||||
const p = part[i], id = mach[i];
|
||||
let t1 = MD[m].name, t2 = [], contents = '';
|
||||
const p = part[i];
|
||||
// Cone gaps have no machine cells; hover still belongs to the silo's whole outline.
|
||||
const silo = machines.find(mm => mm && mm.type === 'silo' && ax >= mm.x && ax < mm.x + mm.w && ay >= mm.y && ay < mm.y + mm.h);
|
||||
const id = silo ? silo.id : mach[i];
|
||||
const b = blockP[((ay / BS) | 0) * BW + ((ax / BS) | 0)];
|
||||
const bar = occ[i] ? ents.find(e => ax >= e.ix && ax < e.ix + BAR_W && ay >= e.iy && ay < e.iy + BAR_H) : null;
|
||||
if (bar) { t1 = MD[bar.m].name; t2.push(fmtT(bar.T)); }
|
||||
if (bar && !silo) { t1 = MD[bar.m].name; t2.push(fmtT(bar.T)); }
|
||||
else if (id && machines[id]) {
|
||||
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.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 === 'silo') {
|
||||
contents = mm.sn ? `${MD[mm.sm].name.toLowerCase()} ${mm.sn.toLocaleString('en-US')} / ${SILO_CAP.toLocaleString('en-US')}` : 'empty';
|
||||
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');
|
||||
}
|
||||
} else if (b === BP_RACK) {
|
||||
const top = rackTop((ay >> 2) * BW + (ax >> 2)), counts = new Map();
|
||||
let n = 0, capacity = 0;
|
||||
@@ -135,9 +141,9 @@ function hoverInfo(ax, ay) {
|
||||
if (!ok && toolId() === 'drill') warn = HARD[m] > 6 ? 'cannot be drilled' : 'too hard';
|
||||
}
|
||||
if (!bar && PH[m] === PH_POWDER) t2.push('loose');
|
||||
if (!bar && m === EMPTY && bgm[i]) { t1 = bgm[i] === WOOD ? 'Tree trunk' : 'Leaves'; t2.unshift(bgm[i] === WOOD ? 'drill it for wood chunks' : 'water and sand pass through'); }
|
||||
if (!id && !bar && m === EMPTY && bgm[i]) { t1 = bgm[i] === WOOD ? 'Tree trunk' : 'Leaves'; t2.unshift(bgm[i] === WOOD ? 'drill it for wood chunks' : 'water and sand pass through'); }
|
||||
if (flags[i] & F_BURN) t2.push('burning');
|
||||
const mo = moldAt(ax, ay);
|
||||
const mo = silo ? null : 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` : '100% set · press F') : mo.state === 'empty' ? 'empty' : `${mo.state === 'short' ? 'not full' : mo.state} ${Math.round(mo.fill * 100)}%`];
|
||||
@@ -146,12 +152,12 @@ function hoverInfo(ax, ay) {
|
||||
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);
|
||||
const reachOk = toolId() === 'drill' || inReach(ax, ay);
|
||||
if (!reachOk) { t2.push('out of reach'); warn = 'out of reach'; }
|
||||
// what it does, unless the cursor is on a machine or mold, which have their own details
|
||||
const what = bar ? bar.m : !mo && !id && !(p && p !== P_GRATE && p !== P_PIPE) && m !== BELTL && m !== BELTR && m !== PIPE ? (m === EMPTY && bgm[i] ? bgm[i] : m) : 0;
|
||||
const t3 = what ? reactionsOf(what) : [];
|
||||
return { t1, t2, t3, warn, air: m === EMPTY && !bgm[i] && !bar && !mo && !id && !p && !b };
|
||||
return { t1, t2, t3, warn, contents, air: m === EMPTY && !bgm[i] && !bar && !mo && !id && !p && !b };
|
||||
}
|
||||
function renderInfo() {
|
||||
const r = hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y));
|
||||
@@ -167,8 +173,8 @@ function drawCursorLabel(px, py, r) {
|
||||
const ctx = R.ctx, dpr = R.canvas.width / window.innerWidth;
|
||||
const fs = Math.round(12 * dpr * SET.uiScale);
|
||||
if (!R.uiFont) R.uiFont = getComputedStyle(document.documentElement).getPropertyValue('--font-ui').trim() || 'sans-serif';
|
||||
const main = info.t1 + (player.lens && R.lens ? '' : '');
|
||||
const sub = info.warn || (R.lens ? info.t2.find(t => /°C$/.test(t)) || '' : '');
|
||||
const main = info.t1 + (info.contents && info.warn ? ` ${info.contents}` : '');
|
||||
const sub = info.warn || info.contents || (R.lens ? info.t2.find(t => /°C$/.test(t)) || '' : '');
|
||||
ctx.save();
|
||||
ctx.font = `600 ${fs}px ${R.uiFont}`;
|
||||
const w1 = ctx.measureText(main).width;
|
||||
@@ -182,7 +188,7 @@ function drawCursorLabel(px, py, r) {
|
||||
ctx.beginPath(); ctx.roundRect(x, y, w1 + w2 + padX * 2, h, 4 * dpr); ctx.fill();
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = `600 ${fs}px ${R.uiFont}`; ctx.fillStyle = '#efe6d8'; ctx.fillText(main, x + padX, y + h / 2);
|
||||
if (sub) { ctx.font = `500 ${Math.round(fs * 0.9)}px ${R.uiFont}`; ctx.fillStyle = info.warn ? '#ef6a55' : '#ffd27a'; ctx.fillText(sub, x + padX + w1 + fs * 0.6, y + h / 2); }
|
||||
if (sub) { ctx.font = `500 ${Math.round(fs * 0.9)}px ${R.uiFont}`; ctx.fillStyle = info.warn ? '#ef6a55' : info.contents ? '#efe6d8' : '#ffd27a'; ctx.fillText(sub, x + padX + w1 + fs * 0.6, y + h / 2); }
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ function drawDrillAim(ax, ay) {
|
||||
const o = drillOrigin(), c = worldToScreenPx(o.x, o.y), p = worldToScreenPx(ax + 0.5, ay + 0.5);
|
||||
const d = Math.hypot(ax + 0.5 - o.x, ay + 0.5 - o.y), ang = Math.atan2(ay + 0.5 - o.y, ax + 0.5 - o.x);
|
||||
const Wd = PULSE_W[GAME.drillW], range = Math.max(3, Math.min(drillReach(GAME.drillW), d + 3));
|
||||
const ok = inDrillReach(ax, ay, GAME.drillW), col = ok ? pulseRGB(GAME.drillTune) : '239,106,85';
|
||||
const col = pulseRGB(GAME.drillTune);
|
||||
const mouth = fanPoints(o.x, o.y, ang, Wd, 2.5, 1), far = fanPoints(o.x, o.y, ang, Wd, range, 10);
|
||||
ctx.save();
|
||||
// the outline gets a dark under-stroke so it reads on bright sky as well as dark rock
|
||||
@@ -84,6 +84,9 @@ function drawDrillAim(ax, ay) {
|
||||
ctx.strokeStyle = 'rgba(10,14,20,0.28)'; ctx.lineWidth = Math.max(2, zp * 0.3); ctx.stroke();
|
||||
ctx.strokeStyle = `rgba(${col},0.6)`; ctx.lineWidth = Math.max(1, zp * 0.14); ctx.stroke();
|
||||
ctx.setLineDash([]); ctx.lineDashOffset = 0;
|
||||
// A solid far edge marks where the pulse stops, even with a distant cursor.
|
||||
ctx.beginPath(); far.forEach((pt, k) => k ? ctx.lineTo(pt.x, pt.y) : ctx.moveTo(pt.x, pt.y));
|
||||
ctx.strokeStyle = `rgba(${col},0.85)`; ctx.lineWidth = Math.max(1, zp * 0.22); ctx.stroke();
|
||||
// the emitter glows across the bot's face while it fires
|
||||
const hot = Math.max(0, 1 - (tick - lastPulseTick) / PULSE_EVERY);
|
||||
if (GAME.lmb && hot > 0) {
|
||||
@@ -95,7 +98,7 @@ function drawDrillAim(ax, ay) {
|
||||
ctx.fillStyle = g; ctx.fillRect(c.x - zp * 3.5, c.y - zp * 3.5, zp * 7, zp * 7);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
ctx.strokeStyle = ok ? 'rgba(239,230,216,0.85)' : 'rgba(239,106,85,0.85)'; ctx.lineWidth = Math.max(1, zp * 0.2);
|
||||
ctx.strokeStyle = 'rgba(239,230,216,0.85)'; ctx.lineWidth = Math.max(1, zp * 0.2);
|
||||
const k = zp * 0.9;
|
||||
ctx.beginPath(); ctx.moveTo(p.x - k * 1.6, p.y); ctx.lineTo(p.x - k * 0.5, p.y); ctx.moveTo(p.x + k * 0.5, p.y); ctx.lineTo(p.x + k * 1.6, p.y);
|
||||
ctx.moveTo(p.x, p.y - k * 1.6); ctx.lineTo(p.x, p.y - k * 0.5); ctx.moveTo(p.x, p.y + k * 0.5); ctx.lineTo(p.x, p.y + k * 1.6); ctx.stroke();
|
||||
|
||||
@@ -70,9 +70,6 @@ function drawPreviews() {
|
||||
if (!reachOk && t !== 'drill' && !sel) {
|
||||
drawReachCircle(REACH, 0.35);
|
||||
}
|
||||
if (t === 'drill' && !sel && !pasting && !inDrillReach(ax, ay, GAME.drillW)) {
|
||||
drawReachCircle(drillReach(GAME.drillW), 0.45);
|
||||
}
|
||||
drawDrillPulses();
|
||||
if (t === 'drill' && !sel && !pasting) drawDrillAim(ax, ay);
|
||||
if (sel) drawSelection(sel, ax, ay);
|
||||
|
||||
Reference in New Issue
Block a user