158 lines
7.2 KiB
JavaScript
158 lines
7.2 KiB
JavaScript
// Per-tick machine work: steam engines, fans, belts, filters, screw conveyors (machinesTick).
|
|
function engineIntake(mm) {
|
|
for (const i of mm.intake) {
|
|
if (mat[i] === STEAM) {
|
|
const T = temp[i];
|
|
mm.energy += 1 + Math.max(0, T - 100) / 120;
|
|
mat[i] = EMPTY; life[i] = 0; temp[i] = AMB[(i / W) | 0];
|
|
wakeIdx(i);
|
|
const ex = mm.dir === 0 ? mm.x + mm.w : mm.x - 1, ey = mm.y + mm.h - 1;
|
|
if (ex >= 0 && ex < W) {
|
|
const j = ey * W + ex;
|
|
if (mat[j] === EMPTY && !part[j]) { mat[j] = WATER; temp[j] = 92; life[j] = 0; flags[j] = 0; wakeXY(ex, ey); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function fanBlow(mm, sat) {
|
|
const dir = mm.dir; // 0 right, 1 down, 2 left, 3 up
|
|
const dx = [1, 0, -1, 0][dir], dy = [0, 1, 0, -1][dir];
|
|
const cur = curAir();
|
|
for (let lane = 0; lane < 4; lane++) {
|
|
let sx, sy;
|
|
if (dir === 0) { sx = mm.x + 4; sy = mm.y + lane; }
|
|
else if (dir === 2) { sx = mm.x - 1; sy = mm.y + lane; }
|
|
else if (dir === 1) { sx = mm.x + lane; sy = mm.y + 4; }
|
|
else { sx = mm.x + lane; sy = mm.y - 1; }
|
|
let powderDepth = 0;
|
|
const range = 28;
|
|
for (let k = 0; k < range; k++) {
|
|
const x = sx + dx * k, y = sy + dy * k;
|
|
if (x < 0 || y < 0 || x >= W || y >= H) break;
|
|
const i = y * W + x, m = mat[i], ph = PH[m];
|
|
if (ph === PH_SOLID && part[i] !== P_GRATE) break;
|
|
airS[i] = cur;
|
|
if (ph === PH_POWDER) { if (++powderDepth > 16) break; }
|
|
else if (ph === PH_GAS && k < range - 1 && rnd() < sat * 0.7 && stamp[i] !== tickStamp) {
|
|
const nx = x + dx, ny = y + dy;
|
|
if (nx >= 0 && ny >= 0 && nx < W && ny < H) {
|
|
const j = ny * W + nx;
|
|
if (mat[j] === EMPTY && passable(m, PH_GAS, j, dx, dy)) move(i, j, x, y, nx, ny);
|
|
}
|
|
}
|
|
}
|
|
wakeHeatXY(Math.min(W - 1, Math.max(0, sx + dx * 4)), Math.min(H - 1, Math.max(0, sy + dy * 4)));
|
|
}
|
|
}
|
|
function machinesTick() {
|
|
procBlocksTick();
|
|
rackTick();
|
|
if (powerDirty && tick >= nextPowerBuild) { rebuildPower(); nextPowerBuild = tick + 6; }
|
|
if (brokenMachines.length) {
|
|
const ids = brokenMachines; brokenMachines = [];
|
|
for (const id of ids) if (machines[id]) removeMachine(id, false, true);
|
|
}
|
|
for (const mm of machines) {
|
|
if (!mm) continue;
|
|
if (mm.type === 'wheel') {
|
|
const inst = mm.flow * WHEEL_K;
|
|
mm.out += (inst - mm.out) * 0.04; mm.flow = 0;
|
|
mm.angle += Math.min(0.35, mm.out * 0.012);
|
|
} else if (mm.type === 'silo') {
|
|
siloTick(mm);
|
|
} else if (mm.type === 'engine') {
|
|
engineIntake(mm);
|
|
mm.out += (mm.energy * ENGINE_K - mm.out) * 0.03; mm.energy = 0;
|
|
mm.angle += Math.min(0.4, mm.out * 0.01);
|
|
}
|
|
}
|
|
for (let n = 1; n < nets.length; n++) {
|
|
const net = nets[n];
|
|
let s = 0; for (const p of net.prods) s += p.out;
|
|
let d = net.nb * BELT_POWER + net.nt * SCREW_POWER;
|
|
for (const f of net.fans) if (f.on) d += FAN_POWER;
|
|
net.supply = s; net.demand = d;
|
|
net.sat = POWER_FREE ? 1 : d > 0 ? Math.min(1, s / d) : 0;
|
|
}
|
|
for (const mm of machines) {
|
|
if (!mm || mm.type !== 'fan') continue;
|
|
const net = nets[mm.net];
|
|
const sat = POWER_FREE ? 1 : net ? net.sat : 0;
|
|
mm.angle += sat * 0.5 * (mm.on ? 1 : 0);
|
|
if (mm.on && sat > 0.05) fanBlow(mm, sat);
|
|
// Keep the measured heat for the marker and hover panel.
|
|
if ((tick + mm.id) % 30 === 0) {
|
|
let hotT = 0;
|
|
for (let y = mm.y; y < mm.y + 4; y++) for (let x = mm.x; x < mm.x + 4; x++) { const tt = temp[y * W + x]; if (tt > hotT) hotT = tt; }
|
|
mm.hot = Math.round(hotT);
|
|
}
|
|
}
|
|
// Belts step every other tick; sloped belts carry only their finite pockets.
|
|
if (filtersDirty) rebuildFilters();
|
|
// slopes first, so a flat belt feeding one finds the room the slope has just made
|
|
slopeCleatsTick();
|
|
carryBeltsAndFilters(beltL, filterL, -1, BELTL, (tick & 1) === 0);
|
|
carryBeltsAndFilters(beltR, filterR, 1, BELTR, (tick & 1) === 0);
|
|
// screws: every loose grain inside steps one cell along, front of the line first, on the same
|
|
// beat as belts (every other tick), so a block-wide screw carries as much as a block-deep belt
|
|
if ((tick & 1) === 0) for (let p = 1; p < IS_SCREW.length; p++) {
|
|
if (!IS_SCREW[p]) continue;
|
|
const list = screwLists[p], dx = SCREW_DX[p], dy = SCREW_DY[p], flat = dy === 0;
|
|
for (let k = 0; k < list.length; k++) {
|
|
const i = list[k];
|
|
if (part[i] !== p || occ[i]) continue;
|
|
const m = mat[i];
|
|
if (m === EMPTY || PH[m] !== PH_POWDER) { if (m === EMPTY || PH[m] !== PH_SOLID) screwIntake(i, p, dx, dy); continue; }
|
|
if (stamp[i] === tickStamp) continue;
|
|
if (temp[i] > 1000 && rnd() < 0.02) {
|
|
part[i] = 0; markPow(i, false); emit({ t: 'warn', x: i % W, y: (i / W) | 0, text: 'melted' }); wakeIdx(i);
|
|
continue;
|
|
}
|
|
const s = netSat(i);
|
|
if (s <= 0 || rnd() > s) continue;
|
|
const x = i % W, y = (i / W) | 0;
|
|
// a sideways screw carries its load along the bottom: grains settle into empty lanes below
|
|
if (flat && y + 1 < H && part[i + W] === p && roomFor(i + W) && !occ[i + W]) { move(i, i + W, x, y, x, y + 1); continue; }
|
|
if (!flat && !dx) {
|
|
let own = 0, shifted = false;
|
|
const end = dy < 0 ? (y & ~3) : (y | 3);
|
|
for (let yy = y; dy < 0 ? yy >= end : yy <= end; yy += dy) if (PH[mat[yy * W + x]] === PH_POWDER) own++;
|
|
const side = ((tick >> 1) + i) & 1 ? 1 : -1;
|
|
for (let c = 0; c < 2; c++) {
|
|
const sx = c ? -side : side, xx = x + sx, j = i + sx;
|
|
if ((xx >> 2) !== (x >> 2) || part[j] !== p || !roomFor(j) || mat[j] !== EMPTY || occ[j] || stamp[j] === tickStamp) continue;
|
|
let load = 0;
|
|
for (let yy = y; dy < 0 ? yy >= end : yy <= end; yy += dy) if (PH[mat[yy * W + xx]] === PH_POWDER) load++;
|
|
// Include this grain: a strict gap prevents a sideways step being undone next beat.
|
|
if (own <= load + 1) continue;
|
|
move(i, j, x, y, xx, y); stats.moved++; shifted = true; break;
|
|
}
|
|
if (shifted) continue;
|
|
}
|
|
if (x + dx < 0 || x + dx >= W || y + dy < 0 || y + dy >= H) continue;
|
|
const dest = dx && dy ? diagonalScrewNext(i, p) : straightScrewNext(i, p);
|
|
if (dest < 0) continue;
|
|
const nx = dest % W, ny = (dest / W) | 0;
|
|
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
|
const j = dest;
|
|
if (occ[j]) continue;
|
|
const pj = part[j], screw = IS_SCREW[pj];
|
|
if (screw && !screwAccepts(pj, dx, dy)) continue;
|
|
const t = mat[j];
|
|
if (!roomFor(j)) continue;
|
|
if (pj === P_GRATE || pj === P_INTAKE || (pj === P_FILTER && !filterPassAt(m, j))) continue;
|
|
if (!screw && !pj && t === EMPTY) {
|
|
// out of the mouth: the screw spits it clear, so the next grain is never blocked by the last
|
|
launchGrain(i, nx, ny, dy && !dx ? (rnd() - 0.5) * (dy < 0 ? 0.9 : 0.1) : dx * (0.42 + rnd() * 0.16), dy ? dy * (0.5 + rnd() * 0.2) : rnd() * 0.08);
|
|
stats.moved++; if (flowOn) flowCross((y >> 2) * BW + (x >> 2), i, j); continue;
|
|
}
|
|
move(i, j, x, y, nx, ny); stats.moved++;
|
|
if (flowOn) flowCross((y >> 2) * BW + (x >> 2), i, j);
|
|
if (screw && pj !== p) {
|
|
// turning a corner: keep going across the new screw's width so every lane fills
|
|
screwSpread(j, nx, ny, dx, dy);
|
|
}
|
|
}
|
|
}
|
|
}
|