// core/grid.js; the ONE rotation convention + facing tables + ground/water probes. import { safeGetBlock } from "./mc.js"; export const RIGHT = { south: [1, 0, 0], north: [-1, 0, 0], east: [0, 0, -1], west: [0, 0, 1] }; export const FWD = { south: [0, 0, 1], north: [0, 0, -1], east: [1, 0, 0], west: [-1, 0, 0] }; // world position of a south-frame offset g given the anchor + facing. rotating=false -> raw offset (symmetric canopies um/bu/bt). export function cellWorld(g, ax, ay, az, dir, rotating = true) { if (!rotating) return { x: ax + g[0], y: ay + g[1], z: az + g[2] }; const R = RIGHT[dir] || RIGHT.south, F = FWD[dir] || FWD.south; return { x: ax + g[0] * R[0] + g[2] * F[0], y: ay + g[1], z: az + g[0] * R[2] + g[2] * F[2] }; } // inverse of cellWorld: back compute the anchor from a cell's world position + facing. export function anchorFrom(g, x, y, z, dir, rotating = true) { if (!rotating) return { x: x - g[0], y: y - g[1], z: z - g[2] }; const R = RIGHT[dir] || RIGHT.south, F = FWD[dir] || FWD.south; return { x: x - g[0] * R[0] - g[2] * F[0], y: y - g[1], z: z - g[0] * R[2] - g[2] * F[2] }; } // Bundle a cell map into ready-to-use cellWorld/anchorFrom closures (used by every multiblock family). export function grid(cells, rotating = true) { return { cells, cellWorld: (cell, ax, ay, az, dir) => cellWorld(cells[cell], ax, ay, az, dir, rotating), anchorFrom: (cell, x, y, z, dir) => anchorFrom(cells[cell], x, y, z, dir, rotating), }; } // Facing / yaw tables export const DIR_YAW = { north: 0, south: 180, east: 90, west: 270 }; // seat: player faces OUT of the seat export const DIR_TROT = { south: 0, north: 180, east: 90, west: -90 }; // seat: block geometry transformation angle export const DISPLAY_DIR_YAW = { north: 0, east: 90, south: 180, west: -90 }; // DecoCraft display-entity summon yaw export const TUBE_DIR_YAW = { south: 0, west: 90, north: 180, east: -90 }; // rideable block-facing -> entity body yaw export const displayYawToDir = (yaw) => { const y = ((yaw % 360) + 360) % 360; if (y < 45 || y >= 315) return "north"; if (y < 135) return "east"; if (y < 225) return "south"; return "west"; }; export const tubeYawToDir = (yaw) => { const y = ((yaw % 360) + 360) % 360; if (y < 45 || y >= 315) return "south"; if (y < 135) return "west"; if (y < 225) return "north"; return "east"; }; export function fcCanReplace(nb) { if (!nb) return false; if (nb.isAir) return true; const t = nb.typeId || ""; if (/grass_block|dirt|podzol|mycelium|mud|farmland|clay|gravel|sand|stone|terracotta|concrete|planks|log|wool/.test(t)) return false; return /short_grass|tall_grass|tallgrass|fern|flower|sapling|snow_layer|dead_bush|azalea|deadbush|seagrass|\bvine|leaf_litter/.test(t); } export function tubeWaterTop(dim, x, z, nearY) { const isW = (b) => { try { return b.typeId.endsWith("water"); } catch { return false; } }; for (let y = Math.floor(nearY) + 3; y >= Math.floor(nearY) - 6; y--) { const b = safeGetBlock(dim, { x, y, z }); if (isW(b)) return y; } return null; } export function tubeOverWater(block) { try { if (block.isWaterlogged) return true; } catch {} const dim = block.dimension; for (const [dx, dy, dz] of [[0, -1, 0], [1, 0, 0], [-1, 0, 0], [0, 0, 1], [0, 0, -1]]) { const b = safeGetBlock(dim, { x: block.x + dx, y: block.y + dy, z: block.z + dz }); try { if (b && (b.typeId.endsWith("water") || b.isWaterlogged)) return true; } catch {} } return false; } export function loungerSupportY(dim, x, z, fromY, noFloat, float = 0) { for (let y = Math.floor(fromY) + 1; y >= Math.floor(fromY) - 48; y--) { const b = safeGetBlock(dim, { x, y, z }); if (!b) continue; const id = b.typeId || ""; if (id.endsWith("water")) { if (noFloat) continue; return y + 1 + float; } let air = id === "minecraft:air"; try { air = b.isAir; } catch {} if (!air && !/water|lava/.test(id)) return y + 1; } return null; }