Sloped belts hold their load with moving cleats (t42); sloped filters are downhill shaking screens (t43)
The magic grip is gone: cleats every 8 face cells, 6 grains tall, carry pockets uphill (about 71 grains/s against 90 flat) and overflow rolls back. Sloped filters always run downhill; grains travel by gravity and a shake, and sorting stays exact. Drawing needs another pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
"sim/pipes.js",
|
||||
"sim/machine-tick.js",
|
||||
"sim/conveyors.js",
|
||||
"sim/slope-conveyors.js",
|
||||
"sim/blocks.js",
|
||||
"sim/molds.js",
|
||||
"sim/items.js",
|
||||
|
||||
@@ -13,6 +13,7 @@ vec3 slopeCorner(vec2 p, vec2 a, float origin) {
|
||||
return vec3(radius - 1.85, origin + d.x, 2.0 + d.y);
|
||||
}
|
||||
vec3 slopeShape(vec2 p, vec2 worldOrigin, float grade, int joins) {
|
||||
if ((joins & 64) != 0) p.y -= 0.45;
|
||||
float sy = grade < 0.0 ? 4.0 : 0.0;
|
||||
vec2 a = vec2(-grade * 1.30814755, sy + 1.30814755), b = vec2(4.0 - grade * 1.30814755, 4.0 - sy + 1.30814755);
|
||||
if ((joins & 1) != 0) { a.y = sy + 2.0; a.x = (a.y - sy - 2.61629509) / grade; }
|
||||
@@ -52,11 +53,11 @@ const SM_SLOPE_DRAW = `
|
||||
vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w);
|
||||
vec3 shapePos = slopeShape(local, w - local, grade, joins);
|
||||
float sd = shapePos.x, axis = shapePos.y, depth = shapePos.z;
|
||||
float time = (joins & 16) != 0 ? uTime : 0.0;
|
||||
float time = !mesh && (joins & 16) != 0 ? uTime : 0.0;
|
||||
vec2 lb = vec2(axis, depth), alongWorld = vec2(axis, depth);
|
||||
float rg = step(0.72, fract((axis - dir * (depth < 2.0 ? 1.0 : -1.0) * time * 30.0) * 0.5));
|
||||
vec3 belt;
|
||||
if (-sd < 0.8) {
|
||||
if (-sd < (mesh ? 1.1 : 0.8)) {
|
||||
belt = bandColor(lb, rg);
|
||||
if (mesh && ((joins & 32) != 0) == (depth < 2.0) && dot(own.rgb, vec3(1)) > 0.01)
|
||||
belt = mix(belt, own.rgb * (depth < 2.0 ? 1.0 : 0.8), 0.75 * rg);
|
||||
|
||||
@@ -42,8 +42,8 @@ function prepareSlopeBelts(x0, y0, x1, y1) {
|
||||
if (c === BP_BELT || c === BP_FILTER) beltCaps[b] = flatBeltCaps(b);
|
||||
if (!isSlopeBelt(c)) continue;
|
||||
const face = beltFaceY(c, bx, by, bx * BS) * W + bx * BS;
|
||||
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0);
|
||||
for (let yy = Math.max(0, by - 1); yy <= Math.min(BH - 1, by + 1); yy++)
|
||||
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0) | (isFilter(c) && netSat(face) > 0 && screenLoaded(b) && (tick & 2) ? 64 : 0);
|
||||
for (let yy = Math.max(0, by - 2); yy <= Math.min(BH - 1, by + 1); yy++)
|
||||
for (let xx = Math.max(0, bx - 1); xx <= Math.min(BW - 1, bx + 1); xx++) slopeBeltHalo[yy * BW + xx] = 1;
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ function slopeBeltCorner(x, y, ax, ay, origin) {
|
||||
function slopeBeltShape(x, y, b, joins = slopeBeltJoins[b], code = blockP[b]) {
|
||||
const grade = beltGrade(code), ox = (b % BW) * BS, oy = ((b / BW) | 0) * BS;
|
||||
x -= ox; y -= oy;
|
||||
if (isFilter(code) && (joins & 64)) y -= 0.45;
|
||||
const offset = 1.85 * Math.SQRT2, sy = grade < 0 ? 4 : 0;
|
||||
const normal = 1.85 * Math.SQRT1_2;
|
||||
let ax = -grade * normal, ay = sy + normal, bx = 4 - grade * normal, by = 4 - sy + normal;
|
||||
@@ -107,9 +108,9 @@ function slopeBeltColor(b, x, y, anim, br, bg, bb) {
|
||||
if (sd > 1e-8) return (br | 0) | ((bg | 0) << 8) | ((bb | 0) << 16);
|
||||
const c = blockP[b], joins = slopeBeltJoins[b], mesh = isFilter(c);
|
||||
const dir = mesh ? (blockV[b] & 128 ? 1 : -1) : (blockV[b] ? 1 : -1);
|
||||
const a = slopeAlong, d = slopeAcross, time = joins & 16 ? anim : 0;
|
||||
const a = slopeAlong, d = slopeAcross, time = !mesh && joins & 16 ? anim : 0;
|
||||
let r = 38, g = 41, blue = 46;
|
||||
if (-sd < 0.8) {
|
||||
if (-sd < (mesh ? 1.1 : 0.8)) {
|
||||
const stripe = (Math.floor((a - dir * (d < 2 ? 1 : -1) * (time >> 1)) / 2) & 1), k = d < 2 ? 1 : 0.72;
|
||||
r = (stripe ? 92 : 56) * k; g = (stripe ? 96 : 58) * k; blue = (stripe ? 104 : 66) * k;
|
||||
} else if (mesh) {
|
||||
@@ -125,3 +126,25 @@ function slopeBeltColor(b, x, y, anim, br, bg, bb) {
|
||||
}
|
||||
return (r | 0) | ((g | 0) << 8) | ((blue | 0) << 16);
|
||||
}
|
||||
|
||||
function screenLoaded(b) {
|
||||
const bx = b % BW, by = (b / BW) | 0, c = blockP[b];
|
||||
for (let x = bx * BS; x < bx * BS + BS; x++) {
|
||||
const i = (beltFaceY(c, bx, by, x) - 1) * W + x;
|
||||
if (i >= 0 && (PH[mat[i]] === PH_POWDER || occ[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Far sidewalls stay behind the load. The cleat raster is the simulation barrier raster.
|
||||
function slopeDetailAt(i, x, y) {
|
||||
if (!slopeBeltHalo[(y >> 2) * BW + (x >> 2)] && !cleatMask[i]) return 0;
|
||||
if (occ[i] || mach[i] || (mat[i] !== EMPTY && PH[mat[i]] !== PH_GAS) || part[i]) return 0;
|
||||
if (cleatMask[i]) return 1;
|
||||
for (let by = y >> 2; by <= Math.min(BH - 1, (y + 9) >> 2); by++) {
|
||||
const b = by * BW + (x >> 2), c = blockP[b];
|
||||
if (!isSlopeBelt(c)) continue;
|
||||
const depth = beltFaceY(c, x >> 2, by, x) - y;
|
||||
if (isFilter(c) ? depth === 2 : depth >= 6 && depth <= 7 + ((x + slopePhase[b]) & 1)) return isFilter(c) ? 3 : 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -289,6 +289,12 @@ function drawWorld() {
|
||||
const color = slopeBeltColor(sb, x, y, anim, br, bgc, bb);
|
||||
r = color & 255; g = color >> 8 & 255; b = color >> 16 & 255;
|
||||
}
|
||||
const slopeDetail = slopeDetailAt(i, x, y);
|
||||
if (slopeDetail) {
|
||||
r = slopeDetail === 1 ? 145 : slopeDetail === 3 ? 96 : 48;
|
||||
g = slopeDetail === 1 ? 125 : slopeDetail === 3 ? 106 : 52;
|
||||
b = slopeDetail === 1 ? 94 : slopeDetail === 3 ? 120 : 60;
|
||||
}
|
||||
// incandescence
|
||||
if (T > 450 && m !== EMPTY && incand) {
|
||||
const gi = glowIndex(PH[m] === PH_SOLID ? T + nz * 0.9 : m === MAGMA ? T + Math.sin(x * 0.19 + y * 0.11 + anim * 0.03 + nz * 0.04) * 55 - 30 : T), w = GLOW_W[gi];
|
||||
@@ -339,6 +345,7 @@ function drawWorld() {
|
||||
((y - ((sb / BW) | 0) * BS + 8) << 16) | ((x - (sb % BW) * BS + 8) << 8);
|
||||
if (slopeArt) { r = fm ? COLR[fm] : 0; g = fm ? COLG[fm] : 0; b = fm ? COLB[fm] : 0; }
|
||||
}
|
||||
if (slopeDetail) cl = 4;
|
||||
top = cl << 24;
|
||||
fpx[row + bx] = (tb ? 0xff000000 : 0) | (cl === 5 ? 0xff0000 : 0) | (cl === 3 ? 0xff00 : 0) | (cl === 1 && !builtForLight(i, x, y) ? 0xff : 0);
|
||||
}
|
||||
|
||||
+3
-3
@@ -68,7 +68,7 @@ function placeBlock(code, v, bx, by, free, extra) {
|
||||
}
|
||||
if (isPipeCode(code)) pipesDirty = true;
|
||||
blockP[by * BW + bx] = code; blockV[by * BW + bx] = v;
|
||||
if (isFilter(code)) { blockV[by * BW + bx] = v & 128; filterCfg.set(by * BW + bx, filterRec(extra)); filtersDirty = true; }
|
||||
if (isFilter(code)) { blockV[by * BW + bx] = filterVariant(code, v); filterCfg.set(by * BW + bx, filterRec(extra)); filtersDirty = true; }
|
||||
if (isProcCode(code)) procDirty = true;
|
||||
if (code === BP_RACK) {
|
||||
const b = by * BW + bx, s = newRack();
|
||||
@@ -197,7 +197,7 @@ function blockInReach(bx, by) { return inReach(bx * BS + BS / 2, by * BS + BS /
|
||||
// Is block b already exactly this piece (same kind, variant and, for a filter, settings)?
|
||||
function blockIs(b, code, v, extra) {
|
||||
if (blockP[b] !== code) return false;
|
||||
if (isFilter(code)) return (blockV[b] & 128) === (v & 128) && sameFilter(filterInfo(b), cleanFilterCfg(extra));
|
||||
if (isFilter(code)) return (blockV[b] & 128) === filterVariant(code, v) && sameFilter(filterInfo(b), cleanFilterCfg(extra));
|
||||
return blockV[b] === v;
|
||||
}
|
||||
// replace: a block already built in the way is taken down (and refunded) to make room
|
||||
@@ -208,7 +208,7 @@ function placeBlocks(code, v, list, extra, replace) {
|
||||
const b = by * BW + bx;
|
||||
if (replace && blockP[b]) {
|
||||
if (blockIs(b, code, v, extra)) continue;
|
||||
if (blockP[b] === code && isFilter(code)) { undoNote({ t: 'filter', bx, by, v: blockV[b], extra: filterInfo(b) }); blockV[b] = v & 128; setFilter(b, extra); filtersDirty = true; n++; continue; } // just re-set it
|
||||
if (blockP[b] === code && isFilter(code)) { undoNote({ t: 'filter', bx, by, v: blockV[b], extra: filterInfo(b) }); blockV[b] = filterVariant(code, v); setFilter(b, extra); filtersDirty = true; n++; continue; } // just re-set it
|
||||
if (!canAfford(pieceCost(code, v), 1)) { last = needText(pieceCost(code, v), 1); break; }
|
||||
removeBlock(bx, by);
|
||||
}
|
||||
|
||||
+16
-6
@@ -36,7 +36,7 @@ function move(i, j, x, y, nx, ny) {
|
||||
// Can a grain of material m (phase ph) enter cell j moving by (dx,dy)?
|
||||
// 0 = no, 1 = into empty, 2 = by displacing what is there.
|
||||
function enter(m, ph, j, dx, dy) {
|
||||
if (occ[j]) return 0;
|
||||
if (occ[j] || (ph === PH_POWDER && !cleatCarrying && cleatCells.length && (cleatMask[j] || (dy > 0 && cleatMask[j - dx - dy * W])))) return 0;
|
||||
const p = part[j];
|
||||
if (p !== 0) {
|
||||
if (IS_SCREW[p]) { if (ph === PH_POWDER && SCREW_DX[p] === -dx && SCREW_DY[p] === -dy) return 0; } // liquids and gases flow through freely
|
||||
@@ -72,20 +72,30 @@ let LSTEP = 8, LFILM = 4;
|
||||
let CORNER_SEAL = true;
|
||||
function powder(i, x, y, m) {
|
||||
if (y >= H - 1) return;
|
||||
const b = i + W;
|
||||
const b = i + W, barriers = cleatCells.length;
|
||||
if (enter(m, PH_POWDER, b, 0, 1)) { move(i, b, x, y, x, y + 1); return; }
|
||||
// a grain at least twice as dense works its way down through a loose pile. Unlit fuel
|
||||
// packs too tight for that, so a mixed furnace charge stays mixed until the fire gets in.
|
||||
const t = mat[b];
|
||||
if (PH[t] === PH_POWDER && DENS[m] >= DENS[t] * 2 && (!FUEL[t] || (flags[b] & F_BURN)) && stamp[b] !== tickStamp && !occ[b] && part[b] !== P_GRATE && part[b] !== P_FILTER && rnd() < (FUEL[t] ? SORTP_FUEL : SORTP)) { move(i, b, x, y, x, y + 1); awake[(y >> 5) * CW + (x >> 5)] = 40; return; }
|
||||
if (PH[t] === PH_POWDER && (!barriers || (!cleatMask[i] && !cleatMask[b])) && DENS[m] >= DENS[t] * 2 && (!FUEL[t] || (flags[b] & F_BURN)) && stamp[b] !== tickStamp && !occ[b] && part[b] !== P_GRATE && part[b] !== P_FILTER && rnd() < (FUEL[t] ? SORTP_FUEL : SORTP)) { move(i, b, x, y, x, y + 1); awake[(y >> 5) * CW + (x >> 5)] = 40; return; }
|
||||
// a grain lying right on a belt or filter is held by it: the surface carries it, it does not slide off the edge
|
||||
if (t === BELTL || t === BELTR || part[b] === P_FILTER) return;
|
||||
if ((t === BELTL || t === BELTR || part[b] === P_FILTER) &&
|
||||
!isSlopeBelt(blockP[((y + 1) >> 2) * BW + (x >> 2)])) return;
|
||||
// Feed a receiving screw mouth sideways instead of falling under its bottom casing.
|
||||
if (t === BELTL || t === BELTR) {
|
||||
const b = ((y + 1) >> 2) * BW + (x >> 2);
|
||||
const d = isSlopeBelt(blockP[b]) ? (blockV[b] & 1 ? 1 : -1) : 0;
|
||||
if (d && x + d >= 0 && x + d < W && IS_SCREW[part[i + d]]) {
|
||||
if (enter(m, PH_POWDER, i + d, d, 0)) move(i, i + d, x, y, x + d, y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (rnd() < STICK[m]) {
|
||||
const d = (RS & 2) ? 1 : -1;
|
||||
let nx = x + d;
|
||||
if (nx >= 0 && nx < W && (!CORNER_SEAL || PH[mat[b]] !== PH_SOLID || PH[mat[i + d]] !== PH_SOLID) && enter(m, PH_POWDER, b + d, d, 1)) { move(i, b + d, x, y, nx, y + 1); return; }
|
||||
if (nx >= 0 && nx < W && (!CORNER_SEAL || (PH[mat[b]] !== PH_SOLID && (!barriers || !cleatMask[b])) || (PH[mat[i + d]] !== PH_SOLID && (!barriers || !cleatMask[i + d]))) && enter(m, PH_POWDER, b + d, d, 1)) { move(i, b + d, x, y, nx, y + 1); return; }
|
||||
nx = x - d;
|
||||
if (nx >= 0 && nx < W && (!CORNER_SEAL || PH[mat[b]] !== PH_SOLID || PH[mat[i - d]] !== PH_SOLID) && enter(m, PH_POWDER, b - d, -d, 1)) { move(i, b - d, x, y, nx, y + 1); return; }
|
||||
if (nx >= 0 && nx < W && (!CORNER_SEAL || (PH[mat[b]] !== PH_SOLID && (!barriers || !cleatMask[b])) || (PH[mat[i - d]] !== PH_SOLID && (!barriers || !cleatMask[i - d]))) && enter(m, PH_POWDER, b - d, -d, 1)) { move(i, b - d, x, y, nx, y + 1); return; }
|
||||
}
|
||||
}
|
||||
function passable(m, ph, j, dx, dy) {
|
||||
|
||||
+9
-14
@@ -19,11 +19,11 @@ function conveyorStep(x, y, d) {
|
||||
const b = (y >> 2) * BW + (x >> 2);
|
||||
return beltGrade(blockP[b]) * d;
|
||||
}
|
||||
function carryColumn(a, d, pass) {
|
||||
function carryColumn(a, d, pass, depth = CARRY_DEPTH) {
|
||||
if (a < 0 || mat[a] === EMPTY) return;
|
||||
const sy = ((a / W) | 0) + 1, step = conveyorStep(a % W, sy, d);
|
||||
let blocked = false;
|
||||
for (let L = 0; L < CARRY_DEPTH; L++, a -= W) {
|
||||
for (let L = 0; L < depth; L++, a -= W) {
|
||||
if (a < 0) return;
|
||||
const m = mat[a];
|
||||
if (m === EMPTY || (PH[m] === PH_SOLID && !part[a])) return; // the top of the heap, or a ceiling
|
||||
@@ -53,14 +53,6 @@ function carryColumn(a, d, pass) {
|
||||
stats.moved++;
|
||||
}
|
||||
}
|
||||
// The running face grips its whole supported load, including on the ticks between steps.
|
||||
function holdSlopeColumn(a, pass) {
|
||||
for (let depth = 0; depth < CARRY_DEPTH && a >= 0; depth++, a -= W) {
|
||||
const m = mat[a];
|
||||
if (m === EMPTY || (PH[m] === PH_SOLID && !part[a])) return;
|
||||
if (PH[m] === PH_POWDER && !occ[a] && !(pass && pass[m])) stamp[a] = tickStamp;
|
||||
}
|
||||
}
|
||||
// Belts are perforated: liquid resting on one drips through to whatever open space is under it.
|
||||
function beltDrain(b) {
|
||||
const a = b - W, x = b % W;
|
||||
@@ -93,8 +85,8 @@ function filterCarry(b, d, carrying = true) {
|
||||
for (let k = 0; k < BS; k++) {
|
||||
const x = d > 0 ? bx * BS + BS - 1 - k : bx * BS + k, top = beltFaceY(code, bx, by, x) * W + x;
|
||||
if (part[top] === P_FILTER) {
|
||||
if (carrying) carryColumn(top - W, d, pass);
|
||||
if (isSlopeBelt(code)) holdSlopeColumn(top - W, pass);
|
||||
if (isSlopeBelt(code)) screenNudge(top, d, pass);
|
||||
else if (carrying) carryColumn(top - W, d, pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,9 +106,12 @@ function carryBeltsAndFilters(belts, filters, d, bm, carrying) {
|
||||
if (above === BELTL || above === BELTR || netSat(b) <= 0) continue;
|
||||
if (carrying) {
|
||||
if (PH[above] === PH_LIQUID) beltDrain(b);
|
||||
carryColumn(b - W, d, null);
|
||||
if (isSlopeBelt(code)) {
|
||||
cleatCarrying = true;
|
||||
carryColumn(b - W, d, null, pocketDepth(x, (y >> 2) * BW + (x >> 2)));
|
||||
cleatCarrying = false;
|
||||
} else carryColumn(b - W, d, null);
|
||||
}
|
||||
if (isSlopeBelt(code)) holdSlopeColumn(b - W, null);
|
||||
}
|
||||
}
|
||||
// Change what a filter does; grains inside it that no longer pass are pushed out on top.
|
||||
|
||||
+4
-4
@@ -134,12 +134,12 @@ function entDiagonalScrewTick(e, p) {
|
||||
function entSlopeBeltTick(e) {
|
||||
const bottom = e.iy + BAR_H;
|
||||
if (bottom >= H) return false;
|
||||
let d = 0, slope = false;
|
||||
let d = 0, slope = false, screen = false;
|
||||
for (let x = e.ix; x < e.ix + BAR_W; x++) {
|
||||
const i = bottom * W + x, b = (bottom >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if ((isBelt(c) || isFilter(c)) && entSolidCell(i)) {
|
||||
d = isFilter(c) ? (blockV[b] & 128 ? 1 : -1) : (blockV[b] ? 1 : -1);
|
||||
if (isSlopeBelt(c)) slope = true;
|
||||
if (isSlopeBelt(c)) { slope = true; if (isFilter(c)) screen = true; }
|
||||
}
|
||||
}
|
||||
if (!d) return false;
|
||||
@@ -150,12 +150,12 @@ function entSlopeBeltTick(e) {
|
||||
const b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if (!(isBelt(c) || isFilter(c)) || (isFilter(c) && filterPassAt(e.m, y * W + x))) continue;
|
||||
if (beltFaceY(c, x >> 2, y >> 2, x) !== y) continue;
|
||||
if (isSlopeBelt(c)) slope = true;
|
||||
if (isSlopeBelt(c)) { slope = true; if (isFilter(c)) screen = true; }
|
||||
face = Math.min(face, y);
|
||||
}
|
||||
if (!slope) return false;
|
||||
e.x = e.ix; e.y = e.iy; e.vx = e.vy = 0;
|
||||
if (tick & 1) return true;
|
||||
if (screen ? tick % SCREEN_NUDGE_TICKS : 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 (!entScrewEnter(e, d, 0)) entScrewEnter(e, d, Math.sign(y - e.iy), true);
|
||||
|
||||
@@ -87,10 +87,11 @@ function machinesTick() {
|
||||
mm.hot = Math.round(hotT);
|
||||
}
|
||||
}
|
||||
// Half a grain per tick; sloped faces grip their load on the intervening ticks too.
|
||||
// Belts step every other tick; sloped belts carry only their finite pockets.
|
||||
if (filtersDirty) rebuildFilters();
|
||||
carryBeltsAndFilters(beltL, filterL, -1, BELTL, (tick & 1) === 0);
|
||||
carryBeltsAndFilters(beltR, filterR, 1, BELTR, (tick & 1) === 0);
|
||||
slopeCleatsTick();
|
||||
// screws: every loose grain inside steps one cell along, front of the line first, on the same
|
||||
// beat as belts (every other tick), so a block-wide screw carries as much as a block-deep belt
|
||||
if ((tick & 1) === 0) for (let p = 1; p < IS_SCREW.length; p++) {
|
||||
|
||||
+4
-3
@@ -14,8 +14,9 @@ function isBelt(c) { return c === BP_BELT || c === BP_BELT_UP || c === BP_BELT_D
|
||||
function beltGrade(c) { return c === BP_BELT_UP || c === BP_FILTER_UP ? -1 : c === BP_BELT_DOWN || c === BP_FILTER_DOWN ? 1 : 0; }
|
||||
function slopeShape(c) { return isSlopeBelt(c) ? (beltGrade(c) < 0 ? BP_SLOPE_BR : BP_SLOPE_BL) : c; }
|
||||
function beltFaceY(c, bx, by, x) { return by * BS + (beltGrade(c) < 0 ? 3 - (x - bx * BS) : beltGrade(c) > 0 ? x - bx * BS : 0); }
|
||||
function filterVariant(c, v) { return beltGrade(c) ? (beltGrade(c) > 0 ? 128 : 0) : v & 128; }
|
||||
function beltDirection(c, v) {
|
||||
const d = isFilter(c) ? (v & 128 ? 1 : -1) : (v ? 1 : -1), g = beltGrade(c);
|
||||
const d = isFilter(c) ? (filterVariant(c, v) & 128 ? 1 : -1) : (v ? 1 : -1), g = beltGrade(c);
|
||||
return g ? (g * d < 0 ? 'up-' : 'down-') + (d > 0 ? 'right' : 'left') : d > 0 ? 'right' : 'left';
|
||||
}
|
||||
function isSlope(c) { return c >= BP_SLOPE_BL && c <= BP_SLOPE_TR; }
|
||||
@@ -70,11 +71,11 @@ const PIECES = [
|
||||
{ id: 'wall', code: BP_WALL, name: 'Wall block', kind: 'block', variants: 'wall',
|
||||
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.' },
|
||||
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 for a sidewall belt. Moving cleats lift pockets of grains; extra grains roll back.' },
|
||||
{ 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.' },
|
||||
desc: 'A shaking mesh that joins belts. 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 for a downhill screen. Grains slide down while it shakes; R keeps a picked slope downhill.' },
|
||||
{ id: 'shaft', code: BP_SHAFT, name: 'Shaft', kind: 'block', cost: [[LWOOD, 8]], hidden: POWER_FREE,
|
||||
desc: 'Carries power between machines. Wooden, so keep it away from fire.' },
|
||||
{ id: 'grate', code: BP_GRATE, name: 'Grate', kind: 'block', cost: [[LCLAY, 8]],
|
||||
|
||||
+3
-1
@@ -9,6 +9,7 @@ function serializeWorld() {
|
||||
const t16 = new Uint16Array(N);
|
||||
for (let i = 0; i < N; i++) { const v = (temp[i] + TOFF) * 16; t16[i] = v < 0 ? 0 : v > 65535 ? 65535 : v; }
|
||||
const meta = {
|
||||
slopePhases: Array.from(slopePhase),
|
||||
v: SAVE_VERSION, seed: worldSeed, W, H, tick, surfBase, underD, shallowD, lapse, tOff: TOFF,
|
||||
saplings: saplings.map(s => [s.x, s.y, s.age, s.th, s.seed]),
|
||||
economy: { money, debt, earned, recent: Array.from(mkRecent, v => Math.round(v)), log: shipLog, orders, ordersDone, nextOrderT, orderSeq },
|
||||
@@ -102,6 +103,7 @@ function deserializeWorld(meta, buffers) {
|
||||
if (!dock()) placeDock((W >> 1) + 34); // worlds from before the economy get their trade dock too
|
||||
ghostBlocks = new Map(meta.ghostBlocks || []); ghostMachines = meta.ghostMachines || []; ghostMolds = meta.ghostMolds || [];
|
||||
tick = meta.tick || 0;
|
||||
if (meta.slopePhases) slopePhase.set(meta.slopePhases);
|
||||
updateRoof(); resetWeather();
|
||||
if (meta.weather) {
|
||||
const mw = meta.weather;
|
||||
@@ -117,7 +119,7 @@ function deserializeWorld(meta, buffers) {
|
||||
seen = new Uint8Array(NMAT); meta.seen.forEach((v, k) => { if (k < NMAT) seen[k] = v; });
|
||||
ledger = meta.ledger || {};
|
||||
Object.assign(stats, meta.stats || {});
|
||||
powerDirty = true; rebuildPower(); updateRoof();
|
||||
powerDirty = true; rebuildPower(); slopeCleatsTick(false); updateRoof();
|
||||
if (!weather.base) weather.base = worldWater(); // older saves: take the water it has now as its starting amount
|
||||
if (!meta.specks) clearHardSpecks(true); // older saves: tidy away tiny specks of too-hard rock once
|
||||
rebuildPipes();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Cleats are moving barriers, not material cells. A cleat supports a finite pocket; the bare face never holds grains.
|
||||
const CLEAT_SPACING = 8, CLEAT_HEIGHT = 6; // spacing in face cells, height in grains
|
||||
const CLEAT_REACH = Math.floor(CLEAT_HEIGHT * Math.SQRT1_2);
|
||||
const SCREEN_NUDGE_TICKS = 4; // one downhill contact step per four ticks (15 steps/s), plus gravity
|
||||
let cleatCarrying = false;
|
||||
let cleatMask = new Uint8Array(0), cleatCells = [], slopePhase = new Uint8Array(0);
|
||||
function cleatFoot(x, b, phase = slopePhase[b]) {
|
||||
const d = blockV[b] & 1 ? 1 : -1;
|
||||
return ((x - d * phase) % CLEAT_SPACING + CLEAT_SPACING) % CLEAT_SPACING === 0;
|
||||
}
|
||||
function pocketDepth(x, b) {
|
||||
const g = beltGrade(blockP[b]), d = blockV[b] & 1 ? 1 : -1;
|
||||
// A periodic pocket envelope, clipped to two vertical projections of the cleat.
|
||||
// End pockets keep moving while their cleat rounds the end of the band.
|
||||
const distance = ((g * (d * slopePhase[b] - x)) % CLEAT_SPACING + CLEAT_SPACING) % CLEAT_SPACING;
|
||||
return Math.max(0, CLEAT_REACH * 2 - distance);
|
||||
}
|
||||
function slopeCleatsTick(advance = true) {
|
||||
for (const i of cleatCells) { cleatMask[i] = 0; wakeIdx(i); }
|
||||
cleatCells.length = 0;
|
||||
// Front first, just like flat transport. Each foot belongs to exactly one face cell.
|
||||
for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if (!isBelt(c) || !isSlopeBelt(c) || beltFaceY(c, x >> 2, y >> 2, x) !== y) continue;
|
||||
const d = blockV[b] & 1 ? 1 : -1, g = beltGrade(c), running = netSat(i) > 0;
|
||||
const phase = (slopePhase[b] + (advance && running && !(tick & 1) ? 1 : 0)) % CLEAT_SPACING;
|
||||
if (!cleatFoot(x, b, phase)) continue;
|
||||
for (let h = CLEAT_REACH; h >= 0; h--) {
|
||||
const xx = x + g * h, yy = y - h - 1;
|
||||
if (xx < 0 || xx >= W || yy < 0 || yy >= H) continue;
|
||||
const j = yy * W + xx;
|
||||
cleatCells.push(j);
|
||||
}
|
||||
}
|
||||
if (advance && !(tick & 1)) for (const list of [beltR, beltL]) for (const i of list) {
|
||||
const x = i % W, y = (i / W) | 0, b = (y >> 2) * BW + (x >> 2), c = blockP[b];
|
||||
if ((x & 3) === 0 && isBelt(c) && isSlopeBelt(c) && beltFaceY(c,x>>2,y>>2,x)===y && netSat(i)>0)
|
||||
slopePhase[b] = (slopePhase[b] + 1) % CLEAT_SPACING;
|
||||
}
|
||||
for (const i of cleatCells) { cleatMask[i] = 1; wakeIdx(i); }
|
||||
}
|
||||
// A screen shakes only the grains touching its mesh, never the whole column above it.
|
||||
function screenNudge(top, d, pass) {
|
||||
if (tick % SCREEN_NUDGE_TICKS) return;
|
||||
const i = top - W, m = mat[i];
|
||||
if (i < 0 || PH[m] !== PH_POWDER || stamp[i] === tickStamp || occ[i]) return;
|
||||
if (pass && pass[m] && enter(m, PH_POWDER, top, 0, 1)) return;
|
||||
const j = i + d + W;
|
||||
if (i % W + d < 0 || i % W + d >= W || j >= N) return;
|
||||
if (enter(m, PH_POWDER, j, d, 1)) move(i, j, i % W, (i / W) | 0, j % W, (j / W) | 0);
|
||||
}
|
||||
+2
-1
@@ -42,10 +42,11 @@ function alloc(w, h) {
|
||||
W = w; H = h; N = w * h;
|
||||
fogAlloc();
|
||||
CW = Math.ceil(w / 32); CH = Math.ceil(h / 32); NCH = CW * CH;
|
||||
cleatMask = new Uint8Array(N); cleatCells = [];
|
||||
mat = new Uint8Array(N); temp = new Float32Array(N); life = new Uint8Array(N); flags = new Uint8Array(N);
|
||||
stamp = new Uint8Array(N); part = new Uint8Array(N); mach = new Uint16Array(N); digp = new Uint8Array(N);
|
||||
noiseB = new Uint8Array(N); bgm = new Uint8Array(N); airS = new Uint16Array(N); netId = new Uint16Array(N); isPow = new Uint8Array(N); occ = new Uint8Array(N);
|
||||
BW = Math.ceil(w / BS); BH = Math.ceil(h / BS); NB = BW * BH; blockP = new Uint8Array(NB); blockV = new Uint8Array(NB); filtersDirty = true; filterCfg = new Map(); procDirty = true; procState.clear(); rackState.clear();
|
||||
BW = Math.ceil(w / BS); BH = Math.ceil(h / BS); NB = BW * BH; blockP = new Uint8Array(NB); blockV = new Uint8Array(NB); slopePhase = new Uint8Array(NB); filtersDirty = true; filterCfg = new Map(); procDirty = true; procState.clear(); rackState.clear();
|
||||
ents = []; flyers = []; ghostBlocks = new Map(); ghostMolds = []; treeBases = []; saplings = [];
|
||||
surfBase = Math.round(Math.min(h * 0.27, 175)); underD = h - surfBase; shallowD = 0; lapse = 0.36;
|
||||
surfY = new Int16Array(w); roofY = new Int16Array(w); dirtY = new Int16Array(w); graniteY = new Int16Array(w); basaltY = new Int16Array(w); magmaY = new Int16Array(w);
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ function dragDirFor(P, a, b) {
|
||||
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 === 'belt' || P.filter) && diagonalDrag(dx, dy)) return P.filter ? (dx * dy > 0 ? 1 : 0) : (dx > 0 ? 1 : 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;
|
||||
|
||||
@@ -12,8 +12,8 @@ const PIECE_ICONS = {
|
||||
wall: '<rect x="3" y="5" width="18" height="14" rx="1"/><path d="M3 12h18M9 5v7M15 12v7"/>',
|
||||
grate: '<rect x="3" y="5" width="18" height="14" rx="1"/><path d="M7.5 5v14M12 5v14M16.5 5v14"/>',
|
||||
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>',
|
||||
belt: '<g transform="rotate(-45 12 12)"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M4 9h16M4 15h16M7 7V3M13 7V3M19 7V3"/><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(35 12 12)"><rect x="2" y="7" width="20" height="10" rx="2"/><path d="M4 9h16M4 15h16M6 9v6M10 9v6M14 9v6M18 9v6M4 12h16"/></g>',
|
||||
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"/>',
|
||||
|
||||
+1
-1
@@ -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, 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.'],
|
||||
['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 belts use cleats to carry pockets up or down. Extra grains roll back. Diagonal filters always screen downhill, whichever way you drag. R leaves a picked sloped filter downhill. 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.'],
|
||||
];
|
||||
|
||||
@@ -221,6 +221,20 @@ function previewBlock(code, x, y, w, h, stroke = false, direction = -1, bx = -1,
|
||||
}
|
||||
if (stroke) { const fill = ctx.fillStyle; ctx.fillStyle = ctx.strokeStyle; ctx.fill(); ctx.fillStyle = fill; }
|
||||
else ctx.fill();
|
||||
// Preview cleats stand perpendicular to the face; screens have a raised far rail.
|
||||
ctx.beginPath();
|
||||
for (let k = 0; k < BS; k++) {
|
||||
const fy = beltFaceY(code, 0, 0, k);
|
||||
if (isFilter(code)) ctx.rect(x + k * scale, y + (fy - 2) * scale, scale, scale);
|
||||
else {
|
||||
ctx.rect(x + k * scale, y + (fy - 7 - ((ox + k) & 1)) * scale, scale, (2 + ((ox + k) & 1)) * scale);
|
||||
}
|
||||
if (!isFilter(code) && (((ox + k) % CLEAT_SPACING + CLEAT_SPACING) % CLEAT_SPACING === 0))
|
||||
for (let h = 0; h <= CLEAT_REACH; h++)
|
||||
ctx.rect(x + (k + beltGrade(code) * h) * scale, y + (fy - h - 1) * scale, scale, scale);
|
||||
}
|
||||
if (stroke) { const fill = ctx.fillStyle; ctx.fillStyle = ctx.strokeStyle; ctx.fill(); ctx.fillStyle = fill; }
|
||||
else ctx.fill();
|
||||
return;
|
||||
}
|
||||
code = slopeShape(code);
|
||||
|
||||
@@ -14,19 +14,19 @@ function filterSentence(f) { const p = filterParts(f); return `drop ${p.drops} a
|
||||
// Change the whole run of matching filters at once
|
||||
function applyFilterRun(run, f) {
|
||||
undoRecord('filter settings', () => {
|
||||
for (const b of run) { undoNote({ t: 'filter', bx: b % BW, by: (b / BW) | 0, v: blockV[b], extra: filterInfo(b) }); blockV[b] = f.dir ? 128 : 0; setFilter(b, f); }
|
||||
for (const b of run) { undoNote({ t: 'filter', bx: b % BW, by: (b / BW) | 0, v: blockV[b], extra: filterInfo(b) }); blockV[b] = filterVariant(blockP[b], f.dir ? 128 : 0); setFilter(b, f); }
|
||||
});
|
||||
filtersDirty = true;
|
||||
}
|
||||
function openFilterPicker(cur, onDone, title) {
|
||||
const sel = new Set(cur.m);
|
||||
let inv = cur.inv ? 1 : 0, dir = cur.dir ? 1 : 0;
|
||||
let inv = cur.inv ? 1 : 0, dir = beltGrade(cur.code) ? (beltGrade(cur.code)>0 ? 1 : 0) : cur.dir ? 1 : 0;
|
||||
const list = filterChoices(cur.m);
|
||||
const body = `<div class="fmode" role="radiogroup" aria-label="Mode">
|
||||
<button class="fm" role="radio" data-inv="0"><b>↓ Drop these</b><span>They fall through; the rest rides along</span></button>
|
||||
<button class="fm" role="radio" data-inv="1"><b>→ Carry these</b><span>They ride along; the rest falls through</span></button>
|
||||
</div>
|
||||
<div class="fdir" role="radiogroup" aria-label="Rollers turn"><span class="label">Rollers turn</span><button class="fd" role="radio" data-dir="0">${cur.code ? esc(beltDirection(cur.code, 0)) : '← Left'}</button><button class="fd" role="radio" data-dir="1">${cur.code ? esc(beltDirection(cur.code, 128)) : 'Right →'}</button></div>`
|
||||
<div class="fdir" role="radiogroup" aria-label="Screen travel"><span class="label">Screen travel</span>${beltGrade(cur.code) ? `<span>${esc(beltDirection(cur.code, dir << 7))}</span>` : '<button class="fd" role="radio" data-dir="0">← Left</button><button class="fd" role="radio" data-dir="1">Right →</button>'}</div>`
|
||||
+ (list.length ? `<div class="fpick">${list.map(m => `<button class="fp" role="checkbox" data-m="${m}"><span class="ck" aria-hidden="true"></span><span class="sw" style="background:${rgbOf(m)}"></span><span>${esc(MD[m].name)}</span></button>`).join('')}</div>` : '<p class="note">Find something loose first.</p>')
|
||||
+ `<p class="note fp-sum" id="fp-sum"></p>`;
|
||||
dialog(title || 'Filter', body, [{ label: 'Clear', cls: 'ghost', fn: () => { sel.clear(); sync(); return true; } }, { label: 'Cancel' }, { label: 'Set filter', cls: 'primary', fn: () => { onDone({ m: [...sel].sort((a, b) => a - b), inv, dir }); hudDirty = true; } }]);
|
||||
@@ -36,7 +36,7 @@ function openFilterPicker(cur, onDone, title) {
|
||||
box.querySelectorAll('[data-dir]').forEach(b => { const on = +b.dataset.dir === dir; b.classList.toggle('on', on); b.setAttribute('aria-checked', on); });
|
||||
box.querySelectorAll('[data-m]').forEach(b => { const on = sel.has(+b.dataset.m); b.classList.toggle('on', on); b.setAttribute('aria-checked', on); });
|
||||
const f = { m: [...sel].sort((a, b) => a - b), inv };
|
||||
box.querySelector('#fp-sum').textContent = `${title && /in this row/.test(title) ? 'These filters' : 'This filter'} will ${filterSentence(f)}, ${dir ? 'right' : 'left'}.`;
|
||||
box.querySelector('#fp-sum').textContent = `${title && /in this row/.test(title) ? 'These filters' : 'This filter'} will ${filterSentence(f)}, ${beltGrade(cur.code) ? beltDirection(cur.code, dir << 7) : dir ? 'right' : 'left'}.`;
|
||||
}
|
||||
box.querySelectorAll('[data-dir]').forEach(b => b.onclick = () => { dir = +b.dataset.dir; playSfx('ui'); sync(); });
|
||||
box.querySelectorAll('[data-inv]').forEach(b => b.onclick = () => { inv = +b.dataset.inv; playSfx('ui'); sync(); });
|
||||
|
||||
+2
-1
@@ -47,6 +47,7 @@ function rotate() {
|
||||
else if (t === 'build') {
|
||||
const P = piece();
|
||||
if (pieceUsesWall(P)) cycleWall(1);
|
||||
else if (P.filter && beltGrade(pieceCode(P))) return;
|
||||
else if (P.dirs) GAME.pieceDir[P.id] = ((GAME.pieceDir[P.id] || 0) + 1) % P.dirs.length;
|
||||
else return;
|
||||
} else if (t === 'drill') { tuneUnderCursor(); return; }
|
||||
@@ -83,7 +84,7 @@ function setBrush(d) {
|
||||
// materials and mode travel separately, as GAME.filterCfg)
|
||||
function pieceV(P) {
|
||||
if (isWall(P.code)) return GAME.wallMat;
|
||||
if (P.filter) return (GAME.pieceDir[P.id] || 0) << 7;
|
||||
if (P.filter) return filterVariant(pieceCode(P), (GAME.pieceDir[P.id] || 0) << 7);
|
||||
return GAME.pieceDir[P.id] || 0;
|
||||
}
|
||||
function pieceExtra(P) { return P.filter ? { m: GAME.filterCfg.m.slice(), inv: GAME.filterCfg.inv } : undefined; }
|
||||
|
||||
Reference in New Issue
Block a user