Rename screw tubes to screw conveyors (t44)
Player text says Screw conveyor; code uses screw (BP_SCREW, piece id screw, screwIntake...). No compatibility code for old saves or blueprints. Suite outputs match before and after apart from names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ function drawRain() {
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
// Info overlay: a small arrow on every belt and screw tube block on screen, the way it moves things
|
||||
// Info overlay: a small arrow on every belt and screw conveyor block on screen, the way it moves things
|
||||
function drawFlowArrows() {
|
||||
const ctx = R.ctx, zp = R.zp, v = viewCells();
|
||||
const bx0 = Math.max(0, Math.floor(R.camX / BS)), bx1 = Math.min(BW - 1, Math.floor((R.camX + v.w) / BS));
|
||||
@@ -37,12 +37,12 @@ function drawFlowArrows() {
|
||||
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
||||
for (let by = by0; by <= by1; by++) for (let bx = bx0; bx <= bx1; bx++) {
|
||||
const b = by * BW + bx, code = blockP[b];
|
||||
if (code !== BP_BELT && code !== BP_TUBE && code !== BP_PUMP && code !== BP_OUTLET) continue;
|
||||
if (code !== BP_BELT && code !== BP_SCREW && 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] : TUBE_DIRS[vv & 7];
|
||||
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] : SCREW_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);
|
||||
ctx.strokeStyle = code === BP_SCREW ? '#ffc58a' : '#e7dccb'; ctx.lineWidth = Math.max(1.2, r * 0.32);
|
||||
// a chevron pointing (dx, dy)
|
||||
const px = -dy, py = dx;
|
||||
ctx.beginPath();
|
||||
|
||||
@@ -13,7 +13,7 @@ function slopeBeltConnections(bx, by, code, planned = null) {
|
||||
const fy = by + (end ? grade > 0 : grade < 0), fb = fy * BW + nx;
|
||||
if (nx < 0 || nx >= BW || fy < 0 || fy >= BH) continue;
|
||||
const flat = planned && planned.get(fb), c = flat ? flat[0] : blockP[fb], v = flat ? flat[1] : blockV[fb];
|
||||
if (c === BP_BELT || c === BP_FILTER || (c === BP_TUBE && (v === 1 || v === 3))) joins |= 1 << end;
|
||||
if (c === BP_BELT || c === BP_FILTER || (c === BP_SCREW && (v === 1 || v === 3))) joins |= 1 << end;
|
||||
}
|
||||
return joins;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ function flatBeltCaps(b) {
|
||||
if (x >= 0 && x < BW) for (let dy = -1; dy <= 0; dy++) {
|
||||
if (by + dy < 0) continue;
|
||||
const n = (by + dy) * BW + x, c = blockP[n], g = beltGrade(c);
|
||||
if ((!dy && (c === BP_BELT || c === BP_FILTER || (c === BP_TUBE && (blockV[n] === 1 || blockV[n] === 3 || blockV[n] >= 4)))) ||
|
||||
if ((!dy && (c === BP_BELT || c === BP_FILTER || (c === BP_SCREW && (blockV[n] === 1 || blockV[n] === 3 || blockV[n] >= 4)))) ||
|
||||
(g && dy === -(end ? g < 0 : g > 0))) joined = true;
|
||||
}
|
||||
if (!joined) caps |= 1 << end;
|
||||
|
||||
@@ -250,17 +250,17 @@ ${SM_SLOPE_DRAW}
|
||||
}
|
||||
}
|
||||
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
|
||||
// screw conveyor: 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;
|
||||
float tubeSD = -1.0, capRing = 0.0;
|
||||
float screwSD = -1.0, capRing = 0.0;
|
||||
if (nc >= 19) {
|
||||
vec2 d = vec2(1.0, nc == 19 || nc == 21 ? -1.0 : 1.0);
|
||||
vec4 tag = cellAt(tTree, c);
|
||||
vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w) - 2.0;
|
||||
int joins = int(tag.a * 255.0 + 0.5);
|
||||
tubeSD = 100.0;
|
||||
screwSD = 100.0;
|
||||
for (int end = 0; end < 2; end++) {
|
||||
int join = (joins >> (end * 3)) & 7;
|
||||
float side = end == 0 ? -1.0 : 1.0;
|
||||
@@ -269,8 +269,8 @@ ${SM_SLOPE_DRAW}
|
||||
float t = dot(local, ray), cross2 = dot(local, vec2(-ray.y, ray.x));
|
||||
float radius = length(vec2(cross2, min(0.0, t))), cap = t - (join == 0 ? 2.82842712 : 2.0);
|
||||
float sd = max(radius - 2.0, join != 0 || (joins & (64 << end)) != 0 ? cap : -10.0);
|
||||
if (sd < tubeSD) {
|
||||
tubeSD = sd;
|
||||
if (sd < screwSD) {
|
||||
screwSD = sd;
|
||||
a = 2.0 + (side * cross2 < 0.0 ? -radius : radius);
|
||||
along = dot(w - local, d) * 0.70710678 + side * t;
|
||||
capRing = (joins & (64 << end)) != 0 ? smoothstep(-0.55, -0.15, cap) : 0.0;
|
||||
@@ -300,7 +300,7 @@ ${SM_SLOPE_DRAW}
|
||||
if (nc >= 19) {
|
||||
bool full = own.r + own.g + own.b > 0.0;
|
||||
if (full) C = mix(C, own.rgb, 1.0 - smoothstep(0.36, 0.5, length(fract(w) - 0.5)));
|
||||
C = mix(C, tc, 1.0 - smoothstep(2.0 - px, 2.0 + px, tubeSD + 2.0));
|
||||
C = mix(C, tc, 1.0 - smoothstep(2.0 - px, 2.0 + px, screwSD + 2.0));
|
||||
} else C = tc;
|
||||
}
|
||||
else if (nc == 6 || (nc >= 15 && nc <= 18)) {
|
||||
|
||||
+25
-25
@@ -2,12 +2,12 @@
|
||||
function fogColor(c, k) { return (c & 0xff000000) | (((c >>> 16 & 255) * k) << 16) | (((c >>> 8 & 255) * k) << 8) | ((c & 255) * k); }
|
||||
// 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(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;
|
||||
// smooth-look class of a screw conveyor cell by direction: 9 up, 10 right, 11 down, 12 left
|
||||
const SCREW_CLASS = new Uint8Array(15); SCREW_CLASS[1] = 9; SCREW_CLASS[4] = 10; SCREW_CLASS[2] = 11; SCREW_CLASS[3] = 12;
|
||||
for (let p = P_SCREW_UR; p <= P_SCREW_UL; p++) SCREW_CLASS[p] = 19 + p - P_SCREW_UR;
|
||||
// Drawing only: the four transport lanes remain in the square block.
|
||||
let diagonalJoins = new Uint8Array(0);
|
||||
function diagonalTubeJoins(bx, by, v, planned = null) {
|
||||
function diagonalScrewJoins(bx, by, v, planned = null) {
|
||||
// Low/high three bits select the two edge exits; bits 6/7 mark free-end collars.
|
||||
const dy = v === 4 || v === 6 ? -1 : 1;
|
||||
let joins = 0;
|
||||
@@ -16,14 +16,14 @@ function diagonalTubeJoins(bx, by, v, planned = null) {
|
||||
const nb = ny * BW + nx, next = planned && planned.get(nb);
|
||||
const nc = next ? next[0] : blockP[nb], nv = next ? next[1] : blockV[nb];
|
||||
if (nx >= 0 && ny >= 0 && nx < BW && ny < BH &&
|
||||
nc === BP_TUBE && nv >= 4 && (nv === 4 || nv === 6) === (dy < 0)) continue;
|
||||
nc === BP_SCREW && nv >= 4 && (nv === 4 || nv === 6) === (dy < 0)) continue;
|
||||
let join = 0;
|
||||
for (const dir of [side > 0 ? 1 : 3, side * dy > 0 ? 2 : 0]) {
|
||||
const x = bx + TUBE_DIRS[dir][0], y = by + TUBE_DIRS[dir][1];
|
||||
const x = bx + SCREW_DIRS[dir][0], y = by + SCREW_DIRS[dir][1];
|
||||
if (x < 0 || y < 0 || x >= BW || y >= BH) continue;
|
||||
const n = y * BW + x, next = planned && planned.get(n);
|
||||
const code = next ? next[0] : blockP[n], nv = next ? next[1] : blockV[n];
|
||||
if ((code === BP_TUBE && nv < 4 && (nv & 1) === (dir & 1)) ||
|
||||
if ((code === BP_SCREW && nv < 4 && (nv & 1) === (dir & 1)) ||
|
||||
((code === BP_BELT || code === BP_FILTER) && (dir & 1))) { join = dir + 1; break; }
|
||||
}
|
||||
joins |= join << (end * 3);
|
||||
@@ -33,13 +33,13 @@ function diagonalTubeJoins(bx, by, v, planned = null) {
|
||||
}
|
||||
// Scratch coordinates shared by the pixel shading; no allocations in the grain loop.
|
||||
let diagonalAcross = 0, diagonalAlong = 0, diagonalCap = 0;
|
||||
function diagonalTubeShape(x, y, b, v, joins) {
|
||||
function diagonalScrewShape(x, y, b, v, joins) {
|
||||
const lx = x - (b % BW) * BS - 2, ly = y - ((b / BW) | 0) * BS - 2;
|
||||
let best = Infinity;
|
||||
for (let end = 0; end < 2; end++) {
|
||||
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
|
||||
const dx = join ? TUBE_DIRS[join - 1][0] : side * Math.SQRT1_2;
|
||||
const dy = join ? TUBE_DIRS[join - 1][1] : side * (v === 4 || v === 6 ? -1 : 1) * Math.SQRT1_2;
|
||||
const dx = join ? SCREW_DIRS[join - 1][0] : side * Math.SQRT1_2;
|
||||
const dy = join ? SCREW_DIRS[join - 1][1] : side * (v === 4 || v === 6 ? -1 : 1) * Math.SQRT1_2;
|
||||
const along = lx * dx + ly * dy, across = -lx * dy + ly * dx;
|
||||
const radius = Math.hypot(across, Math.min(0, along)), cap = along - (join ? 2 : 2 * Math.SQRT2);
|
||||
const sd = Math.max(radius - 2, join || (joins & (64 << end)) ? cap : -10);
|
||||
@@ -50,9 +50,9 @@ function diagonalTubeShape(x, y, b, v, joins) {
|
||||
}
|
||||
return best;
|
||||
}
|
||||
function diagonalTubeBlock(x, y, smooth) {
|
||||
function diagonalScrewBlock(x, y, smooth) {
|
||||
const i = y * W + x, own = (y >> 2) * BW + (x >> 2);
|
||||
if (part[i] >= P_TUR && part[i] <= P_TUL) return own;
|
||||
if (part[i] >= P_SCREW_UR && part[i] <= P_SCREW_UL) return own;
|
||||
if (mat[i] !== EMPTY || part[i] || mach[i] || occ[i] || blockP[own]) return -1;
|
||||
let best = smooth ? 0.75 : 0, found = -1;
|
||||
for (let k = 0; k < 4; k++) {
|
||||
@@ -60,9 +60,9 @@ function diagonalTubeBlock(x, y, smooth) {
|
||||
const by = (y >> 2) + (k === 2 ? -1 : k === 3 ? 1 : 0);
|
||||
if (bx < 0 || by < 0 || bx >= BW || by >= BH) continue;
|
||||
const b = by * BW + bx, v = blockV[b];
|
||||
if (blockP[b] !== BP_TUBE || v < 4) continue;
|
||||
const joins = diagonalJoins[b] === undefined ? diagonalTubeJoins(bx, by, v) : diagonalJoins[b];
|
||||
const sd = diagonalTubeShape(x + 0.5, y + 0.5, b, v, joins);
|
||||
if (blockP[b] !== BP_SCREW || v < 4) continue;
|
||||
const joins = diagonalJoins[b] === undefined ? diagonalScrewJoins(bx, by, v) : diagonalJoins[b];
|
||||
const sd = diagonalScrewShape(x + 0.5, y + 0.5, b, v, joins);
|
||||
if (sd <= best) { best = sd; found = b; }
|
||||
}
|
||||
return found;
|
||||
@@ -89,7 +89,7 @@ function drawWorld() {
|
||||
for (let by = Math.max(0, (vy0 >> 2) - 1); by <= Math.min(BH - 1, ((vy0 + bh) >> 2) + 1); by++) {
|
||||
for (let bx = Math.max(0, (vx0 >> 2) - 1); bx <= Math.min(BW - 1, ((vx0 + bw) >> 2) + 1); bx++) {
|
||||
const b = by * BW + bx;
|
||||
if (blockP[b] === BP_TUBE && blockV[b] >= 4) diagonalJoins[b] = diagonalTubeJoins(bx, by, blockV[b]);
|
||||
if (blockP[b] === BP_SCREW && blockV[b] >= 4) diagonalJoins[b] = diagonalScrewJoins(bx, by, blockV[b]);
|
||||
}
|
||||
}
|
||||
prepareSlopeBelts(vx0, vy0, vx0 + bw, vy0 + bh);
|
||||
@@ -228,23 +228,23 @@ function drawWorld() {
|
||||
const sb = slopeBeltAt(i, x, y, sm);
|
||||
let p = part[i];
|
||||
if (p === P_FILTER && (sb >= 0 || isSlopeBelt(blockP[slopeSealOwner(i)]))) p = 0;
|
||||
const db = diagonalTubeBlock(x, y, sm);
|
||||
const db = diagonalScrewBlock(x, y, sm);
|
||||
let diagonal = false;
|
||||
if (db >= 0) {
|
||||
const v = blockV[db], joins = diagonalJoins[db];
|
||||
const sd = diagonalTubeShape(x + 0.5, y + 0.5, db, v, joins);
|
||||
p = sm || sd <= 0 ? TUBE_PARTS[v] : 0;
|
||||
const sd = diagonalScrewShape(x + 0.5, y + 0.5, db, v, joins);
|
||||
p = sm || sd <= 0 ? SCREW_PARTS[v] : 0;
|
||||
diagonal = !!p;
|
||||
if (sm) tpx[row + bx] = (joins << 24) | ((y - ((db / BW) | 0) * BS + 8) << 16) | ((x - (db % BW) * BS + 8) << 8);
|
||||
}
|
||||
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); } }
|
||||
if (p && sm && (IS_SCREW[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 (IS_TUBE[p]) {
|
||||
if (IS_SCREW[p]) {
|
||||
const on = netSat(i) > 0;
|
||||
const vert = p === P_TU || p === P_TD;
|
||||
const vert = p === P_SCREW_U || p === P_SCREW_D;
|
||||
const axis = diagonal ? Math.floor(diagonalAlong) : vert ? y : x;
|
||||
const across = diagonal ? diagonalAcross + 2 : vert ? x & 3 : y & 3;
|
||||
const dir = diagonal ? (blockV[db] >= 6 ? 1 : -1) : (p === P_TU || p === P_TL) ? 1 : -1;
|
||||
const dir = diagonal ? (blockV[db] >= 6 ? 1 : -1) : (p === P_SCREW_U || p === P_SCREW_L) ? 1 : -1;
|
||||
if (diagonal ? Math.abs(diagonalAcross) > 1.38 || diagonalCap > -0.55 : 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 (!diagonal && (axis & 3) === 0) { r -= 24; g -= 18; b -= 12; }
|
||||
@@ -305,7 +305,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)) || (p === P_FILTER && sb >= 0) ? 0 : p; // the open parts of a pipe block show what is in them
|
||||
if (IS_TUBE[pt]) cl = TUBE_CLASS[pt];
|
||||
if (IS_SCREW[pt]) cl = SCREW_CLASS[pt];
|
||||
else if (p === P_FILTER && sb < 0) {
|
||||
// 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;
|
||||
@@ -369,7 +369,7 @@ function drawWorld() {
|
||||
px[sy * bw + sx] = (sm ? 0x04000000 : 0xff000000) | ((b > 255 ? 255 : b | 0) << 16) | ((g > 255 ? 255 : g | 0) << 8) | (r > 255 ? 255 : r | 0);
|
||||
}
|
||||
}
|
||||
// grains in flight from the vacuum nozzle, a tube or a belt (rain and snow are drawn over the top, see drawRain)
|
||||
// grains in flight from the vacuum nozzle, a screw or a belt (rain and snow are drawn over the top, see drawRain)
|
||||
if (!lens) for (const f of flyers) {
|
||||
if (f.rain || (useFog && fogSeen(f.x, f.y) < 64)) continue;
|
||||
const sx = Math.floor(f.x) - vx0, sy = Math.floor(f.y) - vy0;
|
||||
|
||||
+3
-3
@@ -44,7 +44,7 @@ function canPlaceBlock(bx, by, code = BP_WALL) {
|
||||
}
|
||||
return '';
|
||||
}
|
||||
// v: wall material (loose id) for walls, direction for belts and tubes
|
||||
// v: wall material (loose id) for walls, direction for belts and screws
|
||||
function placeBlock(code, v, bx, by, free, extra) {
|
||||
const err = canPlaceBlock(bx, by, code); if (err) return err;
|
||||
if (isWall(code) && !WALL_OF[v]) return 'Pick a wall material with R';
|
||||
@@ -61,7 +61,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] = TUBE_PARTS[v & 7]; markPow(i, true); wakeXY(x, y); }
|
||||
else if (code === BP_SCREW) { part[i] = SCREW_PARTS[v & 7]; markPow(i, true); wakeXY(x, y); }
|
||||
else if (isFilter(code)) { 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);
|
||||
@@ -99,7 +99,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 (IS_TUBE[p]) n++; }
|
||||
else if (code === BP_SCREW) { if (IS_SCREW[p]) n++; }
|
||||
else if (isFilter(code)) { 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) => isBelt(code) ? 1 - v : isFilter(code) ? v ^ 128 : code === BP_TUBE ? [0, 3, 2, 1, 7, 6, 5, 4][v & 7]
|
||||
const flipV = (code, v) => isBelt(code) ? 1 - v : isFilter(code) ? v ^ 128 : code === BP_SCREW ? [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 (IS_TUBE[p]) { if (ph === PH_POWDER && TDX[p] === -dx && TDY[p] === -dy) return 0; } // liquids and gases flow through freely
|
||||
if (IS_SCREW[p]) { if (ph === PH_POWDER && SCREW_DX[p] === -dx && SCREW_DY[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; }
|
||||
@@ -93,7 +93,7 @@ function passable(m, ph, j, dx, dy) {
|
||||
if (occ[j]) return false;
|
||||
const p = part[j];
|
||||
if (p !== 0) {
|
||||
if (IS_TUBE[p]) { if (ph === PH_POWDER && TDX[p] === -dx && TDY[p] === -dy) return false; }
|
||||
if (IS_SCREW[p]) { if (ph === PH_POWDER && SCREW_DX[p] === -dx && SCREW_DY[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; }
|
||||
@@ -199,7 +199,7 @@ function simMove() {
|
||||
if (stamp[i] === tickStamp) continue;
|
||||
if (ph === PH_SOLID) continue;
|
||||
const p = part[i];
|
||||
if (IS_TUBE[p] && ph === PH_POWDER) continue; // a screw tube moves its own grains
|
||||
if (IS_SCREW[p] && ph === PH_POWDER) continue; // a screw conveyor 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);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -1,4 +1,4 @@
|
||||
// How belts, filters and screw tubes move what is on or in them.
|
||||
// How belts, filters and screw conveyors move what is on or in them.
|
||||
// What rests on a belt or filter rides along: the whole stack standing on the carrying surface
|
||||
// steps one cell toward d together (belts run front of the line first, so the way ahead is
|
||||
// clear). If a powder grain is blocked, the load above it stands still and the belt slips
|
||||
@@ -70,7 +70,7 @@ function beltDrain(b) {
|
||||
if (!passable(mat[a], PH_LIQUID, d, 0, 1)) return;
|
||||
move(a, d, x, (a / W) | 0, x, (d / W) | 0);
|
||||
}
|
||||
// Take a grain out of cell i and send it flying from (x, y): what leaves a belt or a tube mouth
|
||||
// Take a grain out of cell i and send it flying from (x, y): what leaves a belt or a screw mouth
|
||||
function launchGrain(i, x, y, vx, vy) {
|
||||
const m = mat[i], T = temp[i];
|
||||
mat[i] = EMPTY; flags[i] = 0; life[i] = 0; stamp[i] = tickStamp;
|
||||
@@ -145,8 +145,8 @@ function filterRun(b) {
|
||||
|
||||
// A cell a grain can be pushed into: empty, or liquid or gas that simply trades places with it
|
||||
function roomFor(j) { const t = mat[j]; return t === EMPTY || PH[t] === PH_GAS || PH[t] === PH_LIQUID; }
|
||||
// Cross one tube block, far lanes first; corners retain their liquid/gas swaps.
|
||||
function tubeSpread(c, x, y, sx, sy, intake = false) {
|
||||
// Cross one screw block, far lanes first; corners retain their liquid/gas swaps.
|
||||
function screwSpread(c, x, y, sx, sy, intake = false) {
|
||||
const p = part[c], bx = x >> 2, by = y >> 2;
|
||||
for (;;) {
|
||||
const nx = x + sx, ny = y + sy;
|
||||
@@ -158,8 +158,8 @@ function tubeSpread(c, x, y, sx, sy, intake = false) {
|
||||
return c;
|
||||
}
|
||||
// An empty screw cell draws in loose material from behind it or from its sides
|
||||
// (not from the cell it pushes into), so a tube end sitting in a pile feeds itself.
|
||||
function tubeIntake(i, p, dx, dy) {
|
||||
// (not from the cell it pushes into), so a screw end sitting in a pile feeds itself.
|
||||
function screwIntake(i, p, dx, dy) {
|
||||
if (occ[i]) return;
|
||||
const x = i % W, y = (i / W) | 0;
|
||||
const start = (tick + i) % 3;
|
||||
@@ -175,28 +175,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 (IS_TUBE[pj]) continue; // other tube cells move their own contents
|
||||
if (IS_SCREW[pj]) continue; // other screw cells move their own contents
|
||||
if (pj === P_GRATE || pj === P_FILTER) continue;
|
||||
move(j, i, nx, ny, x, y);
|
||||
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);
|
||||
if (diagonal) screwSpread(i, x, y, 0, (y & 3) < 2 ? 1 : -1, true);
|
||||
else if (ox !== -dx || oy !== -dy) screwSpread(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];
|
||||
function diagonalScrewNext(i, p) {
|
||||
const x = i % W, y = (i / W) | 0, dx = SCREW_DX[p], dy = SCREW_DY[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 (yy >= 0 && yy < H && IS_SCREW[part[yy * W + xx]]) return yy * W + xx;
|
||||
if (IS_SCREW[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;
|
||||
if (hy >= 0 && hy < H && IS_SCREW[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;
|
||||
}
|
||||
|
||||
+45
-45
@@ -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 && (IS_TUBE[p] || 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_SCREW[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;
|
||||
@@ -38,97 +38,97 @@ function spawnBar(m, x, y, vx, vy, T) {
|
||||
return e;
|
||||
}
|
||||
function removeEnt(e) { entMark(e, 0); const k = ents.indexOf(e); if (k >= 0) ents.splice(k, 1); }
|
||||
// No transient tube state: even a loaded bar derives its ride from its eight cells.
|
||||
function entTubeAt(x, y) {
|
||||
// No transient screw state: even a loaded bar derives its ride from its eight cells.
|
||||
function entScrewAt(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 (!IS_TUBE[p]) return 0;
|
||||
const p = part[yy * W + xx]; if (!IS_SCREW[p]) return 0;
|
||||
}
|
||||
return part[(y + 1) * W + x + 1];
|
||||
}
|
||||
function entTubeFree(x, y, tubeOnly = false) {
|
||||
function entScrewFree(x, y, screwOnly = 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 = 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;
|
||||
const j = yy * W + xx, p = part[j], screw = IS_SCREW[p];
|
||||
// gas and liquid don't stop a bar, in a screw or in the open
|
||||
if (occ[j] || (mat[j] !== EMPTY && PH[mat[j]] !== PH_GAS && PH[mat[j]] !== PH_LIQUID) || (screwOnly ? !screw : !screw && 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, mouth = false) {
|
||||
if (!entTubeFree(x, y, tubeOnly)) return false;
|
||||
function entScrewMove(e, x, y, screwOnly = false, mouth = false) {
|
||||
if (!entScrewFree(x, y, screwOnly)) return false;
|
||||
if (mouth) {
|
||||
// Go around the receiving corner, checking both legs before moving.
|
||||
const acrossY = y > e.iy ? e.iy : y, downX = y > e.iy ? x : e.ix;
|
||||
for (let yy = Math.min(e.iy, y); yy <= Math.max(e.iy, y); yy++) if (!entTubeFree(downX, yy)) return false;
|
||||
for (let xx = Math.min(e.ix, x); xx <= Math.max(e.ix, x); xx++) if (!entTubeFree(xx, acrossY)) return false;
|
||||
for (let yy = Math.min(e.iy, y); yy <= Math.max(e.iy, y); yy++) if (!entScrewFree(downX, yy)) return false;
|
||||
for (let xx = Math.min(e.ix, x); xx <= Math.max(e.ix, x); xx++) if (!entScrewFree(xx, acrossY)) 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;
|
||||
for (let xx = Math.min(e.ix, x); xx <= Math.max(e.ix, x); xx++) if (!entScrewFree(xx, yy)) return false;
|
||||
e.x = e.ix = x; e.y = e.iy = y; e.vx = e.vy = 0; return true;
|
||||
}
|
||||
function entTubeLane(y, iy) { return Math.max(y & ~3, Math.min((y & ~3) + 2, iy)); }
|
||||
function entTubeEdge(e, dx, dy, k) {
|
||||
function entScrewLane(y, iy) { return Math.max(y & ~3, Math.min((y & ~3) + 2, iy)); }
|
||||
function entScrewEdge(e, dx, dy, k) {
|
||||
const x = e.ix + (dx > 0 ? BAR_W : dx < 0 ? -1 : k), y = e.iy + (dy > 0 ? BAR_H : dy < 0 ? -1 : k);
|
||||
return x < 0 || y < 0 || x >= W || y >= H ? -1 : y * W + x;
|
||||
}
|
||||
function entTubeEnter(e, dx, dy, mouth = false) {
|
||||
function entScrewEnter(e, dx, dy, mouth = false) {
|
||||
for (let k = 0; k < (dx ? BAR_H : BAR_W); k++) {
|
||||
const i = entTubeEdge(e, dx, dy, k);
|
||||
const i = entScrewEdge(e, dx, dy, k);
|
||||
if (i < 0) continue;
|
||||
const xx = i % W, yy = (i / W) | 0;
|
||||
const p = part[i]; if (!IS_TUBE[p] || (!dx && TDX[p]) || dx * TDX[p] + dy * TDY[p] < 0) continue;
|
||||
const p = part[i]; if (!IS_SCREW[p] || (!dx && SCREW_DX[p]) || dx * SCREW_DX[p] + dy * SCREW_DY[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, mouth)) return true;
|
||||
const x = (xx & ~3), y = entScrewLane(yy, e.iy);
|
||||
if (entScrewMove(e, x, SCREW_DX[p] || dy ? y : e.iy, true, mouth)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function entTubeTick(e, p) {
|
||||
function entScrewTick(e, p) {
|
||||
e.vx = e.vy = 0; e.x = e.ix; e.y = e.iy;
|
||||
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; }
|
||||
const dx = SCREW_DX[p], dy = SCREW_DY[p];
|
||||
if (dx && dy) { entDiagonalScrewTick(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);
|
||||
const i = entScrewEdge(e, dx, dy, k);
|
||||
if (i < 0) return;
|
||||
const xx = i % W, yy = (i / W) | 0;
|
||||
const q = part[i];
|
||||
if (IS_TUBE[q] && q !== p) { next = q; ax = xx; ay = yy; break; }
|
||||
if (IS_SCREW[q] && q !== p) { next = q; ax = xx; ay = yy; break; }
|
||||
}
|
||||
if (next) {
|
||||
if (TDX[next] === -dx && TDY[next] === -dy) return;
|
||||
x = ax & ~3; y = entTubeLane(ay, e.iy);
|
||||
if (entTubeMove(e, x, y, true)) stats.moved++;
|
||||
if (SCREW_DX[next] === -dx && SCREW_DY[next] === -dy) return;
|
||||
x = ax & ~3; y = entScrewLane(ay, e.iy);
|
||||
if (entScrewMove(e, x, y, true)) stats.moved++;
|
||||
return;
|
||||
}
|
||||
if (entTubeAt(x, y)) { if (entTubeMove(e, x, y, true)) stats.moved++; return; }
|
||||
if (entScrewAt(x, y)) { if (entScrewMove(e, x, y, true)) stats.moved++; return; }
|
||||
// Clear the mouth in one move so the next tick can derive ordinary falling from position.
|
||||
x = e.ix + dx * BAR_W; y = e.iy + dy * BAR_H;
|
||||
if (!entTubeFree(x, y) || entTubeAt(x, y)) return;
|
||||
if (!entScrewFree(x, y) || entScrewAt(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)) { e.vx = dx ? dx * 0.5 : (rnd() - 0.5) * 0.4; e.vy = dy * 0.6; stats.moved++; }
|
||||
if (entScrewMove(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) {
|
||||
function entDiagonalScrewTick(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;
|
||||
const dx = SCREW_DX[p], dy = SCREW_DY[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);
|
||||
const x = bx + ox * BS, y = by + oy * BS + lane, q = entScrewAt(x, y);
|
||||
if (!q) continue;
|
||||
if (TDX[q] * dx + TDY[q] * dy < 0) return;
|
||||
if (entTubeMove(e, x, y, true)) stats.moved++;
|
||||
if (SCREW_DX[q] * dx + SCREW_DY[q] * dy < 0) return;
|
||||
if (entScrewMove(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;
|
||||
if (!entScrewFree(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++; }
|
||||
if (entScrewMove(e, x, y, false, dy < 0)) { e.vx = dx * 0.5; e.vy = dy * 0.6; stats.moved++; }
|
||||
}
|
||||
// Bars stay level; their highest contact follows the face one grain per belt beat.
|
||||
function entSlopeBeltTick(e) {
|
||||
@@ -158,7 +158,7 @@ function entSlopeBeltTick(e) {
|
||||
if (tick & 1) return true;
|
||||
const y = face === H ? e.iy : face - BAR_H, x = e.ix + d;
|
||||
if (entFreeAt(x, y)) { e.vx = d * 0.5; e.vy = (y - e.iy) * 0.5; e.x = e.ix = x; e.y = e.iy = y; stats.moved++; }
|
||||
else if (!entTubeEnter(e, d, 0)) entTubeEnter(e, d, Math.sign(y - e.iy), true);
|
||||
else if (!entScrewEnter(e, d, 0)) entScrewEnter(e, d, Math.sign(y - e.iy), true);
|
||||
return true;
|
||||
}
|
||||
function entTick() {
|
||||
@@ -167,8 +167,8 @@ function entTick() {
|
||||
entMark(e, 0);
|
||||
entCurM = e.m;
|
||||
let ix = e.ix; const iy = e.iy;
|
||||
const tube = entTubeAt(ix, iy);
|
||||
if (tube) entTubeTick(e, tube);
|
||||
const screw = entScrewAt(ix, iy);
|
||||
if (screw) entScrewTick(e, screw);
|
||||
else if (!entSlopeBeltTick(e)) {
|
||||
// what is underneath?
|
||||
let belt = 0, bsat = 0, transport = false, support = iy + BAR_H >= H;
|
||||
@@ -205,10 +205,10 @@ function entTick() {
|
||||
e.x += e.vx; e.y += e.vy;
|
||||
// step toward the new rounded position one cell at a time
|
||||
let tx = Math.round(e.x), ty = Math.round(e.y), cx = ix, cy = iy;
|
||||
while (cx !== tx) { const s = Math.sign(tx - cx); if (entFreeAt(cx + s, cy)) cx += s; else if (belt && entTubeEnter(e, s, 0)) { cx = e.ix; cy = e.iy; e.x = cx; e.y = cy; break; } else { e.vx = 0; e.x = cx; break; } }
|
||||
while (cy !== ty) { const s = Math.sign(ty - cy); if (entTubeAt(cx, cy)) break; if (entFreeAt(cx, cy + s)) cy += s; else if (entTubeEnter(e, 0, s)) { cx = e.ix; cy = e.iy; break; } else { e.vy = 0; e.y = cy; break; } }
|
||||
if (support && !belt && entTubeEnter(e, 0, 1)) { cx = e.ix; cy = e.iy; }
|
||||
if (!entTubeAt(cx, cy) && !entFreeAt(cx, cy)) {
|
||||
while (cx !== tx) { const s = Math.sign(tx - cx); if (entFreeAt(cx + s, cy)) cx += s; else if (belt && entScrewEnter(e, s, 0)) { cx = e.ix; cy = e.iy; e.x = cx; e.y = cy; break; } else { e.vx = 0; e.x = cx; break; } }
|
||||
while (cy !== ty) { const s = Math.sign(ty - cy); if (entScrewAt(cx, cy)) break; if (entFreeAt(cx, cy + s)) cy += s; else if (entScrewEnter(e, 0, s)) { cx = e.ix; cy = e.iy; break; } else { e.vy = 0; e.y = cy; break; } }
|
||||
if (support && !belt && entScrewEnter(e, 0, 1)) { cx = e.ix; cy = e.iy; }
|
||||
if (!entScrewAt(cx, cy) && !entFreeAt(cx, cy)) {
|
||||
// something moved into us (a grain fell in): try to step up out of it
|
||||
let freed = false;
|
||||
for (let u = 1; u < 6; u++) if (entFreeAt(cx, cy - u)) { cy -= u; e.y = cy; freed = true; break; }
|
||||
|
||||
+14
-14
@@ -1,4 +1,4 @@
|
||||
// Per-tick machine work: steam engines, fans, belts, filters, screw tubes (machinesTick).
|
||||
// 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) {
|
||||
@@ -69,7 +69,7 @@ function machinesTick() {
|
||||
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 * TUBE_POWER;
|
||||
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;
|
||||
@@ -91,16 +91,16 @@ function machinesTick() {
|
||||
if (filtersDirty) rebuildFilters();
|
||||
carryBeltsAndFilters(beltL, filterL, -1, BELTL, (tick & 1) === 0);
|
||||
carryBeltsAndFilters(beltR, filterR, 1, BELTR, (tick & 1) === 0);
|
||||
// tubes: every loose grain inside steps one cell along, front of the line first, on the same
|
||||
// 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_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;
|
||||
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], dj = dy * W + dx, 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) tubeIntake(i, p, dx, dy); continue; }
|
||||
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);
|
||||
@@ -128,26 +128,26 @@ function machinesTick() {
|
||||
if (shifted) continue;
|
||||
}
|
||||
if (x + dx < 0 || x + dx >= W || y + dy < 0 || y + dy >= H) continue;
|
||||
const dest = dx && dy ? diagonalTubeNext(i, p) : i + dj;
|
||||
const dest = dx && dy ? diagonalScrewNext(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 = dest;
|
||||
if (occ[j]) continue;
|
||||
const pj = part[j], tube = IS_TUBE[pj];
|
||||
if (tube && TDX[pj] === -dx && TDY[pj] === -dy) continue;
|
||||
const pj = part[j], screw = IS_SCREW[pj];
|
||||
if (screw && SCREW_DX[pj] === -dx && SCREW_DY[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) {
|
||||
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++; continue;
|
||||
}
|
||||
move(i, j, x, y, nx, ny); stats.moved++;
|
||||
if (tube && pj !== p) {
|
||||
// turning a corner: keep going across the new tube's width so every lane fills
|
||||
tubeSpread(j, nx, ny, dx, dy);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -10,7 +10,7 @@ let nextPowerBuild = 0;
|
||||
function rebuildPower() {
|
||||
powerDirty = false;
|
||||
for (const i of pcells) netId[i] = 0;
|
||||
nets = [null]; beltL = []; beltR = []; tubeLists = Array.from({ length: IS_TUBE.length }, () => []);
|
||||
nets = [null]; beltL = []; beltR = []; screwLists = Array.from({ length: IS_SCREW.length }, () => []);
|
||||
const stack = [];
|
||||
for (const s of pcells) {
|
||||
if (netId[s]) continue;
|
||||
@@ -23,11 +23,11 @@ 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 (IS_TUBE[p]) { net.nt++; tubeLists[p].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_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 (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;
|
||||
@@ -45,10 +45,10 @@ 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 % W) - (b % W) || a - b); beltR.sort((a, b) => (b % W) - (a % W) || 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;
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ 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, 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];
|
||||
const P_NONE = 0, P_SCREW_U = 1, P_SCREW_D = 2, P_SCREW_L = 3, P_SCREW_R = 4, P_GRATE = 5, P_GOPEN = 6, P_WHEEL = 7, P_INTAKE = 8, P_FILTER = 9, P_PIPE = 10, P_SCREW_UR = 11, P_SCREW_DR = 12, P_SCREW_DL = 13, P_SCREW_UL = 14;
|
||||
const SCREW_PARTS = [P_SCREW_U, P_SCREW_R, P_SCREW_D, P_SCREW_L, P_SCREW_UR, P_SCREW_DR, P_SCREW_DL, P_SCREW_UL];
|
||||
const SCREW_DIRS = [[0, -1], [1, 0], [0, 1], [-1, 0], [1, -1], [1, 1], [-1, 1], [-1, -1]];
|
||||
const IS_SCREW = new Uint8Array(15), SCREW_DX = new Int8Array(15), SCREW_DY = new Int8Array(15);
|
||||
for (let v = 0; v < SCREW_PARTS.length; v++) {
|
||||
const p = SCREW_PARTS[v]; IS_SCREW[p] = 1; SCREW_DX[p] = SCREW_DIRS[v][0]; SCREW_DY[p] = SCREW_DIRS[v][1];
|
||||
}
|
||||
|
||||
// ---------- Tool tiers ----------
|
||||
|
||||
@@ -46,7 +46,7 @@ def(SLAG, { name: 'Slag', ph: PH_POWDER, col: [76, 66, 64], v: 34, dens: 2.4, k:
|
||||
def(MSLAG, { name: 'Molten slag', ph: PH_LIQUID, col: [232, 112, 40], v: 16, dens: 3.6, k: 0.02, cap: 1.2, hard: 0, freeze: 1000, freezeTo: SLAG, fluid: 0.5, spread: 10, liqT: 1150, desc: 'Smelting waste. Trickles down through the charge but is too thick to pass a grate, so it pools there and sets into slag. Knocking out the mold below rakes it away.' });
|
||||
def(BRICK, { name: 'Brick', col: [168, 76, 56], v: 16, k: 0.004, hard: 3, melt: 1750, meltTo: MAGMA, yld: RBRICK, desc: 'Fired clay. Insulates and survives 1750°C.' });
|
||||
def(GLASS, { name: 'Glass', col: [178, 216, 224], v: 6, k: 0.006, hard: 2, melt: 1300, meltTo: MGL, alpha: 0.45, yld: CULLET, desc: 'Clear. Lets light through.' });
|
||||
def(COPPER, { name: 'Copper', col: [200, 122, 76], v: 8, k: 0.04, cap: 1.5, hard: 4, melt: 1085, meltTo: MCU, yld: SCRCU, desc: 'Conducts heat well. Belts, tubes, fans, engines.' });
|
||||
def(COPPER, { name: 'Copper', col: [200, 122, 76], v: 8, k: 0.04, cap: 1.5, hard: 4, melt: 1085, meltTo: MCU, yld: SCRCU, desc: 'Conducts heat well. Belts, screw conveyors, fans, engines.' });
|
||||
def(IRON, { name: 'Iron', col: [128, 131, 138], v: 8, k: 0.035, cap: 1.5, hard: 5, melt: 1538, meltTo: MFE, yld: SCRFE, desc: 'Strong metal. Melts at 1538°C.' });
|
||||
def(STEEL, { name: 'Steel', col: [172, 180, 192], v: 6, k: 0.035, cap: 1.5, hard: 6, melt: 1450, meltTo: MST, yld: SCRST, desc: 'Iron refined by blowing air through it while molten. The best bit material.' });
|
||||
def(WATER, { name: 'Water', ph: PH_LIQUID, col: [44, 100, 194], v: 6, dens: 1, k: 0.02, cap: 4, hard: 0, boil: 100, boilTo: STEAM, fluid: 1, spread: 48, alpha: 0.78, liqT: 20, bag: true, desc: 'Boils at 100°C. Steam that rises into the open sky comes back down as rain.' });
|
||||
@@ -77,7 +77,7 @@ def(CULLET, { name: 'Glass shards', ph: PH_POWDER, col: [196, 228, 236], v: 22,
|
||||
def(SCRCU, { name: 'Copper scrap', ph: PH_POWDER, col: [192, 118, 74], v: 36, dens: 5, k: 0.035, cap: 1.5, hard: 0, stick: 0.7, melt: 1085, meltTo: MCU, bag: true, desc: 'Copper that set outside a mold. Melt it again and cast it.' });
|
||||
def(SCRFE, { name: 'Iron scrap', ph: PH_POWDER, col: [124, 126, 132], v: 30, dens: 5, k: 0.03, cap: 1.5, hard: 0, stick: 0.7, melt: 1538, meltTo: MFE, bag: true, desc: 'Iron that set outside a mold. Melt it again and cast it.' });
|
||||
def(SCRST, { name: 'Steel scrap', ph: PH_POWDER, col: [168, 176, 188], v: 28, dens: 5, k: 0.03, cap: 1.5, hard: 0, stick: 0.7, melt: 1450, meltTo: MST, bag: true, desc: 'Steel that set outside a mold. Melt it again and cast it.' });
|
||||
def(BARCU, { name: 'Copper bar', ph: PH_ITEM, col: [214, 132, 82], v: 0, dens: 8.9, k: 0, hard: 0, melt: 1085, meltTo: MCU, bag: true, desc: 'A cast bar. Belts, screw tubes, fans and engines are made from these.' });
|
||||
def(BARCU, { name: 'Copper bar', ph: PH_ITEM, col: [214, 132, 82], v: 0, dens: 8.9, k: 0, hard: 0, melt: 1085, meltTo: MCU, bag: true, desc: 'A cast bar. Belts, screw conveyors, fans and engines are made from these.' });
|
||||
def(BARFE, { name: 'Iron bar', ph: PH_ITEM, col: [140, 143, 150], v: 0, dens: 7.9, k: 0, hard: 0, melt: 1538, meltTo: MFE, bag: true, desc: 'A cast bar of iron.' });
|
||||
def(BARST, { name: 'Steel bar', ph: PH_ITEM, col: [184, 192, 204], v: 0, dens: 7.9, k: 0, hard: 0, melt: 1450, meltTo: MST, bag: true, desc: 'A cast bar of steel.' });
|
||||
def(ACORN, { name: 'Acorn', ph: PH_POWDER, col: [150, 104, 56], v: 22, dens: 0.9, k: 0.006, hard: 0, stick: 0.6, ign: 300, flame: 700, flameF: 900, fuel: 30, burnTo: ASH, ashP: 0.3, bag: true, desc: 'A tree seed. Left on dirt or grass under open sky, it sprouts and grows into a tree.' });
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@
|
||||
// Everything you build snaps to a grid of 4x4-grain blocks. The world itself
|
||||
// stays grain-level; only built pieces snap.
|
||||
const BS = 4;
|
||||
const BP_WALL = 1, BP_BELT = 2, BP_TUBE = 3, BP_SHAFT = 4, BP_GRATE = 5, BP_GATE = 6, BP_FILTER = 7, BP_PIPE = 8, BP_PUMP = 9, BP_OUTLET = 10, BP_GRIND = 11, BP_SAW = 12, BP_PRESS = 13, BP_RACK = 14;
|
||||
const BP_WALL = 1, BP_BELT = 2, BP_SCREW = 3, BP_SHAFT = 4, BP_GRATE = 5, BP_GATE = 6, BP_FILTER = 7, BP_PIPE = 8, BP_PUMP = 9, BP_OUTLET = 10, BP_GRIND = 11, BP_SAW = 12, BP_PRESS = 13, BP_RACK = 14;
|
||||
const BP_SLOPE_BL = 15, BP_SLOPE_BR = 16, BP_SLOPE_TL = 17, BP_SLOPE_TR = 18;
|
||||
// The shape is independent of the direction: reversing a belt never flips its face.
|
||||
const BP_BELT_UP = 19, BP_BELT_DOWN = 20, BP_FILTER_UP = 21, BP_FILTER_DOWN = 22;
|
||||
@@ -71,8 +71,8 @@ const PIECES = [
|
||||
desc: 'A solid block. Diagonal drags make a sloped face; T flips it. For one block, T cycles square and four slope corners. R picks the material: a square costs 16 grains or 2 bars; a slope costs 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. Drag diagonally to make a sloped belt that carries up or down.' },
|
||||
{ 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: 'screw', code: BP_SCREW, name: 'Screw conveyor', kind: 'block', cost: [[CASH, 25]], dirs: ['up', 'right', 'down', 'left', 'up-right', 'down-right', 'down-left', 'up-left'],
|
||||
desc: 'A turning screw inside a tube 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 screw conveyor. Join either end to screw conveyors 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. Drag diagonally to make a sloped filter that carries up or down.' },
|
||||
{ id: 'shaft', code: BP_SHAFT, name: 'Shaft', kind: 'block', cost: [[LWOOD, 8]], hidden: POWER_FREE,
|
||||
@@ -100,7 +100,7 @@ const PIECES = [
|
||||
{ id: 'fan', name: 'Fan', kind: 'machine', bw: 1, bh: 1, cost: [[CASH, 60]], dirs: ['right', 'down', 'left', 'up'],
|
||||
desc: 'Blows gas and forces air into fires, making them far hotter: its stream reaches 28 grains and passes through grates but not walls. Its copper body melts at 1085°C, so keep it outside the furnace, a block clear of the wall, blowing in through a grate. Press F to switch it on or off.' },
|
||||
{ id: 'silo', name: 'Silo', kind: 'machine', bw: 3, bh: 6, cost: [[CASH, 180]],
|
||||
desc: 'A steel bin on legs for one loose material: up to 4,000 grains go in through the hatch in its roof (drop them in from a belt or tube). Its gauge shows how full it is. Press F to open the gate in its cone and let it out underneath, about 60 grains a second; F again shuts it. The first material in decides what it holds until it is empty.' },
|
||||
desc: 'A steel bin on legs for one loose material: up to 4,000 grains go in through the hatch in its roof (drop them in from a belt or screw conveyor). Its gauge shows how full it is. Press F to open the gate in its cone and let it out underneath, about 60 grains a second; F again shuts it. The first material in decides what it holds until it is empty.' },
|
||||
{ id: 'engine', name: 'Steam engine', kind: 'machine', bw: 3, bh: 2, cost: [[BARCU, 10]], dirs: ['exhaust right', 'exhaust left'], hidden: POWER_FREE,
|
||||
desc: 'Steam rising into the intake slots on its underside drives it. Condensate drips out the exhaust side.' },
|
||||
{ id: 'dock', name: 'Trade dock', kind: 'machine', bw: 6, bh: 4, cost: [], hidden: true, fixed: true,
|
||||
@@ -117,8 +117,8 @@ PIECES.forEach((p, i) => { p.index = i; PIECE_BY_ID[p.id] = p; if (p.code) PIECE
|
||||
for (let c = BP_SLOPE_BL; c <= BP_SLOPE_TR; c++) PIECE_BY_CODE[c] = PIECE_BY_ID.wall;
|
||||
for (const c of [BP_BELT_UP, BP_BELT_DOWN]) PIECE_BY_CODE[c] = PIECE_BY_ID.belt;
|
||||
for (const c of [BP_FILTER_UP, BP_FILTER_DOWN]) PIECE_BY_CODE[c] = PIECE_BY_ID.filter;
|
||||
// power per grain of belt / tube (a block is 16 grains)
|
||||
const BELT_POWER = 0.03, TUBE_POWER = 0.125, FAN_POWER = 6;
|
||||
// power per grain of belt / screw (a block is 16 grains)
|
||||
const BELT_POWER = 0.03, SCREW_POWER = 0.125, FAN_POWER = 6;
|
||||
const WHEEL_K = 0.32, ENGINE_K = 40;
|
||||
|
||||
const MOLDS = {
|
||||
|
||||
+2
-2
@@ -30,14 +30,14 @@ const MILESTONES = [
|
||||
{ id: 'copper', title: 'Molten copper', msg: 'Molten copper sinks to the bottom of the charge. Get it into a mold before it sets.', hint: 'Copper ore melts at 1085°C. An open fire loses too much heat, so wall coal and ore in with blocks and leave a small vent at the top for smoke. Mix ore and coal about half and half: Ctrl+click coal and copper ore in the bag to blow a mix. Right click blows an even mix.' },
|
||||
{ id: 'bars', title: 'Copper bars', msg: 'Bars sell for far more than the ore in them, and make metal wall blocks.', hint: 'Build an Ingot mold (Build, 3) right under the furnace with a floor of clay Grates between them: molten copper drips through, while the charge and the thick slag stay up. When the bars set, they drop out of the bottom of the mold onto whatever is under it; a belt carries them off. Leave space below, or pick up the bars with the Pneumatic nozzle. 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. Copper that sets in the furnace melts again next firing.' },
|
||||
{ id: 'bit1', title: 'Copper drill bit', hint: 'Cast copper in a Bit mold. Building the furnace right on top works. A Grate over the funnel keeps the charge out. Let it set, then press F.', reward: 'Copper bit. You can now dig iron ore.' },
|
||||
{ id: 'auto', title: 'Moving parts', msg: 'Machines can now haul for you. Feed furnaces automatically, and lift ore out of your mines.', hint: 'Belts carry grains and bars resting on them. Screw tubes push loose material along (never liquids); stacked pointing up they lift it out of a mine. A Filter lets one material fall through and carries the rest along, so you can sort ore from rubble. Move 300 grains with machines.' },
|
||||
{ id: 'auto', title: 'Moving parts', msg: 'Machines can now haul for you. Feed furnaces automatically, and lift ore out of your mines.', hint: 'Belts carry grains and bars resting on them. Screw conveyors push loose material along (never liquids); stacked pointing up they lift it out of a mine. A Filter lets one material fall through and carries the rest along, so you can sort ore from rubble. Move 300 grains with machines.' },
|
||||
{ id: 'glass', title: 'Glass', hint: 'Sand melts near 1300°C. Still air will not get a fire that hot: point a Fan into the fuel. Molten glass runs through a grate like metal, so cast it in an Ingot mold for glass bricks, which sell well.', reward: 'Flask unlocked. The nozzle can suck up and blow out liquids.' },
|
||||
{ id: 'iron', title: 'Molten iron', msg: 'Iron is hard to keep molten. A brick mold right under the hot zone works best.', hint: 'Iron ore melts at 1538°C. That needs forced air, and stone slumps at 1250°C, so build the furnace from brick.' },
|
||||
{ id: 'bit2', title: 'Iron drill bit', hint: 'Cast iron in a Bit mold. A stone mold melts at iron temperatures, so build it from brick.', reward: 'Iron bit. Granite is now diggable.' },
|
||||
{ id: 'steel', title: 'Steel', msg: 'Cast it in a brick Bit mold for the best drill bit there is.', hint: 'Blow air through molten iron. Aim a Fan at a pool of it and the excess carbon burns off, turning it into steel.' },
|
||||
{ id: 'bit3', title: 'Steel drill bit', hint: 'Cast steel in a brick Bit mold.', reward: 'Steel bit. Basalt is now diggable.' },
|
||||
{ id: 'tank', title: 'Bigger bag', hint: 'Cast any metal in a Tank mold and knock it out to carry more.' },
|
||||
{ id: 'sale', title: 'First sale', msg: 'Money buys machines: belts, screw tubes and fans. Refined goods pay far more than raw.', hint: 'Blow, belt or lift goods into the trade dock near where you started. They are paid for the moment they drop through the grate in its floor.' },
|
||||
{ id: 'sale', title: 'First sale', msg: 'Money buys machines: belts, screw conveyors and fans. Refined goods pay far more than raw.', hint: 'Blow, belt or lift goods into the trade dock near where you started. They are paid for the moment they drop through the grate in its floor.' },
|
||||
{ id: 'order', title: 'First order filled', msg: 'Bigger buyers notice you as you fill more orders.', hint: 'Buyers post orders on the Market page: an amount of one good by a deadline, at well over market price. Goods dropped into the dock fill an open order first.' },
|
||||
{ id: 'alloy', title: 'An alloy', msg: 'Alloys sell for far more than the metals that went into them.', hint: 'Tin ore lies a little deeper than copper, zinc deeper again. Melt tin into a pool of molten copper and it mixes into bronze; zinc makes brass.' },
|
||||
{ id: 'precious', title: 'Precious metal', hint: 'Silver lies deep in the granite, gold deeper still. Their ore sells high even raw, and much higher as bars.' },
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
const SAVE_VERSION = 2;
|
||||
const TOFF = 64; // temperatures are saved with an offset so sub-zero snow and ice survive
|
||||
function serializeWorld() {
|
||||
// grains in flight from a tube or belt land where they are (rain in the air simply carries on)
|
||||
// grains in flight from a screw or belt land where they are (rain in the air simply carries on)
|
||||
for (const f of flyers) if (!f.bag && !f.rain) landFlyer(f, Math.max(0, Math.min(W - 1, Math.floor(f.x))), Math.max(0, Math.min(H - 1, Math.floor(f.y))));
|
||||
flyers = flyers.filter(f => f.bag || f.rain);
|
||||
const t16 = new Uint16Array(N);
|
||||
@@ -86,7 +86,7 @@ function deserializeWorld(meta, buffers) {
|
||||
const p = savedPart[i];
|
||||
if (p === P_WHEEL || p === P_INTAKE) continue;
|
||||
part[i] = p;
|
||||
if (IS_TUBE[p]) markPow(i, true);
|
||||
if (IS_SCREW[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 (IS_TUBE[part[i]]) return;
|
||||
if (IS_SCREW[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 = Array.from({ length: IS_TUBE.length }, () => []);
|
||||
let beltL = [], beltR = [], screwLists = Array.from({ length: IS_SCREW.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 = Array.from({ length: IS_TUBE.length }, () => []); brokenMachines = [];
|
||||
beltL = []; beltR = []; screwLists = Array.from({ length: IS_SCREW.length }, () => []); brokenMachines = [];
|
||||
ghostMachines = []; ghostBlocked = new Set();
|
||||
tick = 0; tickStamp = 1; drillPulses = []; lastPulseTick = -99; undoStack = []; undoRec = null;
|
||||
pipeNets = []; pipeNetAt = null; pipesDirty = true;
|
||||
|
||||
+5
-5
@@ -95,14 +95,14 @@ function doUndo() {
|
||||
}
|
||||
// right-click take-backs while the button is held count as one thing to undo
|
||||
function endRmbRecord() { if (GAME.rmbRec) { const r = GAME.rmbRec; GAME.rmbRec = null; const n = r.ops.length; r.label = `taking back ${plural(n, 'piece')}`; undoPush(r); } }
|
||||
// Belts, filters and tubes run the way you drag them; a single click uses the direction R sets
|
||||
// Belts, filters and screws run the way you drag them; a single click uses the direction R sets
|
||||
function dragDirFor(P, a, b) {
|
||||
if (!a || !P.dirs || !(P.id === 'belt' || P.id === 'tube' || P.filter)) return -1;
|
||||
if (!a || !P.dirs || !(P.id === 'belt' || P.id === 'screw' || P.filter)) return -1;
|
||||
const dx = b.bx - a.bx, dy = b.by - a.by;
|
||||
if (!dx && !dy) return -1;
|
||||
if ((P.id === 'belt' || P.filter) && diagonalDrag(dx, dy)) return dx > 0 ? 1 : 0;
|
||||
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);
|
||||
if (P.id === 'screw' && diagonalDrag(dx, dy)) return dx > 0 ? (dy < 0 ? 4 : 5) : (dy > 0 ? 6 : 7);
|
||||
if (P.id === 'screw') 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;
|
||||
}
|
||||
function blockLabel(P, rec) {
|
||||
@@ -165,7 +165,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) || isSlopeBelt(code) || code === BP_TUBE);
|
||||
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) || isSlopeBelt(code) || code === BP_SCREW);
|
||||
const dd = dragDirFor(P, GAME.drag, b);
|
||||
if (dd >= 0) GAME.pieceDir[P.id] = dd;
|
||||
const v = pieceV(P), ex = pieceExtra(P);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// ---------- Build menu ----------
|
||||
const BUILD_CATS = [
|
||||
['Structure', ['wall', 'grate', 'gate']],
|
||||
['Moving', ['belt', 'filter', 'tube']],
|
||||
['Moving', ['belt', 'filter', 'screw']],
|
||||
['Liquids', ['pipe', 'pump', 'outlet']],
|
||||
['Machines', ['fan', 'grinder', 'saw', 'press', 'rack', 'silo']],
|
||||
['Molds', ['ingotmold', 'bitmold', 'tankmold']],
|
||||
@@ -14,7 +14,7 @@ const PIECE_ICONS = {
|
||||
gate: '<rect x="3" y="5" width="18" height="14" rx="1"/><path d="M9 5v14"/><path d="M12 12h6M16 9.5l2.5 2.5-2.5 2.5"/>',
|
||||
belt: '<g transform="rotate(-45 12 12)"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M4 9h16M4 15h16"/><circle cx="6" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="18" cy="12" r="2"/></g>',
|
||||
filter: '<g transform="rotate(-45 12 12)"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M4 9h16M4 15h16M6 9v6M10 9v6M14 9v6M18 9v6M4 12h16"/></g>',
|
||||
tube: '<rect x="3" y="6" width="18" height="12" rx="2"/><path d="M6.5 18l3.5-12M11.5 18l3.5-12M16.5 18l3-10"/>',
|
||||
screw: '<rect x="3" y="6" width="18" height="12" rx="2"/><path d="M6.5 18l3.5-12M11.5 18l3.5-12M16.5 18l3-10"/>',
|
||||
pipe: '<path d="M2 10h20M2 14h20"/><path d="M6 7.5v9M18 7.5v9"/>',
|
||||
pump: '<circle cx="11" cy="14" r="6"/><path d="M11 14l3.5-2.5M11 14l-1 4.2M11 14l-4-1.2"/><path d="M15.5 9.5L20 5M16 5h4v4"/>',
|
||||
outlet: '<path d="M2 7h12a4 4 0 0 1 4 4v3"/><path d="M2 11h10a2 2 0 0 1 2 2v1"/><path d="M15 18v1.5M18 20v1.5M12 20v1.5"/>',
|
||||
@@ -29,9 +29,9 @@ const PIECE_ICONS = {
|
||||
tankmold: '<rect x="5" y="7" width="14" height="13" rx="3"/><path d="M9 4h6v3M8.5 12h7"/>',
|
||||
};
|
||||
function pieceIcon(id) {
|
||||
const v = GAME.drag && GAME.dragDir >= 0 ? GAME.dragDir : GAME.pieceDir.tube || 0;
|
||||
const v = GAME.drag && GAME.dragDir >= 0 ? GAME.dragDir : GAME.pieceDir.screw || 0;
|
||||
const body = PIECE_ICONS[id] || '';
|
||||
const icon = id === 'tube' && v >= 4 ? `<g transform="rotate(${v === 4 || v === 6 ? -45 : 45} 12 12)">${body}</g>` : body;
|
||||
const icon = id === 'screw' && v >= 4 ? `<g transform="rotate(${v === 4 || v === 6 ? -45 : 45} 12 12)">${body}</g>` : body;
|
||||
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round" aria-hidden="true">${icon}</svg>`;
|
||||
}
|
||||
function catOf(id) { const c = BUILD_CATS.findIndex(([, ids]) => ids.includes(id)); return c < 0 ? 0 : c; }
|
||||
@@ -49,7 +49,7 @@ function cycleBuildCat(d) {
|
||||
}
|
||||
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
|
||||
// while dragging a line of belts, filters or screws, the direction is the drag's
|
||||
const dragging = GAME.drag && GAME.dragDir >= 0 && P.dirs;
|
||||
const glyph = d => ({ left: '←', right: '→', up: '↑', down: '↓', 'up-right': '↗', 'down-right': '↘', 'down-left': '↙', 'up-left': '↖' })[d] || '';
|
||||
const code = pieceCode(P), direction = dragging ? GAME.dragDir : GAME.pieceDir[P.id] || 0;
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ 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 (IS_TUBE[p]) t1 = 'Screw tube ' + PIECE_BY_ID.tube.dirs[TUBE_PARTS.indexOf(p)] + (m !== EMPTY ? ` · ${MD[m].name}` : '');
|
||||
} else if (IS_SCREW[p]) t1 = 'Screw conveyor ' + PIECE_BY_ID.screw.dirs[SCREW_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) { const owner = slopeSealOwner(i), fb = owner >= 0 ? owner : (ay >> 2) * BW + (ax >> 2); t1 = 'Belt ' + beltDirection(blockP[fb], blockV[fb]); }
|
||||
else if (m === PIPE && isPipeCode(b)) {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ window.addEventListener('keydown', e => {
|
||||
case 'f': doInteract(); break;
|
||||
case 'g': GAME.replace = true; hudDirty = true; break;
|
||||
case 'h': toggleLens(); break;
|
||||
case 'i': GAME.infoOverlay = !GAME.infoOverlay; playSfx('ui'); toast(GAME.infoOverlay ? 'Info overlay on: what every filter does, and which way belts and tubes run. I hides it.' : 'Info overlay off.'); break;
|
||||
case 'i': GAME.infoOverlay = !GAME.infoOverlay; playSfx('ui'); toast(GAME.infoOverlay ? 'Info overlay on: what every filter does, and which way belts and screw conveyors run. I hides it.' : 'Info overlay off.'); break;
|
||||
case 'l': bot.lamp = !bot.lamp; playSfx('ui'); break;
|
||||
case 'n': openNotebook(); break;
|
||||
case 'b': openNotebook('Blueprints'); break;
|
||||
|
||||
+4
-4
@@ -60,7 +60,7 @@ function controlsHtml() {
|
||||
['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'],
|
||||
['Tools', '1 – 4'], ['Select one to blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall: cycle shape · flip diagonal drag', 'T'], ['Wall or mold material menu (Build)', 'F'],
|
||||
['Pick up selection or what you click (Pneumatic nozzle)', 'Left drag · Shift: everything'], ['Select (Pneumatic nozzle)', 'Click a bag name · Q/E'], ['Add to selection (Pneumatic nozzle)', 'Ctrl+click a bag name'], ['Show everything found (Pneumatic nozzle)', 'Hold Ctrl in the bag'], ['Clear selection (Pneumatic nozzle)', 'Clear in the bag'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
|
||||
['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and tube directions', 'I'], ['Bot lamp', 'L'],
|
||||
['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and screw conveyor directions', 'I'], ['Bot lamp', 'L'],
|
||||
['Pick the piece under the cursor (with its direction and settings)', 'Middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'Shift + M'], ['Full map', 'M'], ['Minimap zoom', '+ / − on minimap'],
|
||||
];
|
||||
const pad = [
|
||||
@@ -71,7 +71,7 @@ function controlsHtml() {
|
||||
const tools = [
|
||||
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
|
||||
['2 Pneumatic nozzle', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
|
||||
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps, outlets, Grinders, Saws, Brick presses and Bar racks, machines (fans and silos) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. A diagonal wall drag makes a sealed sloped face, solid below; T flips it. For one wall block, T cycles square and four slope corners. Diagonal belt and filter drags make sloped runs that carry up or down, following the drag. Their faces always point up. Right click takes a piece back. Your bot flies through anything you build.'],
|
||||
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, screw conveyors, filters, grates, gates, pipes, pumps, outlets, Grinders, Saws, Brick presses and Bar racks, machines (fans and silos) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. A diagonal wall drag makes a sealed sloped face, solid below; T flips it. For one wall block, T cycles square and four slope corners. Diagonal belt and filter drags make sloped runs that carry up or down, following the drag. Their faces always point up. Right click takes a piece back. Your bot flies through anything you build.'],
|
||||
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
|
||||
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
|
||||
];
|
||||
@@ -84,8 +84,8 @@ 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. 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>
|
||||
Screw conveyors 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 conveyors 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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ function drawPastePreview() {
|
||||
ctx.strokeStyle = 'rgba(92,186,166,0.85)'; ctx.setLineDash([zp, zp]); ctx.lineWidth = Math.max(1, zp * 0.3);
|
||||
ctx.strokeRect(x, y, w, h); ctx.setLineDash([]);
|
||||
const planned = new Map(bp.blocks.map(([dx, dy, code, v]) => [(oy + dy) * BW + ox + dx, [code, v]]));
|
||||
for (const [dx, dy, code, v] of bp.blocks) { const [bx2, by2, bw2, bh2] = blockRectPx(ox + dx, oy + dy, 1, 1); ctx.fillStyle = `rgba(${previewColor(code, v)},0.5)`; previewBlock(code, bx2, by2, bw2, bh2, false, code === BP_TUBE ? v : -1, ox + dx, oy + dy, planned); }
|
||||
for (const [dx, dy, code, v] of bp.blocks) { const [bx2, by2, bw2, bh2] = blockRectPx(ox + dx, oy + dy, 1, 1); ctx.fillStyle = `rgba(${previewColor(code, v)},0.5)`; previewBlock(code, bx2, by2, bw2, bh2, false, code === BP_SCREW ? v : -1, ox + dx, oy + dy, planned); }
|
||||
for (const [type, dx, dy] of bp.machines) { const P = PIECE_BY_ID[type]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, P.bw, P.bh); ctx.strokeStyle = 'rgba(255,210,122,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
||||
for (const [k, dx, dy] of bp.molds) { const md = MOLDS[k]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, md.bw, md.bh); ctx.strokeStyle = 'rgba(255,190,90,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
||||
ctx.restore();
|
||||
|
||||
+10
-10
@@ -17,7 +17,7 @@ function previewColor(code, v) {
|
||||
if (isWall(code)) { const m = WALL_OF[v]; return `${COLR[m]},${COLG[m]},${COLB[m]}`; }
|
||||
if (isFilter(code)) return '96,100,110';
|
||||
if (isBelt(code)) return '110,114,124';
|
||||
return { [BP_BELT]: '110,114,124', [BP_TUBE]: '120,132,150', [BP_SHAFT]: '160,118,70', [BP_GRATE]: '170,90,66', [BP_GATE]: '160,84,64', [BP_PIPE]: '176,112,72', [BP_PUMP]: '104,122,142', [BP_OUTLET]: '170,106,66' }[code] || '120,200,190';
|
||||
return { [BP_BELT]: '110,114,124', [BP_SCREW]: '120,132,150', [BP_SHAFT]: '160,118,70', [BP_GRATE]: '170,90,66', [BP_GATE]: '160,84,64', [BP_PIPE]: '176,112,72', [BP_PUMP]: '104,122,142', [BP_OUTLET]: '170,106,66' }[code] || '120,200,190';
|
||||
}
|
||||
// Air swirls into the pickup ring or spreads from the nozzle toward the cursor.
|
||||
function drawAirStreaks(ax, ay, p, r, blowing) {
|
||||
@@ -105,12 +105,12 @@ 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) || isSlopeBelt(code) || code === BP_TUBE)) : [[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) || isSlopeBelt(code) || code === BP_SCREW)) : [[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);
|
||||
const afford = canAfford(pieceCostNow(P), cells.length);
|
||||
const planned = code === BP_TUBE && dIdx >= 4 ? new Map(cells.map(([x, y]) => [y * BW + x, [BP_TUBE, dIdx]])) : null;
|
||||
const planned = code === BP_SCREW && dIdx >= 4 ? new Map(cells.map(([x, y]) => [y * BW + x, [BP_SCREW, dIdx]])) : null;
|
||||
for (const [bx, by] of cells) {
|
||||
const err = canPlaceBlock(bx, by, code), rb = by * BW + bx;
|
||||
const swap = GAME.replace && !!blockP[rb] && /already built/.test(err) && !blockIs(rb, code, v, pieceExtra(P));
|
||||
@@ -118,13 +118,13 @@ function drawPreviews() {
|
||||
const ok = (!err || swap) && !ghost && afford;
|
||||
const [x, y, w, h] = blockRectPx(bx, by, 1, 1);
|
||||
ctx.fillStyle = ghost ? 'rgba(90,190,230,0.22)' : ok ? (swap ? 'rgba(255,190,90,0.5)' : `rgba(${col},0.55)`) : 'rgba(239,106,85,0.35)';
|
||||
previewBlock(code, x, y, w, h, false, P.id === 'tube' ? dIdx : -1, bx, by, planned);
|
||||
previewBlock(code, x, y, w, h, false, P.id === 'screw' ? dIdx : -1, bx, by, planned);
|
||||
ctx.strokeStyle = ghost ? 'rgba(90,190,230,0.8)' : ok ? 'rgba(236,226,210,0.8)' : 'rgba(239,106,85,0.9)'; ctx.lineWidth = Math.max(1, zp * 0.25);
|
||||
if (ghost) ctx.setLineDash([zp, zp * 0.7]);
|
||||
previewBlock(code, x, y, w, h, true, P.id === 'tube' ? dIdx : -1, bx, by, planned); ctx.setLineDash([]);
|
||||
if (P.dirs && (P.id === 'belt' || P.id === 'tube' || P.filter || P.id === 'pump' || P.id === 'outlet')) {
|
||||
previewBlock(code, x, y, w, h, true, P.id === 'screw' ? dIdx : -1, bx, by, planned); ctx.setLineDash([]);
|
||||
if (P.dirs && (P.id === 'belt' || P.id === 'screw' || 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] = isSlopeBelt(code) ? [dIdx ? 1 : -1, beltGrade(code) * (dIdx ? 1 : -1)] : P.id === 'tube' ? TUBE_DIRS[dIdx] : P.id === 'pump' ? PUMP_V[dIdx] : P.id === 'outlet' ? OUTLET_V[dIdx] : [[-1, 0], [1, 0]][dIdx];
|
||||
const [dx, dy] = isSlopeBelt(code) ? [dIdx ? 1 : -1, beltGrade(code) * (dIdx ? 1 : -1)] : P.id === 'screw' ? SCREW_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(); };
|
||||
@@ -180,12 +180,12 @@ function drawPreviews() {
|
||||
|
||||
function previewBlock(code, x, y, w, h, stroke = false, direction = -1, bx = -1, by = -1, planned = null) {
|
||||
const ctx = R.ctx;
|
||||
if (code === BP_TUBE && direction >= 4) {
|
||||
const joins = bx >= 0 ? diagonalTubeJoins(bx, by, direction, planned) : 0;
|
||||
if (code === BP_SCREW && direction >= 4) {
|
||||
const joins = bx >= 0 ? diagonalScrewJoins(bx, by, direction, planned) : 0;
|
||||
const points = [];
|
||||
for (let end = 0; end < 2; end++) {
|
||||
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
|
||||
const d = join ? TUBE_DIRS[join - 1] : [side, side * (direction === 4 || direction === 6 ? -1 : 1)];
|
||||
const d = join ? SCREW_DIRS[join - 1] : [side, side * (direction === 4 || direction === 6 ? -1 : 1)];
|
||||
points.push([d[0] * w / 2, d[1] * h / 2]);
|
||||
}
|
||||
const r = w / 2, a = points[0], b = points[1];
|
||||
|
||||
Reference in New Issue
Block a user