134 lines
6.7 KiB
JavaScript
134 lines
6.7 KiB
JavaScript
// Tool selection: cycling, drill tuning, piece variants and costs.
|
|
function pieceSize(P) { return P.kind === 'mold' ? MOLDS[P.mold] : P; }
|
|
function pieceUsesWall(P) { return P.variants === 'wall'; }
|
|
function setDrillWidth(w) {
|
|
const n = Math.max(0, Math.min(PULSE_W.length - 1, w));
|
|
if (n === GAME.drillW) return;
|
|
GAME.drillW = n; drillWidth = n; hudDirty = true; playSfx('ui');
|
|
const what = ['long beam', 'balanced cone', 'broad fan'][n];
|
|
setStatus(`${PULSE_W[n].name[0].toUpperCase() + PULSE_W[n].name.slice(1)} pulse, ${what} (reach ${drillReach(n)}).`, 'info');
|
|
}
|
|
// R with the drill: tune it to the material under the cursor, or back to everything
|
|
function tuneUnderCursor() {
|
|
const ax = Math.floor(GAME.aim.x), ay = Math.floor(GAME.aim.y);
|
|
if (ax < 0 || ay < 0 || ax >= W || ay >= H) return;
|
|
const m = mat[ay * W + ax];
|
|
const to = PH[m] === PH_SOLID ? YLD[m] : PH[m] === PH_POWDER ? m : 0;
|
|
GAME.drillTune = !to || to === GAME.drillTune ? 0 : to;
|
|
hudDirty = true; playSfx('ui');
|
|
setStatus(GAME.drillTune ? `Tuned to ${MD[GAME.drillTune].name.toLowerCase()}: only it shatters.` : 'Untuned: the drill shatters any rock it can.', 'info');
|
|
}
|
|
function cycleItem(d) {
|
|
const t = toolId();
|
|
if (t === 'drill') { setDrillWidth(GAME.drillW + d); return; }
|
|
if (t === 'vacuum') {
|
|
const l = bagMaterials();
|
|
if (!l.length) return;
|
|
const first = blowList()[0], k = first ? l.indexOf(first) : d > 0 ? -1 : 0;
|
|
GAME.blow = new Set([l[(k + d + l.length) % l.length]]);
|
|
} else if (t === 'build') {
|
|
let k = BUILD_ORDER.indexOf(piece().id); k = (k + d + BUILD_ORDER.length) % BUILD_ORDER.length;
|
|
GAME.pieceIdx = PIECE_BY_ID[BUILD_ORDER[k]].index; GAME.buildCat = catOf(BUILD_ORDER[k]);
|
|
GAME.catPick = GAME.catPick || {}; GAME.catPick[GAME.buildCat] = BUILD_ORDER[k];
|
|
GAME.drag = null;
|
|
} else return;
|
|
playSfx('ui'); hudDirty = true;
|
|
}
|
|
function cycleWall(d) {
|
|
// prefer materials you actually have, but allow choosing any
|
|
const have = WALL_LIST.filter(m => bag[m] > 0);
|
|
const list = have.length ? have.concat(WALL_LIST.filter(m => !have.includes(m))) : WALL_LIST;
|
|
let k = list.indexOf(GAME.wallMat); k = (k + d + list.length) % list.length;
|
|
GAME.wallMat = list[k];
|
|
}
|
|
function rotate() {
|
|
const t = toolId();
|
|
if (GAME.paste && GAME.clip) GAME.clip = flipBlueprint(GAME.clip);
|
|
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; }
|
|
else return;
|
|
playSfx('ui'); hudDirty = true;
|
|
}
|
|
function toggleBlow(m) {
|
|
if (GAME.blow.has(m)) GAME.blow.delete(m);
|
|
else if (PH[m] === PH_ITEM) GAME.blow = new Set([m]);
|
|
else { for (const k of GAME.blow) if (PH[k] === PH_ITEM) GAME.blow.delete(k); GAME.blow.add(m); }
|
|
lastBagKey = ''; hudDirty = true; playSfx('ui');
|
|
}
|
|
function pickFilterUnderCursor() {
|
|
const ax = Math.floor(GAME.aim.x), ay = Math.floor(GAME.aim.y);
|
|
const bar = ents.find(e => ax >= e.ix && ax < e.ix + BAR_W && ay >= e.iy && ay < e.iy + BAR_H);
|
|
const m = bar ? bar.m : grainAt(ax, ay);
|
|
if (!m || !filterable(m)) { setStatus('Aim at something loose to add it to the filter.', 'info'); return; }
|
|
toggleFilterMat(m);
|
|
}
|
|
function toggleFilterMat(m) {
|
|
const f = GAME.filterCfg, k = f.m.indexOf(m);
|
|
if (k >= 0) f.m.splice(k, 1); else f.m.push(m);
|
|
f.m.sort((a, b) => a - b);
|
|
hudDirty = true; lastBagKey = ''; playSfx('ui');
|
|
setStatus(`New filter selection updated.`, 'info');
|
|
}
|
|
function setBrush(d) {
|
|
const t = toolId();
|
|
if (t === 'drill') { setDrillWidth(GAME.drillW + d); return; }
|
|
if (t === 'build' || t === 'blueprint' || t === 'ignite') return;
|
|
GAME.brush[GAME.tool] = Math.max(0, Math.min(t === 'vacuum' ? 6 : 8, GAME.brush[GAME.tool] + d)); hudDirty = true;
|
|
}
|
|
// The block variant a piece is placed with: wall material, or direction (a filter's
|
|
// materials and mode travel separately, as GAME.filterCfg)
|
|
function pieceV(P) {
|
|
if (isWall(P.code)) return GAME.wallMat;
|
|
if (P.filter) return filterVariant(pieceCode(P), (GAME.pieceDir[P.id] || 0) << 7);
|
|
return GAME.pieceDir[P.id] || 0;
|
|
}
|
|
// One drag for the build tool and its preview: code, direction, variant, blocks, and the planned
|
|
// blocks the join rule sees. A line that joins one block up or down snaps there (joinSnap).
|
|
function dragPlan(P, a, b, shift) {
|
|
const code = pieceCode(P, b), dd = a ? dragDirFor(P, a, b) : -1, dir = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
|
const v = isWall(P.code) ? GAME.wallMat : P.filter ? filterVariant(code, dir << 7) : dir;
|
|
let cells = !a ? [[b.bx, b.by]] : shift && !lineOnly(code) ? blockRect(a.bx, a.by, b.bx, b.by)
|
|
: blockLine(a.bx, a.by, b.bx, b.by, isWall(code) || isSlopeBelt(code) || code === BP_SCREW);
|
|
const snap = lineOnly(code) ? joinSnap(code, v, cells) : 0;
|
|
if (snap) cells = cells.map(([x, y]) => [x, y + snap]);
|
|
return { code, dd, dir, v, cells, snap, planned: new Map(cells.map(([x, y]) => [y * BW + x, [code, v]])) };
|
|
}
|
|
function pieceExtra(P) { return P.filter ? { m: GAME.filterCfg.m.slice(), inv: GAME.filterCfg.inv } : undefined; }
|
|
function filterBlockAt(x, y) { if (x < 0 || y < 0 || x >= W || y >= H) return -1; const b = ((y / BS) | 0) * BW + ((x / BS) | 0); return isFilter(blockP[b]) ? b : -1; }
|
|
function chipCost(cost) { const ok = canAfford(cost, 1); return `<span class="chip ${ok ? 'ok' : 'bad'}">${esc(costText(cost, 1))}</span>`; }
|
|
function pieceCostNow(P, m = GAME.wallMat) {
|
|
if (devFree) return [[CASH, 0]];
|
|
if (P.kind === 'mold') return wallCost(m, MOLDS[P.mold].walls);
|
|
if (P.kind === 'block' && isWall(P.code)) return pieceCost(pieceCode(P), m);
|
|
return P.cost;
|
|
}
|
|
|
|
function pieceCode(P, end = aimBlock()) {
|
|
if (P.id === 'belt' || P.filter) {
|
|
const a = GAME.drag || end;
|
|
// Shift changes nothing here: belts and filters only ever draw a line
|
|
if (GAME.drag && diagonalDrag(end.bx - a.bx, end.by - a.by))
|
|
return (P.filter ? BP_FILTER_UP : BP_BELT_UP) + ((end.bx - a.bx) * (end.by - a.by) > 0 ? 1 : 0);
|
|
if (end.bx === a.bx && end.by === a.by) return GAME.pieceShape && GAME.pieceShape[P.id] || P.code;
|
|
return P.code;
|
|
}
|
|
if (P.code !== BP_WALL) return P.code;
|
|
const a = GAME.drag || end, dx = end.bx - a.bx, dy = end.by - a.by;
|
|
if (GAME.drag && (dx || dy)) {
|
|
return diagonalDrag(dx, dy) && !(typeof modShift === 'function' && modShift()) ? slopeCode(dx, dy, a.flip ? 2 : 0) : BP_WALL;
|
|
}
|
|
const shape = GAME.pieceDir.wall || 0;
|
|
return shape ? BP_SLOPE_BL + shape - 1 : BP_WALL;
|
|
}
|
|
function turnSlope() {
|
|
const a = GAME.drag, b = aimBlock();
|
|
if (a && diagonalDrag(b.bx - a.bx, b.by - a.by)) a.flip = !a.flip;
|
|
else GAME.pieceDir.wall = ((GAME.pieceDir.wall || 0) + 1) % 5;
|
|
hudDirty = true;
|
|
}
|