Diagonal screw tubes, laid by a 45° drag (t35)
- Tubes run in eight directions: a drag at about 45° lays a diagonal run, R cycles all eight. - Diagonal runs carry grains at the straight rate (120 grains/s), join straight tubes and belts, keep grains in at the corners, pass water, and carry bars level. - New part ids 11-14 (saved ids are never renumbered); tube checks use an IS_TUBE table. - New test/t_diag_tubes.js (85 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ function drawFlowArrows() {
|
||||
const b = by * BW + bx, code = blockP[b];
|
||||
if (code !== BP_BELT && code !== BP_TUBE && code !== BP_PUMP && code !== BP_OUTLET) continue;
|
||||
const vv = blockV[b];
|
||||
const [dx, dy] = code === BP_BELT ? (vv ? [1, 0] : [-1, 0]) : code === BP_PUMP ? PUMP_V[vv & 3] : code === BP_OUTLET ? OUTLET_V[vv & 3] : [[0, -1], [1, 0], [0, 1], [-1, 0]][vv & 3];
|
||||
const [dx, dy] = code === BP_BELT ? (vv ? [1, 0] : [-1, 0]) : code === BP_PUMP ? PUMP_V[vv & 3] : code === BP_OUTLET ? OUTLET_V[vv & 3] : TUBE_DIRS[vv & 7];
|
||||
const c = worldToScreenPx(bx * BS + BS / 2, by * BS + BS / 2);
|
||||
ctx.fillStyle = 'rgba(14,12,11,0.55)'; ctx.beginPath(); ctx.arc(c.x, c.y, r * 1.35, 0, Math.PI * 2); ctx.fill();
|
||||
ctx.strokeStyle = code === BP_TUBE ? '#ffc58a' : '#e7dccb'; ctx.lineWidth = Math.max(1.2, r * 0.32);
|
||||
|
||||
@@ -245,11 +245,16 @@ void main() {
|
||||
C = mix(C, bc, inside);
|
||||
}
|
||||
}
|
||||
else if (nc >= 9 && nc <= 12) {
|
||||
else if ((nc >= 9 && nc <= 12) || (nc >= 19 && nc <= 22)) {
|
||||
// screw tube: a copper pipe; what is inside shows through, and the screw's flights turn
|
||||
bool vert = nc == 9 || nc == 11;
|
||||
float sp2 = nc == 9 ? -1.0 : nc == 10 ? 1.0 : nc == 11 ? 1.0 : -1.0;
|
||||
float a = (vert ? fract(w.x * 0.25) : fract(w.y * 0.25)) * 4.0, along = vert ? w.y : w.x;
|
||||
if (nc >= 19) {
|
||||
vec2 d = nc == 19 ? vec2(1.0, -1.0) : nc == 20 ? vec2(1.0, 1.0) : nc == 21 ? vec2(-1.0, 1.0) : vec2(-1.0, -1.0);
|
||||
a = fract(dot(w, vec2(-d.y, d.x)) * 0.25) * 4.0;
|
||||
along = dot(w, d); sp2 = 1.0;
|
||||
}
|
||||
float wall = 0.62;
|
||||
vec3 tc;
|
||||
if (a < wall || a > 4.0 - wall) {
|
||||
|
||||
+9
-7
@@ -3,7 +3,8 @@ function fogColor(c, k) { return (c & 0xff000000) | (((c >>> 16 & 255) * k) << 1
|
||||
// drawWorld: the per-grain pixel buffer (pixel look), also the input of the smooth look.
|
||||
// ---------- World pixels ----------
|
||||
// smooth-look class of a screw tube cell by direction: 9 up, 10 right, 11 down, 12 left
|
||||
const TUBE_CLASS = new Uint8Array(9); TUBE_CLASS[1] = 9; TUBE_CLASS[4] = 10; TUBE_CLASS[2] = 11; TUBE_CLASS[3] = 12;
|
||||
const TUBE_CLASS = new Uint8Array(15); TUBE_CLASS[1] = 9; TUBE_CLASS[4] = 10; TUBE_CLASS[2] = 11; TUBE_CLASS[3] = 12;
|
||||
for (let p = P_TUR; p <= P_TUL; p++) TUBE_CLASS[p] = 19 + p - P_TUR;
|
||||
function drawWorld() {
|
||||
const bw = R.bw, bh = R.bh, px = R.px;
|
||||
R.vx0 = Math.floor(R.camX); R.vy0 = Math.floor(R.camY);
|
||||
@@ -155,13 +156,14 @@ function drawWorld() {
|
||||
}
|
||||
// field parts drawn over contents
|
||||
const p = part[i];
|
||||
if (p && sm && (p <= P_TR || p === P_FILTER)) { if (m === EMPTY || PH[m] === PH_GAS) { r = 0; g = 0; b = 0; } else { r = Math.max(1, r); } }
|
||||
if (p && sm && (IS_TUBE[p] || p === P_FILTER)) { if (m === EMPTY || PH[m] === PH_GAS) { r = 0; g = 0; b = 0; } else { r = Math.max(1, r); } }
|
||||
else if (p) {
|
||||
if (p <= P_TR) {
|
||||
if (IS_TUBE[p]) {
|
||||
const on = netSat(i) > 0;
|
||||
const vert = p === P_TU || p === P_TD;
|
||||
const axis = vert ? y : x, across = vert ? x & 3 : y & 3;
|
||||
const dir = (p === P_TU || p === P_TL) ? 1 : -1;
|
||||
const vert = p === P_TU || p === P_TD, diagonal = TDX[p] && TDY[p];
|
||||
const axis = diagonal ? x * TDX[p] + y * TDY[p] : vert ? y : x;
|
||||
const across = diagonal ? ((x * -TDY[p] + y * TDX[p]) & 3) : vert ? x & 3 : y & 3;
|
||||
const dir = diagonal ? -1 : (p === P_TU || p === P_TL) ? 1 : -1;
|
||||
if (across === 0 || across === 3) { // copper casing on the two long sides; its load shows through
|
||||
if (m === EMPTY) { r = 150; g = 96; b = 64; } else { r = r * 0.72 + 44; g = g * 0.72 + 26; b = b * 0.72 + 14; }
|
||||
if ((axis & 3) === 0) { r -= 24; g -= 18; b -= 12; }
|
||||
@@ -217,7 +219,7 @@ function drawWorld() {
|
||||
// 4 built piece (drawn crisp), 5 smoke and flame, 6 built wall block (drawn as a tile)
|
||||
let cl = 0;
|
||||
const pt = p === P_PIPE && !pipeBodyCell(x, y) ? 0 : p; // the open parts of a pipe block show what is in them
|
||||
if (pt >= P_TU && pt <= P_TR) cl = TUBE_CLASS[pt];
|
||||
if (IS_TUBE[pt]) cl = TUBE_CLASS[pt];
|
||||
else if (p === P_FILTER) {
|
||||
// filter: 13 carries left, 14 right; the tree buffer carries its colour tag (first material) and mode
|
||||
const fb = (y >> 2) * BW + (x >> 2), fc = filterCfg.get(fb), fm = fc && fc.m.length ? fc.m[0] : 0;
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ function placeBlock(code, v, bx, by, free, extra) {
|
||||
else if (isProcCode(code)) setCell(i, code === BP_GRIND ? GRINDER : code === BP_PRESS ? PRESSB : SAWB, AMB[y], 0);
|
||||
else if (code === BP_GATE) setCell(i, GATE, AMB[y], 0);
|
||||
else if (code === BP_GRATE) { part[i] = P_GRATE; wakeXY(x, y); }
|
||||
else if (code === BP_TUBE) { part[i] = [P_TU, P_TR, P_TD, P_TL][v & 3]; markPow(i, true); wakeXY(x, y); }
|
||||
else if (code === BP_TUBE) { part[i] = TUBE_PARTS[v & 7]; markPow(i, true); wakeXY(x, y); }
|
||||
else if (code === BP_FILTER) { part[i] = P_FILTER; wakeXY(x, y); }
|
||||
else if (code === BP_PIPE) { part[i] = P_PIPE; wakeXY(x, y); }
|
||||
else if (isPipeCode(code)) setCell(i, PIPE, AMB[y], 0);
|
||||
@@ -90,7 +90,7 @@ function blockIntegrity(b) {
|
||||
else if (isProcCode(code)) { if (m === (code === BP_GRIND ? GRINDER : code === BP_PRESS ? PRESSB : SAWB)) n++; }
|
||||
else if (code === BP_GATE) { if (m === GATE || p === P_GOPEN) n++; }
|
||||
else if (code === BP_GRATE) { if (p === P_GRATE) n++; }
|
||||
else if (code === BP_TUBE) { if (p >= P_TU && p <= P_TR) n++; }
|
||||
else if (code === BP_TUBE) { if (IS_TUBE[p]) n++; }
|
||||
else if (code === BP_FILTER) { if (p === P_FILTER) n++; }
|
||||
else if (isPipeCode(code)) { if (m === PIPE || p === P_PIPE) n++; }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ function copyRegion(x0, y0, x1, y1) {
|
||||
}
|
||||
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)
|
||||
const flipV = (code, v) => code === BP_BELT ? 1 - v : code === BP_FILTER ? v ^ 128 : code === BP_TUBE ? [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,
|
||||
|
||||
+3
-3
@@ -39,7 +39,7 @@ function enter(m, ph, j, dx, dy) {
|
||||
if (occ[j]) return 0;
|
||||
const p = part[j];
|
||||
if (p !== 0) {
|
||||
if (p <= P_TR) { if (ph === PH_POWDER && TDX[p] === -dx && TDY[p] === -dy) return 0; } // liquids and gases flow through freely
|
||||
if (IS_TUBE[p]) { if (ph === PH_POWDER && TDX[p] === -dx && TDY[p] === -dy) return 0; } // liquids and gases flow through freely
|
||||
else if (p === P_GRATE) { if (ph === PH_POWDER || m === MSLAG) return 0; } // thick slag does not run through a grate
|
||||
else if (p === P_INTAKE) { if (ph !== PH_GAS) return 0; }
|
||||
else if (p === P_FILTER) { if (ph === PH_POWDER && !filterPassAt(m, j)) return 0; }
|
||||
@@ -92,7 +92,7 @@ function passable(m, ph, j, dx, dy) {
|
||||
if (occ[j]) return false;
|
||||
const p = part[j];
|
||||
if (p !== 0) {
|
||||
if (p <= P_TR) { if (ph === PH_POWDER && TDX[p] === -dx && TDY[p] === -dy) return false; }
|
||||
if (IS_TUBE[p]) { if (ph === PH_POWDER && TDX[p] === -dx && TDY[p] === -dy) return false; }
|
||||
else if (p === P_GRATE) { if (ph === PH_POWDER || m === MSLAG) return false; }
|
||||
else if (p === P_INTAKE) { if (ph !== PH_GAS) return false; }
|
||||
else if (p === P_FILTER) { if (ph === PH_POWDER && !filterPassAt(m, j)) return false; }
|
||||
@@ -198,7 +198,7 @@ function simMove() {
|
||||
if (stamp[i] === tickStamp) continue;
|
||||
if (ph === PH_SOLID) continue;
|
||||
const p = part[i];
|
||||
if (p >= P_TU && p <= P_TR && ph === PH_POWDER) continue; // a screw tube moves its own grains
|
||||
if (IS_TUBE[p] && ph === PH_POWDER) continue; // a screw tube moves its own grains
|
||||
if (ph === PH_POWDER) powder(i, x, y, m); else if (ph === PH_LIQUID) liquid(i, x, y, m); else gas(i, x, y, m);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-4
@@ -108,10 +108,11 @@ function tubeSpread(c, x, y, sx, sy, intake = false) {
|
||||
function tubeIntake(i, p, dx, dy) {
|
||||
if (occ[i]) return;
|
||||
const x = i % W, y = (i / W) | 0;
|
||||
const cands = [[-dx, -dy], [dy, dx], [-dy, -dx]];
|
||||
const start = (tick + i) % 3;
|
||||
for (let c = 0; c < 3; c++) {
|
||||
const [ox, oy] = cands[(start + c) % 3];
|
||||
const k = (start + c) % 3, diagonal = dx && dy;
|
||||
const ox = diagonal ? (k === 1 ? 0 : -dx) : k === 0 ? -dx : k === 1 ? dy : -dy;
|
||||
const oy = diagonal ? (k === 0 ? 0 : -dy) : k === 0 ? -dy : k === 1 ? dx : -dx;
|
||||
const nx = x + ox, ny = y + oy;
|
||||
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||
const j = ny * W + nx, m = mat[j];
|
||||
@@ -119,10 +120,28 @@ function tubeIntake(i, p, dx, dy) {
|
||||
if (PH[m] !== PH_POWDER) continue; // the screw only takes loose material; liquids just flow through
|
||||
if (stamp[i] === tickStamp) return;
|
||||
const pj = part[j];
|
||||
if (pj >= P_TU && pj <= P_TR) continue; // other tube cells move their own contents
|
||||
if (IS_TUBE[pj]) continue; // other tube cells move their own contents
|
||||
if (pj === P_GRATE || pj === P_FILTER) continue;
|
||||
move(j, i, nx, ny, x, y);
|
||||
if (ox !== -dx || oy !== -dy) tubeSpread(i, x, y, -ox, -oy, true);
|
||||
if (diagonal) tubeSpread(i, x, y, 0, (y & 3) < 2 ? 1 : -1, true);
|
||||
else if (ox !== -dx || oy !== -dy) tubeSpread(i, x, y, -ox, -oy, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Four lanes cross a block in four beats; the casing carries every lane across the corner.
|
||||
// Prefer the next diagonal block, then either straight join, then the open head.
|
||||
function diagonalTubeNext(i, p) {
|
||||
const x = i % W, y = (i / W) | 0, dx = TDX[p], dy = TDY[p];
|
||||
const xx = x + dx, bx = x & ~3, by = y & ~3;
|
||||
if (xx < 0 || xx >= W) return -1;
|
||||
if ((xx >> 2) === (x >> 2)) return i + dx;
|
||||
const yy = y + dy * BS;
|
||||
if (yy >= 0 && yy < H && IS_TUBE[part[yy * W + xx]]) return yy * W + xx;
|
||||
if (IS_TUBE[part[y * W + xx]]) return y * W + xx;
|
||||
if (dy > 0 && yy < H && (mat[yy * W + xx] === BELTL || mat[yy * W + xx] === BELTR)) return ((yy & ~3) - 1) * W + xx;
|
||||
const hy = by + (dy > 0 ? BS : -1), hx = bx + (y & 3);
|
||||
if (hy >= 0 && hy < H && IS_TUBE[part[hy * W + hx]]) return hy * W + hx;
|
||||
// An open mouth discharges along the diagonal, clear of the last block.
|
||||
return yy >= 0 && yy < H ? yy * W + xx : -1;
|
||||
}
|
||||
|
||||
+31
-7
@@ -5,7 +5,7 @@
|
||||
// its cells as solid (occ).
|
||||
const BAR_W = 4, BAR_H = 2;
|
||||
let entCurM = 0; // the bar being moved, so a filter can let its own kind of bar through
|
||||
function entSolidCell(j) { const m = mat[j], p = part[j]; return occ[j] || PH[m] === PH_SOLID || PH[m] === PH_POWDER || (p !== 0 && (p <= P_GRATE || (p === P_FILTER && !filterPassAt(entCurM, j)))); }
|
||||
function entSolidCell(j) { const m = mat[j], p = part[j]; return occ[j] || PH[m] === PH_SOLID || PH[m] === PH_POWDER || (p !== 0 && (IS_TUBE[p] || p === P_GRATE || (p === P_FILTER && !filterPassAt(entCurM, j)))); }
|
||||
function entSupportCount(x, y) {
|
||||
if (y + BAR_H >= H) return BAR_W;
|
||||
let n = 0;
|
||||
@@ -42,23 +42,27 @@ function removeEnt(e) { entMark(e, 0); const k = ents.indexOf(e); if (k >= 0) en
|
||||
function entTubeAt(x, y) {
|
||||
if (x < 0 || y < 0 || x + BAR_W > W || y + BAR_H > H) return 0;
|
||||
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) {
|
||||
const p = part[yy * W + xx]; if (p < P_TU || p > P_TR) return 0;
|
||||
const p = part[yy * W + xx]; if (!IS_TUBE[p]) return 0;
|
||||
}
|
||||
return part[(y + 1) * W + x + 1];
|
||||
}
|
||||
function entTubeFree(x, y, tubeOnly = false) {
|
||||
if (x < 0 || y < 0 || x + BAR_W > W || y + BAR_H > H) return false;
|
||||
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) {
|
||||
const j = yy * W + xx, p = part[j], tube = p >= P_TU && p <= P_TR;
|
||||
const j = yy * W + xx, p = part[j], tube = IS_TUBE[p];
|
||||
// gas and liquid don't stop a bar, in a tube or in the open
|
||||
if (occ[j] || (mat[j] !== EMPTY && PH[mat[j]] !== PH_GAS && PH[mat[j]] !== PH_LIQUID) || (tubeOnly ? !tube : !tube && entSolidCell(j))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Check the swept rectangle too: a snap must never jump through a grain or another bar.
|
||||
function entTubeMove(e, x, y, tubeOnly = false) {
|
||||
function entTubeMove(e, x, y, tubeOnly = false, mouth = false) {
|
||||
if (!entTubeFree(x, y, tubeOnly)) return false;
|
||||
for (let yy = Math.min(e.iy, y); yy <= Math.max(e.iy, y); yy++)
|
||||
if (mouth) {
|
||||
// Lift clear of the receiving belt before sliding over it; check both legs before moving.
|
||||
for (let yy = Math.min(e.iy, y); yy <= Math.max(e.iy, y); yy++) if (!entTubeFree(e.ix, yy)) return false;
|
||||
for (let xx = Math.min(e.ix, x); xx <= Math.max(e.ix, x); xx++) if (!entTubeFree(xx, y)) return false;
|
||||
} else for (let yy = Math.min(e.iy, y); yy <= Math.max(e.iy, y); yy++)
|
||||
for (let xx = Math.min(e.ix, x); xx <= Math.max(e.ix, x); xx++) if (!entTubeFree(xx, yy)) return false;
|
||||
e.x = e.ix = x; e.y = e.iy = y; e.vx = e.vy = 0; return true;
|
||||
}
|
||||
@@ -72,7 +76,7 @@ function entTubeEnter(e, dx, dy) {
|
||||
const i = entTubeEdge(e, dx, dy, k);
|
||||
if (i < 0) continue;
|
||||
const xx = i % W, yy = (i / W) | 0;
|
||||
const p = part[i]; if (p < P_TU || p > P_TR || (!dx && TDX[p]) || dx * TDX[p] + dy * TDY[p] < 0) continue;
|
||||
const p = part[i]; if (!IS_TUBE[p] || (!dx && TDX[p]) || dx * TDX[p] + dy * TDY[p] < 0) continue;
|
||||
// Vertical screws require the full width; horizontal screws use the nearest two-row lane.
|
||||
const x = (xx & ~3), y = entTubeLane(yy, e.iy);
|
||||
if (entTubeMove(e, x, TDX[p] || dy ? y : e.iy, true)) return true;
|
||||
@@ -84,13 +88,14 @@ function entTubeTick(e, p) {
|
||||
const ci = (e.iy + 1) * W + e.ix + 1;
|
||||
if ((tick & 1) || netSat(ci) <= 0 || rnd() > netSat(ci)) return;
|
||||
const dx = TDX[p], dy = TDY[p];
|
||||
if (dx && dy) { entDiagonalTubeTick(e, p); return; }
|
||||
let x = e.ix + dx, y = e.iy + dy, next = 0, ax = 0, ay = 0;
|
||||
for (let k = 0; k < (dx ? BAR_H : BAR_W); k++) {
|
||||
const i = entTubeEdge(e, dx, dy, k);
|
||||
if (i < 0) return;
|
||||
const xx = i % W, yy = (i / W) | 0;
|
||||
const q = part[i];
|
||||
if (q >= P_TU && q <= P_TR && q !== p) { next = q; ax = xx; ay = yy; break; }
|
||||
if (IS_TUBE[q] && q !== p) { next = q; ax = xx; ay = yy; break; }
|
||||
}
|
||||
if (next) {
|
||||
if (TDX[next] === -dx && TDY[next] === -dy) return;
|
||||
@@ -105,6 +110,25 @@ function entTubeTick(e, p) {
|
||||
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) if (part[yy * W + xx]) return;
|
||||
if (entTubeMove(e, x, y)) { e.vx = dx ? dx * 0.5 : (rnd() - 0.5) * 0.4; e.vy = dy * 0.6; stats.moved++; }
|
||||
}
|
||||
// A horizontal bar stays whole: one diagonal block per four screw beats.
|
||||
function entDiagonalTubeTick(e, p) {
|
||||
if (tick & 7) return;
|
||||
const dx = TDX[p], dy = TDY[p], bx = e.ix & ~3, by = e.iy & ~3, lane = e.iy & 3;
|
||||
for (const [ox, oy] of [[dx, dy], [dx, 0], [0, dy]]) {
|
||||
const x = bx + ox * BS, y = by + oy * BS + lane, q = entTubeAt(x, y);
|
||||
if (!q) continue;
|
||||
if (TDX[q] * dx + TDY[q] * dy < 0) return;
|
||||
if (entTubeMove(e, x, y, true)) stats.moved++;
|
||||
return;
|
||||
}
|
||||
const x = bx + dx * BS;
|
||||
let y = by + dy * BS + lane;
|
||||
const belt = y >= 0 && y < H ? mat[y * W + x] : EMPTY;
|
||||
if (dy > 0 && (belt === BELTL || belt === BELTR)) y = (y & ~3) - BAR_H;
|
||||
if (!entTubeFree(x, y)) return;
|
||||
for (let yy = y; yy < y + BAR_H; yy++) for (let xx = x; xx < x + BAR_W; xx++) if (part[yy * W + xx]) return;
|
||||
if (entTubeMove(e, x, y, false, dy < 0)) { e.vx = dx * 0.5; e.vy = dy * 0.6; stats.moved++; }
|
||||
}
|
||||
function entTick() {
|
||||
for (let k = ents.length - 1; k >= 0; k--) {
|
||||
const e = ents[k];
|
||||
|
||||
+10
-6
@@ -107,7 +107,8 @@ function machinesTick() {
|
||||
}
|
||||
// tubes: 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 = P_TU; p <= P_TR; p++) {
|
||||
if ((tick & 1) === 0) for (let p = 1; p < IS_TUBE.length; p++) {
|
||||
if (!IS_TUBE[p]) continue;
|
||||
const list = tubeLists[p], dx = TDX[p], dy = TDY[p], dj = dy * W + dx, flat = dy === 0;
|
||||
for (let k = 0; k < list.length; k++) {
|
||||
const i = list[k];
|
||||
@@ -124,7 +125,7 @@ function machinesTick() {
|
||||
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) {
|
||||
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++;
|
||||
@@ -140,18 +141,21 @@ function machinesTick() {
|
||||
}
|
||||
if (shifted) continue;
|
||||
}
|
||||
const nx = x + dx, ny = y + dy;
|
||||
if (x + dx < 0 || x + dx >= W || y + dy < 0 || y + dy >= H) continue;
|
||||
const dest = dx && dy ? diagonalTubeNext(i, p) : i + dj;
|
||||
if (dest < 0) continue;
|
||||
const nx = dest % W, ny = (dest / W) | 0;
|
||||
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
||||
const j = i + dj;
|
||||
const j = dest;
|
||||
if (occ[j]) continue;
|
||||
const pj = part[j], tube = pj >= P_TU && pj <= P_TR;
|
||||
const pj = part[j], tube = IS_TUBE[pj];
|
||||
if (tube && TDX[pj] === -dx && TDY[pj] === -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 (!tube && !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 ? (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);
|
||||
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++; continue;
|
||||
}
|
||||
move(i, j, x, y, nx, ny); stats.moved++;
|
||||
|
||||
+14
-5
@@ -10,7 +10,7 @@ let nextPowerBuild = 0;
|
||||
function rebuildPower() {
|
||||
powerDirty = false;
|
||||
for (const i of pcells) netId[i] = 0;
|
||||
nets = [null]; beltL = []; beltR = []; tubeLists = [[], [], [], [], []];
|
||||
nets = [null]; beltL = []; beltR = []; tubeLists = Array.from({ length: IS_TUBE.length }, () => []);
|
||||
const stack = [];
|
||||
for (const s of pcells) {
|
||||
if (netId[s]) continue;
|
||||
@@ -23,8 +23,13 @@ function rebuildPower() {
|
||||
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 (p >= P_TU && p <= P_TR) { net.nt++; tubeLists[p].push(i); }
|
||||
const x = i % W;
|
||||
if (IS_TUBE[p]) { net.nt++; tubeLists[p].push(i); }
|
||||
const x = i % W, y = (i / W) | 0;
|
||||
// Corner-touching screws share power, without connecting unrelated diagonal machinery.
|
||||
if (IS_TUBE[p] && TDX[p] && TDY[p]) for (let d = -1; d <= 1; d += 2) {
|
||||
const xx = x + d * TDX[p], yy = y + d * TDY[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); } }
|
||||
@@ -40,8 +45,12 @@ function rebuildPower() {
|
||||
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 - b); beltR.sort((a, b) => b - a);
|
||||
tubeLists[P_TU].sort((a, b) => a - b); tubeLists[P_TD].sort((a, b) => b - a);
|
||||
tubeLists[P_TL].sort((a, b) => a - b); tubeLists[P_TR].sort((a, b) => b - a);
|
||||
for (const p of TUBE_PARTS) {
|
||||
tubeLists[p].sort((a, b) => {
|
||||
if (TDX[p] && TDY[p]) return ((b % W) - (a % W)) * TDX[p];
|
||||
return (TDY[p] || TDX[p]) < 0 ? a - b : b - a;
|
||||
});
|
||||
}
|
||||
}
|
||||
function netSat(i) { if (POWER_FREE) return 1; const n = nets[netId[i]]; return n ? n.sat : 0; }
|
||||
|
||||
|
||||
@@ -28,8 +28,13 @@ const SOLIDMETAL = new Uint8Array(NMAT); for (const m of [COPPER, IRON, STEEL, T
|
||||
|
||||
// ---------- Flags & parts ----------
|
||||
const F_NAT = 1, F_CAST = 2, F_BURN = 4;
|
||||
const P_NONE = 0, P_TU = 1, P_TD = 2, P_TL = 3, P_TR = 4, P_GRATE = 5, P_GOPEN = 6, P_WHEEL = 7, P_INTAKE = 8, P_FILTER = 9, P_PIPE = 10;
|
||||
const TDX = [0, 0, 0, -1, 1], TDY = [0, -1, 1, 0, 0];
|
||||
const P_NONE = 0, P_TU = 1, P_TD = 2, P_TL = 3, P_TR = 4, P_GRATE = 5, P_GOPEN = 6, P_WHEEL = 7, P_INTAKE = 8, P_FILTER = 9, P_PIPE = 10, P_TUR = 11, P_TDR = 12, P_TDL = 13, P_TUL = 14;
|
||||
const TUBE_PARTS = [P_TU, P_TR, P_TD, P_TL, P_TUR, P_TDR, P_TDL, P_TUL];
|
||||
const TUBE_DIRS = [[0, -1], [1, 0], [0, 1], [-1, 0], [1, -1], [1, 1], [-1, 1], [-1, -1]];
|
||||
const IS_TUBE = new Uint8Array(15), TDX = new Int8Array(15), TDY = new Int8Array(15);
|
||||
for (let v = 0; v < TUBE_PARTS.length; v++) {
|
||||
const p = TUBE_PARTS[v]; IS_TUBE[p] = 1; TDX[p] = TUBE_DIRS[v][0]; TDY[p] = TUBE_DIRS[v][1];
|
||||
}
|
||||
|
||||
// ---------- Tool tiers ----------
|
||||
// reach: how far from the bot the drill works. Harder rock takes longer, and
|
||||
|
||||
+2
-2
@@ -55,8 +55,8 @@ const PIECES = [
|
||||
desc: 'A half-block with a flat 45° face. Drag diagonally to make a funnel. R picks the material. T flips a dragged slope; without a drag, T cycles its four corners. Costs and returns 8 grains or 1 bar.' },
|
||||
{ id: 'belt', code: BP_BELT, name: 'Belt', kind: 'block', cost: [[CASH, 12]], dirs: ['left', 'right'],
|
||||
desc: 'Carries loose grains and bars resting on top of it, left or right, in a layer up to a block deep. Water and other liquids drain through it.' },
|
||||
{ id: 'tube', code: BP_TUBE, name: 'Screw tube', kind: 'block', cost: [[CASH, 25]], dirs: ['up', 'right', 'down', 'left'],
|
||||
desc: 'A turning screw that draws in loose grains and whole bars and pushes them along, four grains wide. Water and other liquids run straight through it; the screw never carries them. Stack them pointing up to lift ore out of a mine, and turn the top one sideways to unload. Copper softens around 1000°C.' },
|
||||
{ id: 'tube', code: BP_TUBE, name: 'Screw tube', kind: 'block', cost: [[CASH, 25]], dirs: ['up', 'right', 'down', 'left', 'up-right', 'down-right', 'down-left', 'up-left'],
|
||||
desc: 'A turning screw that draws in loose grains and whole bars and pushes them along, four grains wide. Water and other liquids run straight through it; the screw never carries them. Drag a line to set its direction, including diagonals. R turns a single tube. Join either end to tubes or belts. Copper softens around 1000°C.' },
|
||||
{ id: 'filter', code: BP_FILTER, name: 'Filter', kind: 'block', cost: [[CASH, 40]], dirs: ['left', 'right'], filter: true,
|
||||
desc: 'A stretch of belt with a mesh in it. Put it in a line of belts and it joins them as one belt. Pick materials and a mode: drop them through and carry everything else along, or carry them and drop the rest. Liquids drain through.' },
|
||||
{ id: 'shaft', code: BP_SHAFT, name: 'Shaft', kind: 'block', cost: [[LWOOD, 8]], hidden: POWER_FREE,
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ function deserializeWorld(meta, buffers) {
|
||||
const p = savedPart[i];
|
||||
if (p === P_WHEEL || p === P_INTAKE) continue;
|
||||
part[i] = p;
|
||||
if (p >= P_TU && p <= P_TR) markPow(i, true);
|
||||
if (IS_TUBE[p]) markPow(i, true);
|
||||
}
|
||||
for (let i = 0; i < N; i++) { const m = mat[i]; if (m === BELTL || m === BELTR || m === SHAFT) markPow(i, true); }
|
||||
// Old machines removed from the game are refunded.
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ function toolVacuum(cx, cy, r, pick) {
|
||||
const m = mat[i], ph = PH[m];
|
||||
if (!(ph === PH_POWDER || (ph === PH_LIQUID && player.flask))) return;
|
||||
if (!pick(m)) return;
|
||||
if (part[i] >= P_TU && part[i] <= P_TR) return;
|
||||
if (IS_TUBE[part[i]]) return;
|
||||
if (bagTotal >= bagCap()) { full = true; return false; }
|
||||
if (!first) first = m;
|
||||
bagAdd(m, 1); setCell(i, EMPTY, AMB[y], 0); got++;
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ let awake, hot, hotLvl, powderArea, roofDirty;
|
||||
let loadWakeOrder = null, loadWakePending = null, loadWakeIndex = 0;
|
||||
let tick = 0, tickStamp = 1;
|
||||
let machines = [null], molds = [], nets = [null], pcells = new Set(), powerDirty = true;
|
||||
let beltL = [], beltR = [], tubeLists = [[], [], [], [], []];
|
||||
let beltL = [], beltR = [], tubeLists = Array.from({ length: IS_TUBE.length }, () => []);
|
||||
let brokenMachines = [];
|
||||
let ghostMachines = [], ghostBlocked = new Set();
|
||||
const EV = [];
|
||||
@@ -55,7 +55,7 @@ function alloc(w, h) {
|
||||
roofDirty = new Uint8Array(CW).fill(1);
|
||||
powderArea = new Uint8Array(NCH).fill(1); // unknown areas get one visit before being ruled out
|
||||
machines = [null]; molds = []; nets = [null]; pcells = new Set(); powerDirty = true;
|
||||
beltL = []; beltR = []; tubeLists = [[], [], [], [], []]; brokenMachines = [];
|
||||
beltL = []; beltR = []; tubeLists = Array.from({ length: IS_TUBE.length }, () => []); brokenMachines = [];
|
||||
ghostMachines = []; ghostBlocked = new Set();
|
||||
tick = 0; tickStamp = 1; drillPulses = []; lastPulseTick = -99; undoStack = []; undoRec = null;
|
||||
pipeNets = []; pipeNetAt = null; pipesDirty = true;
|
||||
|
||||
+3
-2
@@ -61,7 +61,7 @@ function pickUnderCursor() {
|
||||
const P = PIECE_BY_CODE[code]; pid = P.id;
|
||||
if (isWall(code)) { GAME.wallMat = v; if (isSlope(code)) GAME.pieceDir.slope = code - BP_SLOPE_BL; }
|
||||
else if (P.filter) { GAME.filterCfg = blockP[bi] ? filterInfo(bi) : cleanFilterCfg(ghost[2]); GAME.pieceDir[P.id] = v >> 7; }
|
||||
else if (P.dirs) GAME.pieceDir[P.id] = v & 3;
|
||||
else if (P.dirs) GAME.pieceDir[P.id] = v % P.dirs.length;
|
||||
}
|
||||
if (pid) {
|
||||
const P = PIECE_BY_ID[pid];
|
||||
@@ -99,6 +99,7 @@ function dragDirFor(P, a, b) {
|
||||
if (!a || !P.dirs || !(P.id === 'belt' || P.id === 'tube' || P.filter)) return -1;
|
||||
const dx = b.bx - a.bx, dy = b.by - a.by;
|
||||
if (!dx && !dy) return -1;
|
||||
if (P.id === 'tube' && diagonalDrag(dx, dy)) return dx > 0 ? (dy < 0 ? 4 : 5) : (dy > 0 ? 6 : 7);
|
||||
if (P.id === 'tube') return Math.abs(dx) >= Math.abs(dy) ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0);
|
||||
return Math.abs(dx) >= Math.abs(dy) ? (dx > 0 ? 1 : 0) : -1;
|
||||
}
|
||||
@@ -162,7 +163,7 @@ function primaryRelease(shift) {
|
||||
}
|
||||
if (t === 'build' && GAME.drag) {
|
||||
const P = piece(), b = aimBlock(), code = pieceCode(P, b);
|
||||
const list = shift ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code));
|
||||
const list = shift ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || code === BP_TUBE);
|
||||
const dd = dragDirFor(P, GAME.drag, b);
|
||||
if (dd >= 0) GAME.pieceDir[P.id] = dd;
|
||||
const v = pieceV(P), ex = pieceExtra(P);
|
||||
|
||||
@@ -47,7 +47,7 @@ function buildVariantChips(P) {
|
||||
const dir = P.dirs ? P.dirs[GAME.pieceDir[P.id] || 0] : null;
|
||||
// while dragging a line of belts, filters or tubes, the direction is the drag's
|
||||
const dragging = GAME.drag && GAME.dragDir >= 0 && P.dirs;
|
||||
const glyph = d => ({ left: '←', right: '→', up: '↑', down: '↓' })[d] || '';
|
||||
const glyph = d => ({ left: '←', right: '→', up: '↑', down: '↓', 'up-right': '↗', 'down-right': '↘', 'down-left': '↙', 'up-left': '↖' })[d] || '';
|
||||
const dirChip = !P.dirs ? '' : dragging ? `<span class="chip ok">${glyph(P.dirs[GAME.dragDir])} ${esc(P.dirs[GAME.dragDir])} · from your drag</span>` : `<span class="chip hot">${glyph(dir)} ${esc(dir)} · R</span>`;
|
||||
const fc = GAME.filterCfg;
|
||||
const sws = ms => ms.slice(0, 6).map(m => `<span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:2px;background:${rgbOf(m)}"></span>`).join('') + (ms.length > 6 ? `+${ms.length - 6}` : '');
|
||||
|
||||
+2
-1
@@ -115,7 +115,8 @@ function hoverInfo(ax, ay) {
|
||||
const pb = ((ay / BS) | 0) * BW + ((ax / BS) | 0), pn = pipeNetOf(pb);
|
||||
t1 = 'Pipe' + (m !== EMPTY ? ` · ${MD[m].name} flowing past` : '');
|
||||
t2.push(pipeContentsText(pn));
|
||||
} else if (p) t1 = ({ 1: 'Screw tube ↑', 2: 'Screw tube ↓', 3: 'Screw tube ←', 4: 'Screw tube →', 5: 'Grate', 6: 'Gate (open)', 7: 'Water wheel', 8: 'Engine intake' })[p] + (m !== EMPTY ? ` · ${MD[m].name}` : '');
|
||||
} else if (IS_TUBE[p]) t1 = 'Screw tube ' + PIECE_BY_ID.tube.dirs[TUBE_PARTS.indexOf(p)] + (m !== EMPTY ? ` · ${MD[m].name}` : '');
|
||||
else if (p) t1 = ({ 5: 'Grate', 6: 'Gate (open)', 7: 'Water wheel', 8: 'Engine intake' })[p] + (m !== EMPTY ? ` · ${MD[m].name}` : '');
|
||||
else if (m === BELTL || m === BELTR) t1 = 'Belt ' + (m === BELTL ? '←' : '→');
|
||||
else if (m === PIPE && isPipeCode(b)) {
|
||||
const pb = ((ay / BS) | 0) * BW + ((ax / BS) | 0), pn = pipeNetOf(pb), v = blockV[pb], arrows = ['↑', '→', '↓', '←'];
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ function controlsHtml() {
|
||||
3. Build (3) a chamber of stone wall blocks with a one-block vent in the roof, a floor of clay Grates, and an Ingot mold right under the grates. The notebook's Blueprints tab has this as a starter design.<br>
|
||||
4. Ctrl+click coal and copper ore in the bag to blow a mix. Fly inside (the bot passes through what you build) and right click to blow an even mix onto the grates: about 50 of each. (Ore first and coal on top works too; coal buried under ore will not light.) Light a coal grain on top with the Igniter.<br>
|
||||
5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or pick them up with the nozzle. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.<br>
|
||||
Screw tubes carry grains and whole bars: feed bars in from a belt, turn corners and unload at the mouth.<br>
|
||||
Screw tubes carry grains and whole bars: feed bars in from a belt, turn corners and unload at the mouth. Drag at about 45° for a diagonal run; R cycles eight directions.<br>
|
||||
6. Blow the bars into the trade dock: they sell the moment they drop through its grate, and bars pay far more than ore. Watch the Market page for orders that pay double. Money buys belts, screw tubes and fans; pay down the claim there too.<br>
|
||||
7. From there: deeper ores (tin, zinc, silver, gold), alloys, fans for hotter fires, iron and steel.</p>
|
||||
`;
|
||||
|
||||
+3
-3
@@ -106,7 +106,7 @@ function drawPreviews() {
|
||||
if (P.kind === 'block') {
|
||||
const v = pieceV(P);
|
||||
col = previewColor(code, v);
|
||||
cells = GAME.drag ? (modShift() ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code))) : [[b.bx, b.by]];
|
||||
cells = GAME.drag ? (modShift() ? blockRect(GAME.drag.bx, GAME.drag.by, b.bx, b.by) : blockLine(GAME.drag.bx, GAME.drag.by, b.bx, b.by, isWall(code) || code === BP_TUBE)) : [[b.bx, b.by]];
|
||||
const dd = GAME.drag ? dragDirFor(P, GAME.drag, b) : -1;
|
||||
if (dd !== GAME.dragDir) { GAME.dragDir = dd; hudDirty = true; }
|
||||
const dIdx = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
||||
@@ -124,7 +124,7 @@ function drawPreviews() {
|
||||
previewBlock(code, x, y, w, h, true); ctx.setLineDash([]);
|
||||
if (P.dirs && (P.id === 'belt' || P.id === 'tube' || P.filter || P.id === 'pump' || P.id === 'outlet')) {
|
||||
// a chevron on every block, pointing the way it will carry (or pump, or pour)
|
||||
const [dx, dy] = P.id === 'tube' ? [[0, -1], [1, 0], [0, 1], [-1, 0]][dIdx] : P.id === 'pump' ? PUMP_V[dIdx] : P.id === 'outlet' ? OUTLET_V[dIdx] : [[-1, 0], [1, 0]][dIdx];
|
||||
const [dx, dy] = P.id === 'tube' ? TUBE_DIRS[dIdx] : P.id === 'pump' ? PUMP_V[dIdx] : P.id === 'outlet' ? OUTLET_V[dIdx] : [[-1, 0], [1, 0]][dIdx];
|
||||
const cx = x + w / 2, cy = y + h / 2, k = w * 0.22, px_ = -dy, py_ = dx;
|
||||
ctx.strokeStyle = 'rgba(20,16,12,0.55)'; ctx.lineWidth = Math.max(2, zp * 0.9); ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
||||
const chev = () => { ctx.beginPath(); ctx.moveTo(cx - dx * k * 0.6 + px_ * k, cy - dy * k * 0.6 + py_ * k); ctx.lineTo(cx + dx * k * 0.7, cy + dy * k * 0.7); ctx.lineTo(cx - dx * k * 0.6 - px_ * k, cy - dy * k * 0.6 - py_ * k); ctx.stroke(); };
|
||||
@@ -135,7 +135,7 @@ function drawPreviews() {
|
||||
if (GAME.drag && dd >= 0 && cells.length > 1 && !modShift()) {
|
||||
const [x0, y0, w0, h0] = blockRectPx(cells[0][0], cells[0][1], 1, 1), [x1, y1, w1, h1] = blockRectPx(cells[cells.length - 1][0], cells[cells.length - 1][1], 1, 1);
|
||||
let sx = x0 + w0 / 2, sy = y0 + h0 / 2, ex = x1 + w1 / 2, ey = y1 + h1 / 2;
|
||||
const [dx, dy] = P.id === 'tube' ? [[0, -1], [1, 0], [0, 1], [-1, 0]][dd] : [[-1, 0], [1, 0]][dd];
|
||||
const [dx, dy] = P.id === 'tube' ? TUBE_DIRS[dd] : [[-1, 0], [1, 0]][dd];
|
||||
if ((ex - sx) * dx + (ey - sy) * dy < 0) { const tx = sx, ty = sy; sx = ex; sy = ey; ex = tx; ey = ty; }
|
||||
const off = BS * zp * 1.0, hx = ex + dx * BS * zp * 0.55, hy = ey + dy * BS * zp * 0.55, ah = Math.max(8, BS * zp * 0.5);
|
||||
const ox = dy ? off : 0, oy = dx ? off : 0;
|
||||
|
||||
Reference in New Issue
Block a user