Files
deepgrain-foundry/src/sim/blueprints.js
T

106 lines
5.6 KiB
JavaScript

// 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(blockP[b] === BP_FILTER ? [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) => code === BP_BELT ? 1 - v : code === BP_FILTER ? v ^ 128 : code === BP_TUBE ? (v === 1 ? 3 : v === 3 ? 1 : v)
: 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, c, flipV(c, v), e] : [bw - 1 - x, y, 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 '';
}