Build range follows the screen; out-of-range replace ghosts replace (t64)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 16:41:32 -04:00
co-authored by Claude Opus 5.5
parent 6bc4fd5928
commit ef4ba6d24a
11 changed files with 77 additions and 16 deletions
+4 -3
View File
@@ -131,7 +131,7 @@ function primaryPress(shift) {
else {
const { bw, bh } = pieceSize(P);
const { bx, by } = stampOrigin(bw, bh);
if (!inReach(bx * BS + bw * BS / 2, by * BS + bh * BS / 2)) { setStatus('Out of reach.'); playSfx('error'); return; }
if (!inBuildRange(bx * BS + bw * BS / 2, by * BS + bh * BS / 2)) { setStatus('Out of reach.'); playSfx('error'); return; }
const place = () => P.kind === 'mold' ? placeMoldAt(P.mold, bx, by, GAME.wallMat) : placeMachineAt(P.id, GAME.pieceDir[P.id] || 0, bx, by);
const check = () => P.kind === 'mold' ? canPlaceMold(P.mold, bx, by) : canPlaceMachine(P.id, bx, by);
let err = '';
@@ -178,7 +178,8 @@ function primaryRelease(shift) {
for (const [bx, by] of far) {
const b = by * BW + bx;
if (bx < 0 || by < 0 || bx >= BW || by >= BH || blockIs(b, code, v, ex) || ghostBlocks.has(b)) continue;
ghostBlocks.set(b, ex ? [code, v, ex] : [code, v]); g.blocks.push([bx, by, code]); ghosts++;
// held G over a different piece: the ghost replaces it when the bot comes in range
ghostBlocks.set(b, GAME.replace && blockP[b] ? [code, v, ex, 1] : ex ? [code, v, ex] : [code, v]); g.blocks.push([bx, by, code]); ghosts++;
}
if (ghosts) undoNote(g);
return res;
@@ -230,7 +231,7 @@ function toolTick() {
if (r.placed) { playSfx('blow'); hudDirty = true; } else if (r.status) setStatus(r.status);
} else if (t === 'build') {
const key = `${(ax / BS) | 0},${(ay / BS) | 0}`;
if (!GAME.rmbSeen.has(key) && inReach(ax, ay)) {
if (!GAME.rmbSeen.has(key) && inBuildRange(ax, ay)) {
GAME.rmbSeen.add(key);
const keep = undoRec; undoRec = GAME.rmbRec;
const msg = removePieceAt(ax, ay);
+1 -1
View File
@@ -158,7 +158,7 @@ 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' || inReach(ax, ay);
const reachOk = toolId() === 'drill' || (toolId() === 'build' ? inBuildRange(ax, ay) : 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;
+1
View File
@@ -54,6 +54,7 @@ function frame(now) {
// grid under the cursor does not slide around mid-line.
if (GAME.drag) { lookX = GAME.lookHold.x; lookY = GAME.lookHold.y; } else { GAME.lookHold.x = lookX; GAME.lookHold.y = lookY; }
if (!GAME.drag || Math.hypot(bot.vx, bot.vy) > 0.08) updateCamera(dt, lookX, lookY);
{ const v = viewCells(); setBuildRange(R.camX + v.w / 2, R.camY + v.h / 2, v.w, v.h); }
R.frame++;
drawWorld();
drawCracks();
+2 -2
View File
@@ -67,7 +67,7 @@ function drawPreviews() {
ctx.save();
const reachOk = inReach(ax, ay);
const sel = GAME.selDrag ? GAME.selDrag.mode : selModeHeld(), pasting = !sel && GAME.paste && GAME.clip;
if (!reachOk && t !== 'drill' && !sel) {
if (!reachOk && t !== 'drill' && t !== 'build' && !sel) {
drawReachCircle(REACH, 0.35);
}
drawDrillPulses();
@@ -143,7 +143,7 @@ function drawPreviews() {
const { bx, by } = stampOrigin(bw, bh);
const err = P.kind === 'mold' ? canPlaceMold(P.mold, bx, by) : canPlaceMachine(P.id, bx, by);
const swap = !!err && GAME.replace && /already/.test(err) && !footprintBlocker(bx, by, bw, bh);
const ok = (!err || swap) && inReach(bx * BS + bw * BS / 2, by * BS + bh * BS / 2) && canAfford(pieceCostNow(P), 1);
const ok = (!err || swap) && inBuildRange(bx * BS + bw * BS / 2, by * BS + bh * BS / 2) && canAfford(pieceCostNow(P), 1);
const [x, y, w, h] = blockRectPx(bx, by, bw, bh);
if (swap && ok) { ctx.fillStyle = 'rgba(255,190,90,0.35)'; ctx.fillRect(x, y, w, h); }
if (P.kind === 'mold') {