// Player actions on built things: take back, place a machine or mold, gates, F to interact. // Take back whatever piece is at (x,y): machine, mold or block. Returns a message. function removePieceAt(x, y) { droppedLast = 0; const msg = removePieceAt0(x, y); return msg && droppedLast ? msg.replace('taken back.', 'taken back; bag full, excess dropped here.') : msg; } function removePieceAt0(x, y) { if (x < 0 || y < 0 || x >= W || y >= H) return ''; const i = y * W + x; if (ghostBlocks.has(((y / BS) | 0) * BW + ((x / BS) | 0))) { ghostBlocks.delete(((y / BS) | 0) * BW + ((x / BS) | 0)); return 'Ghost removed.'; } const gbx = (x / BS) | 0, gby = (y / BS) | 0; for (let k = ghostMachines.length - 1; k >= 0; k--) { const g = ghostMachines[k], P = PIECE_BY_ID[g.type]; if (gbx >= g.bx && gby >= g.by && gbx < g.bx + P.bw && gby < g.by + P.bh) { ghostMachines.splice(k, 1); return 'Ghost removed.'; } } for (let k = ghostMolds.length - 1; k >= 0; k--) { const g = ghostMolds[k], md = MOLDS[g.kind]; if (gbx >= g.bx && gby >= g.by && gbx < g.bx + md.bw && gby < g.by + md.bh) { ghostMolds.splice(k, 1); return 'Ghost removed.'; } } if (mach[i] && machines[mach[i]]) { const P = PIECE_BY_ID[machines[mach[i]].type]; if (P.fixed) return ''; if (siloFull(machines[mach[i]])) return 'Empty the silo first; press F to open its gate.'; removeMachine(mach[i], true, false); return `${P.name} taken back.`; } const mo = moldAt(x, y); if (mo) { removeMold(mo); return `${MOLDS[mo.kind].name} taken back.`; } const { bx, by } = blockOf(x, y), b = by * BW + bx; if (blockP[b]) { const nm = PIECE_BY_CODE[blockP[b]].name; removeBlock(bx, by); return `${nm} taken back.`; } return ''; } function placeMachineAt(pid, dir, bx, by) { const P = PIECE_BY_ID[pid]; const err = canPlaceMachine(pid, bx, by); if (err) return err; if (!canAfford(P.cost, 1)) return needText(P.cost, 1); payCost(P.cost, 1); addMachine(pid, bx, by, dir); stats.placed++; undoNote({ t: 'mach+', type: pid, bx, by }); return ''; } function placeMoldAt(kind, bx, by, wallLoose) { const err = canPlaceMold(kind, bx, by); if (err) return err; const r = addMold(kind, bx, by, wallLoose, false); if (typeof r !== 'string') undoNote({ t: 'mold+', kind, bx, by }); return typeof r === 'string' ? r : ''; } function toggleGateAt(x, y) { const i0 = y * W + x; if (!(mat[i0] === GATE || part[i0] === P_GOPEN)) return false; const open = mat[i0] === GATE; // currently closed -> open const seenS = new Set([i0]), st = [i0], cells = []; while (st.length && cells.length < 1600) { const i = st.pop(); cells.push(i); const cx = i % W; for (const j of [cx > 0 ? i - 1 : -1, cx < W - 1 ? i + 1 : -1, i - W, i + W]) { if (j < 0 || j >= N || seenS.has(j)) continue; if (mat[j] === GATE || part[j] === P_GOPEN) { seenS.add(j); st.push(j); } } } for (const i of cells) { const cx = i % W, cy = (i / W) | 0; if (open) { if (mat[i] === GATE) { mat[i] = EMPTY; flags[i] = 0; part[i] = P_GOPEN; } } else if (part[i] === P_GOPEN) { const m = mat[i]; if (m !== EMPTY && PH[m] !== PH_GAS) { const u = i - W; if (u >= 0 && mat[u] === EMPTY && !part[u]) { mat[u] = m; temp[u] = temp[i]; life[u] = life[i]; flags[u] = flags[i]; } } part[i] = 0; mat[i] = GATE; flags[i] = 0; life[i] = 0; } wakeXY(cx, cy); } return open ? 'open' : 'closed'; } function interactAt(x, y) { if (x < 0 || y < 0 || x >= W || y >= H) return ''; const g = toggleGateAt(x, y); if (g) { emit({ t: 'sfx', s: 'gate' }); return g === 'open' ? 'Gate opened.' : 'Gate closed.'; } const mo = moldAt(x, y); // ingot molds empty themselves: F passes them by if (mo && mo.kind !== 'ingot') { const r = collectMold(mo); if (!r.startsWith('New') && !r.startsWith('Knocked')) emit({ t: 'sfx', s: 'ui' }); return r; } const pb = ((y / BS) | 0) * BW + ((x / BS) | 0); if (blockP[pb] === BP_PUMP || blockP[pb] === BP_OUTLET) { blockV[pb] ^= 4; emit({ t: 'sfx', s: 'ui' }); return blockP[pb] === BP_PUMP ? (blockV[pb] & 4 ? 'Pump off.' : 'Pump on.') : (blockV[pb] & 4 ? 'Outlet closed.' : 'Outlet open.'); } const id = mach[y * W + x]; if (id && machines[id] && machines[id].type === 'silo') { const mm = machines[id]; mm.open = !mm.open; return mm.open ? (mm.sn ? 'Silo gate open.' : 'Silo gate open; it is empty.') : 'Silo gate shut.'; } if (id && machines[id] && machines[id].type === 'fan') { machines[id].on = !machines[id].on; powerDirty = true; return machines[id].on ? 'Fan on.' : 'Fan off.'; } return ''; }