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

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` });
}