// Blueprints: copy, cut, flip, cost and paste (as ghosts) a region of built pieces. // ---------- Blueprints ---------- // A blueprint is a list of pieces in block coordinates, so it always pastes on the grid. function copyRegion(x0, y0, x1, y1) { const bx0 = Math.max(0, Math.floor(Math.min(x0, x1) / BS)), bx1 = Math.min(BW - 1, Math.floor(Math.max(x0, x1) / BS)); const by0 = Math.max(0, Math.floor(Math.min(y0, y1) / BS)), by1 = Math.min(BH - 1, Math.floor(Math.max(y0, y1) / BS)); const blocks = [], ms = [], ols = []; for (let by = by0; by <= by1; by++) for (let bx = bx0; bx <= bx1; bx++) { const b = by * BW + bx; if (blockP[b] && blockIntegrity(b) >= 8) blocks.push(isFilter(blockP[b]) ? [bx - bx0, by - by0, blockP[b], blockV[b], filterInfo(b)] : [bx - bx0, by - by0, blockP[b], blockV[b]]); } for (const mm of machines) { if (!mm || PIECE_BY_ID[mm.type].fixed) continue; const mbx = mm.x / BS, mby = mm.y / BS, P = PIECE_BY_ID[mm.type]; if (mbx >= bx0 && mby >= by0 && mbx + P.bw - 1 <= bx1 && mby + P.bh - 1 <= by1) ms.push([mm.type, mbx - bx0, mby - by0, mm.dir]); } for (const mo of molds) { const mbx = mo.x / BS, mby = mo.y / BS, md = MOLDS[mo.kind]; if (mbx >= bx0 && mby >= by0 && mbx + md.bw - 1 <= bx1 && mby + md.bh - 1 <= by1) ols.push([mo.kind, mbx - bx0, mby - by0, mo.wall]); } if (!blocks.length && !ms.length && !ols.length) return null; return { v: 2, bw: bx1 - bx0 + 1, bh: by1 - by0 + 1, blocks, machines: ms, molds: ols }; } function flipBlueprint(bp) { const bw = bp.bw; const flipV = (code, v) => isBelt(code) ? 1 - v : isFilter(code) ? v ^ 128 : code === BP_SCREW ? [0, 3, 2, 1, 7, 6, 5, 4][v & 7] : code === BP_PUMP || code === BP_OUTLET ? (((v & 3) === 1 ? 3 : (v & 3) === 3 ? 1 : v & 3) | (v & 4)) : v; return { v: 2, bw, bh: bp.bh, name: bp.name, blocks: bp.blocks.map(([x, y, c, v, e]) => e ? [bw - 1 - x, y, isSlopeBelt(c) ? c + (c & 1 ? 1 : -1) : isSlope(c) ? BP_SLOPE_BL + ((c - BP_SLOPE_BL) ^ 1) : c, flipV(c, v), e] : [bw - 1 - x, y, isSlopeBelt(c) ? c + (c & 1 ? 1 : -1) : isSlope(c) ? BP_SLOPE_BL + ((c - BP_SLOPE_BL) ^ 1) : c, flipV(c, v)]), machines: bp.machines.map(([t, x, y, d]) => { const P = PIECE_BY_ID[t]; let nd = d; if (t === 'fan') nd = d === 0 ? 2 : d === 2 ? 0 : d; if (t === 'engine') nd = d ? 0 : 1; return [t, bw - P.bw - x, y, nd]; }), molds: bp.molds.map(([k, x, y, wl]) => [k, bw - MOLDS[k].bw - x, y, wl]), }; } function bpCost(bp) { const c = new Map(); const add = (m, n) => c.set(m, (c.get(m) || 0) + n); for (const [, , code, v] of bp.blocks) for (const [m, n] of pieceCost(code, v)) add(m, n); for (const [t] of bp.machines) for (const [m, n] of PIECE_BY_ID[t].cost) add(m, n); for (const [k, , , wl] of bp.molds) for (const [m, n] of wallCost(wl, MOLDS[k].walls)) add(m, n); return [...c.entries()]; } function pasteGhost(bp, obx, oby) { let n = 0; const rec = { t: 'paste', blocks: [], machines: [], molds: [] }; for (const [dx, dy, code, v, e] of bp.blocks) { const bx = obx + dx, by = oby + dy; if (bx < 0 || by < 0 || bx >= BW || by >= BH) continue; ghostBlocks.set(by * BW + bx, e ? [code, v, e] : [code, v]); n++; rec.blocks.push([bx, by, code]); } for (const [t, dx, dy, d] of bp.machines) { const g = { type: t, bx: obx + dx, by: oby + dy, dir: d }; ghostMachines.push(g); rec.machines.push(g); } for (const [k, dx, dy, wl] of bp.molds) { const g = { kind: k, bx: obx + dx, by: oby + dy, wall: wl }; ghostMolds.push(g); rec.molds.push(g); } undoNote(rec); return n; } // Cut: copy what you built in the box, then take it all down (refunded to the bag) function cutRegion(x0, y0, x1, y1) { const bp = copyRegion(x0, y0, x1, y1); if (!bp) return null; const bx0 = Math.max(0, Math.floor(Math.min(x0, x1) / BS)), by0 = Math.max(0, Math.floor(Math.min(y0, y1) / BS)); droppedLast = 0; for (const mm of machines.slice()) { if (!mm || PIECE_BY_ID[mm.type].fixed || siloFull(mm)) continue; // a full silo stays where it is if (bp.machines.some(([t, dx, dy]) => t === mm.type && bx0 + dx === mm.x / BS && by0 + dy === mm.y / BS)) removeMachine(mm.id, true, false); } for (const mo of molds.slice()) if (bp.molds.some(([k, dx, dy]) => k === mo.kind && bx0 + dx === mo.x / BS && by0 + dy === mo.y / BS)) removeMold(mo); for (const [dx, dy] of bp.blocks) removeBlock(bx0 + dx, by0 + dy); return bp; } // Replace: make room for a machine or mold by taking down what you built in its way. Returns '' // when the space is clear now, or why it cannot be (rock, a bar, something fixed). function footprintBlocker(bx, by, bw, bh) { for (let y = by * BS; y < (by + bh) * BS; y++) for (let x = bx * BS; x < (bx + bw) * BS; x++) { if (x < 0 || y < 0 || x >= W || y >= H) return 'Out of bounds'; const i = y * W + x; if (PH[mat[i]] === PH_SOLID && (flags[i] & F_NAT)) return 'Needs open space; clear the rock first.'; if (occ[i]) return 'A bar is in the way'; if (mach[i] && machines[mach[i]] && PIECE_BY_ID[machines[mach[i]].type].fixed) return 'That cannot be replaced'; if (mach[i] && siloFull(machines[mach[i]])) return 'Empty the silo first'; } return ''; } function clearFootprint(bx, by, bw, bh) { const why = footprintBlocker(bx, by, bw, bh); if (why) return why; droppedLast = 0; for (const mm of machines.slice()) { if (!mm) continue; const P = PIECE_BY_ID[mm.type], mbx = mm.x / BS, mby = mm.y / BS; if (mbx < bx + bw && mbx + P.bw > bx && mby < by + bh && mby + P.bh > by) removeMachine(mm.id, true, false); } for (const mo of molds.slice()) { const md = MOLDS[mo.kind], mbx = mo.x / BS, mby = mo.y / BS; if (mbx < bx + bw && mbx + md.bw > bx && mby < by + bh && mby + md.bh > by) removeMold(mo); } for (let y = by; y < by + bh; y++) for (let x = bx; x < bx + bw; x++) if (blockP[y * BW + x]) removeBlock(x, y); return ''; }