You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

62 lines
3.5 KiB
JavaScript

// core/multiblock.js; defineGridMultiblock(): the shared place/break machinery.
import { BlockPermutation, getDir, safeGetBlock } from "./mc.js";
import { grid, fcCanReplace } from "./grid.js";
import { undoPlace, breakAir } from "./fx.js";
import { onPlace, onBreak } from "./events.js";
// cfg:
// prefix block-id prefix, e.g. "rzb_dcs:fc_" (color canopies: "rzb_dcs:um_")
// anchorKey the catalog/menu cell, e.g. "3_0_0"
// cells { cellKey: [dx,dy,dz] } south-frame offsets
// name human label for the "not enough room" undo message
// rotating=true grid rotates with facing (false = symmetric canopy: raw offsets, placed with no facing state)
// color=false ids carry a trailing "_<color>" (um/bu/bt); parse splits it off
// canReplace predicate for a cell being buildable (default fcCanReplace; lg passes air|water)
// undefinedBlocks unreadable cell (chunk edge/ceiling) blocks placement (at/tent/lg); default: it's allowed
// actionBar optional (placed,total,dir) => string shown after a successful build (fc/ic)
// returns { prefix, anchorKey, cells, color, rotating, parse, cellOf, cellWorld, anchorFrom, idFor }.
export function defineGridMultiblock(cfg) {
const { prefix, anchorKey, cells, name, rotating = true, color = false, canReplace = fcCanReplace, undefinedBlocks = false, actionBar = null } = cfg;
const g = grid(cells, rotating);
const total = Object.keys(cells).length - 1;
const parse = color
? (id) => { if (!id || !id.startsWith(prefix)) return null; const m = id.slice(prefix.length).match(/^(\d+_\d+_\d+)_(.+)$/); return (m && cells[m[1]]) ? { cell: m[1], color: m[2] } : null; }
: (id) => { if (!id || !id.startsWith(prefix)) return null; const c = id.slice(prefix.length); return cells[c] ? c : null; };
const cellOf = color ? (id) => parse(id)?.cell ?? null : parse;
const idFor = (cell, col) => color ? prefix + cell + "_" + col : prefix + cell;
onPlace((ev) => {
const b = ev.block, pr = parse(b.typeId);
if (color ? !(pr && pr.cell === anchorKey) : pr !== anchorKey) return;
const dir = rotating ? getDir(b.permutation) : "south", col = color ? pr.color : null;
for (const c in cells) { // validate every non-anchor cell first -> undo if any is blocked (never half-place)
if (c === anchorKey) continue;
const nb = safeGetBlock(b.dimension, g.cellWorld(c, b.x, b.y, b.z, dir));
const blocked = undefinedBlocks ? !canReplace(nb) : (nb !== undefined && !canReplace(nb));
if (blocked) { undoPlace(b, ev.player, idFor(anchorKey, col), name); return; }
}
let placed = 0;
for (const c in cells) {
if (c === anchorKey) continue;
const nb = b.dimension.getBlock(g.cellWorld(c, b.x, b.y, b.z, dir));
if (canReplace(nb)) { try { nb.setPermutation(BlockPermutation.resolve(idFor(c, col), rotating ? { "minecraft:cardinal_direction": dir } : {})); placed++; } catch {} }
}
if (actionBar) { try { ev.player.onScreenDisplay.setActionBar(actionBar(placed, total, dir)); } catch {} }
});
onBreak((ev) => {
const cell = cellOf(ev.brokenBlockPermutation?.type?.id || "");
if (!cell) return;
const dir = rotating ? getDir(ev.brokenBlockPermutation) : "south";
const b = ev.block, a = g.anchorFrom(cell, b.x, b.y, b.z, dir);
for (const c in cells) {
if (c === cell) continue;
const nb = safeGetBlock(b.dimension, g.cellWorld(c, a.x, a.y, a.z, dir));
if (nb && cellOf(nb.typeId) === c) { try { breakAir(nb); } catch {} }
}
});
return { prefix, anchorKey, cells, color, rotating, parse, cellOf, cellWorld: g.cellWorld, anchorFrom: g.anchorFrom, idFor };
}