158 lines
7.4 KiB
JavaScript
158 lines
7.4 KiB
JavaScript
// Machines: the power network, footprints, placing and removing machines.
|
|
// ---------- Power & machines ----------
|
|
function markPow(i, on) {
|
|
if (on) { if (!isPow[i]) { isPow[i] = 1; pcells.add(i); } }
|
|
else if (isPow[i]) { isPow[i] = 0; pcells.delete(i); netId[i] = 0; }
|
|
powerDirty = true;
|
|
}
|
|
function partLost(i) { if (chunkWork) chunkEvent('lost', i); else markPow(i, false); }
|
|
let nextPowerBuild = 0;
|
|
function rebuildPower() {
|
|
powerDirty = false;
|
|
for (const i of pcells) netId[i] = 0;
|
|
nets = [null]; beltL = []; beltR = []; screwLists = Array.from({ length: IS_SCREW.length }, () => []);
|
|
const stack = [];
|
|
for (const s of pcells) {
|
|
if (netId[s]) continue;
|
|
const id = nets.length;
|
|
if (id >= 65535) break;
|
|
const net = { id, nb: 0, nt: 0, fans: [], prods: [], supply: 0, demand: 0, sat: 0 };
|
|
nets.push(net);
|
|
netId[s] = id; stack.push(s);
|
|
while (stack.length) {
|
|
const i = stack.pop();
|
|
const m = mat[i], p = part[i];
|
|
if (m === BELTL) { net.nb++; beltL.push(i); } else if (m === BELTR) { net.nb++; beltR.push(i); }
|
|
if (IS_SCREW[p]) { net.nt++; screwLists[p].push(i); }
|
|
const x = i % W, y = (i / W) | 0;
|
|
// Corner-touching screws share power, without connecting unrelated diagonal machinery.
|
|
if (IS_SCREW[p] && SCREW_DX[p] && SCREW_DY[p]) for (let d = -1; d <= 1; d += 2) {
|
|
const xx = x + d * SCREW_DX[p], yy = y + d * SCREW_DY[p], j = yy * W + xx;
|
|
if (xx >= 0 && xx < W && yy >= 0 && yy < H && part[j] === p && isPow[j] && !netId[j]) { netId[j] = id; stack.push(j); }
|
|
}
|
|
let j;
|
|
if (x > 0) { j = i - 1; if (isPow[j] && !netId[j]) { netId[j] = id; stack.push(j); } }
|
|
if (x < W - 1) { j = i + 1; if (isPow[j] && !netId[j]) { netId[j] = id; stack.push(j); } }
|
|
if (i >= W) { j = i - W; if (isPow[j] && !netId[j]) { netId[j] = id; stack.push(j); } }
|
|
if (i < N - W) { j = i + W; if (isPow[j] && !netId[j]) { netId[j] = id; stack.push(j); } }
|
|
}
|
|
}
|
|
for (const mm of machines) {
|
|
if (!mm) continue;
|
|
mm.net = netId[mm.cell0] || 0;
|
|
const net = nets[mm.net];
|
|
if (!net) continue;
|
|
if (mm.type === 'fan') net.fans.push(mm); else if (mm.type === 'wheel' || mm.type === 'engine') net.prods.push(mm);
|
|
}
|
|
beltL.sort((a, b) => (a % W) - (b % W) || a - b); beltR.sort((a, b) => (b % W) - (a % W) || b - a);
|
|
for (const p of SCREW_PARTS) {
|
|
screwLists[p].sort((a, b) => {
|
|
if (SCREW_DX[p] && SCREW_DY[p]) return ((b % W) - (a % W)) * SCREW_DX[p];
|
|
return (SCREW_DY[p] || SCREW_DX[p]) < 0 ? a - b : b - a;
|
|
});
|
|
}
|
|
}
|
|
function netSat(i) { if (POWER_FREE) return 1; const n = nets[netId[i]]; return n ? n.sat : 0; }
|
|
|
|
// Move loose grains out of the given cells, to the nearest open cell above
|
|
// the footprint in the same column (they then fall back on top). Gas just vanishes.
|
|
function evictCells(cells, topY) {
|
|
for (const i of cells) { bgm[i] = 0; renderDirtyIdx(i); }
|
|
for (const i of cells) {
|
|
const m = mat[i], ph = PH[m];
|
|
if (m === EMPTY || ph === PH_SOLID) continue;
|
|
if (ph === PH_GAS) { mat[i] = EMPTY; life[i] = 0; continue; }
|
|
const x = i % W;
|
|
let moved = false;
|
|
for (let y = topY - 1; y >= Math.max(0, topY - 80); y--) {
|
|
const j = y * W + x;
|
|
if (mat[j] === EMPTY && !part[j] && !occ[j] && !mach[j]) { swapCells(i, j); wakeXY(x, y); moved = true; break; }
|
|
if (PH[mat[j]] === PH_SOLID) break;
|
|
}
|
|
if (!moved) { mat[i] = EMPTY; life[i] = 0; flags[i] = 0; }
|
|
wakeIdx(i);
|
|
}
|
|
}
|
|
function cellBuildable(i) { return PH[mat[i]] !== PH_SOLID && !part[i] && !mach[i] && !occ[i]; }
|
|
|
|
function machineFootprint(type, x, y) {
|
|
// [cx, cy, kind] where kind is 'hull', 'void' (inside the machine's area but open) or a part code
|
|
const out = [];
|
|
if (type === 'wheel') {
|
|
for (let dy = 0; dy < 12; dy++) for (let dx = 0; dx < 12; dx++) {
|
|
const ex = dx - 5.5, ey = dy - 5.5;
|
|
out.push([x + dx, y + dy, ex * ex + ey * ey <= 36.5 ? P_WHEEL : 'void']);
|
|
}
|
|
} else if (type === 'fan') {
|
|
for (let dy = 0; dy < 4; dy++) for (let dx = 0; dx < 4; dx++) out.push([x + dx, y + dy, 'hull']);
|
|
} else if (type === 'silo') {
|
|
// roof with a hatch, the body, a cone narrowing to the gate, and two legs; the gaps beside
|
|
// the cone are left open
|
|
for (let dy = 0; dy < 24; dy++) for (let dx = 0; dx < 12; dx++) {
|
|
let k = null;
|
|
if (dy < 2) k = dx >= 3 && dx <= 8 ? 'void' : 'hull';
|
|
else if (dy < 20) k = 'hull';
|
|
else if (dx === 0 || dx === 11) k = 'hull'; // legs
|
|
else if (dy === 20) k = dx >= 1 && dx <= 10 ? 'hull' : null;
|
|
else if (dy === 21) k = dx >= 2 && dx <= 9 ? 'hull' : null;
|
|
else k = dx >= 4 && dx <= 7 ? 'void' : dx === 3 || dx === 8 ? 'hull' : null; // the gate
|
|
if (k) out.push([x + dx, y + dy, k]);
|
|
}
|
|
} else if (type === 'dock') {
|
|
// an open-topped hopper: 2-grain walls and floor around a 20x14 bin
|
|
for (let dy = 0; dy < 16; dy++) for (let dx = 0; dx < 24; dx++) out.push([x + dx, y + dy, dx < 2 || dx >= 22 || dy >= 14 ? 'hull' : 'void']);
|
|
} else if (type === 'engine') {
|
|
for (let dy = 0; dy < 8; dy++) for (let dx = 0; dx < 12; dx++) {
|
|
if (dy === 7 && dx >= 4 && dx <= 7) out.push([x + dx, y + dy, P_INTAKE]);
|
|
else out.push([x + dx, y + dy, 'hull']);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
function canPlaceMachine(type, bx, by) {
|
|
const P = PIECE_BY_ID[type];
|
|
if (bx < 0 || by < 0 || bx + P.bw > BW || by + P.bh > BH) return 'Out of bounds';
|
|
for (const [cx, cy] of machineFootprint(type, bx * BS, by * BS)) {
|
|
const i = cy * W + cx;
|
|
if (mach[i] || part[i]) return 'Something is already built there';
|
|
if (PH[mat[i]] === PH_SOLID) return (flags[i] & F_NAT) ? 'Needs open space; clear the rock first.' : 'Something is already built there';
|
|
if (occ[i]) return 'A bar is in the way';
|
|
}
|
|
for (let y = by; y < by + P.bh; y++) for (let x = bx; x < bx + P.bw; x++) if (blockP[y * BW + x]) return 'Something is already built there';
|
|
return null;
|
|
}
|
|
function addMachine(type, bx, by, dir) {
|
|
let id = machines.indexOf(null, 1);
|
|
if (id < 0) id = machines.length;
|
|
const P = PIECE_BY_ID[type];
|
|
const x = bx * BS, y = by * BS;
|
|
const mm = { id, type, x, y, w: P.w, h: P.h, dir: dir | 0, on: true, out: 0, flow: 0, energy: 0, angle: 0, net: 0, cell0: y * W + x, intake: [] };
|
|
machines[id] = mm;
|
|
const fp = machineFootprint(type, x, y);
|
|
evictCells(fp.filter(f => f[2] === 'hull').map(([cx, cy]) => cy * W + cx), y);
|
|
for (const [cx, cy, k] of fp) {
|
|
const i = cy * W + cx;
|
|
if (k === 'hull') { mat[i] = type === 'dock' ? DOCKW : HULL; flags[i] = 0; life[i] = 0; temp[i] = AMB[cy]; if (type !== 'dock') markPow(i, true); }
|
|
else if (k !== 'void') { part[i] = k; markPow(i, true); if (k === P_INTAKE) mm.intake.push(i); }
|
|
mach[i] = id;
|
|
wakeXY(cx, cy);
|
|
}
|
|
if (type === 'wheel') mm.cell0 = (y + 6) * W + x + 6;
|
|
return mm;
|
|
}
|
|
function removeMachine(id, refund, scrap) {
|
|
const mm = machines[id];
|
|
if (!mm) return;
|
|
for (const [cx, cy, k] of machineFootprint(mm.type, mm.x, mm.y)) {
|
|
const i = cy * W + cx;
|
|
if (mach[i] !== id) continue;
|
|
mach[i] = 0; markPow(i, false);
|
|
if (k === 'hull') { if (mat[i] === HULL || mat[i] === DOCKW) { mat[i] = scrap ? SCRCU : EMPTY; flags[i] = 0; } }
|
|
else if (k !== 'void') part[i] = 0;
|
|
wakeXY(cx, cy);
|
|
}
|
|
machines[id] = null;
|
|
if (refund) { refundAt(PIECE_BY_ID[mm.type].cost, 1, mm.x + mm.w / 2, mm.y + mm.h / 2); undoNote({ t: 'mach-', type: mm.type, bx: mm.x / BS, by: mm.y / BS, dir: mm.dir, on: mm.on }); }
|
|
if (scrap) emit({ t: 'warn', x: mm.x + mm.w / 2, y: mm.y, text: `${PIECE_BY_ID[mm.type].name} destroyed` });
|
|
}
|