// features/stations.js crafting stations placement + removal (1-to-1 with DecoCraft). // Placer item -> minecraft:entity_placer spawns an INVISIBLE _init; on its spawn we grid-align, check clearance + the // 8-block duplicate guard, then summon the real station PRE-ROTATED to the player's look cardinal (one step, no visible // rotation) and remove the init. A blocked placement refunds the item + plays the reject sound. The station is non-solid; // its footprint is invisible full-collision blocks (bench = 2 cells, decomposer = 1). Hitting a station returns the stored // materials + the item (no item in creative, like DecoCraft) and clears the footprint blocks. import { world, ItemStack, isCreative, safeGetBlock } from "../core/mc.js"; import { fcCanReplace } from "../core/grid.js"; import { dropStored } from "./decostation.js"; const INIT_TO_STATION = { "rzb_dcs:decobench_init": "rzb_dcs:decobench", "rzb_dcs:decomposer_init": "rzb_dcs:decomposer" }; const STATIONS = new Set(["rzb_dcs:decobench", "rzb_dcs:decomposer"]); const ITEM_FOR = { "rzb_dcs:decobench": "rzb_dcs:seasons_bench", "rzb_dcs:decomposer": "rzb_dcs:deco_seasons_decomp" }; const FILL = { "rzb_dcs:decobench": "^ ^ ^ ^1 ^ ^", "rzb_dcs:decomposer": "^ ^ ^ ^ ^ ^" }; // 2-cell bench, 1-cell decomposer const TWO_CELL = new Set(["rzb_dcs:decobench"]); const leftCell = (yaw) => { const r = yaw * Math.PI / 180; return [Math.round(Math.cos(r)), Math.round(Math.sin(r))]; }; // world offset of the local-left cell (^1) const replaceable = (dim, cx, cy, cz) => { const b = safeGetBlock(dim, { x: cx, y: cy, z: cz }); return !!b && (b.isAir || fcCanReplace(b)); }; world.afterEvents.entitySpawn?.subscribe((ev) => { const e = ev.entity; const station = INIT_TO_STATION[e?.typeId]; if (station) { const dim = e.dimension, l = e.location; const bx = Math.floor(l.x), by = Math.floor(l.y), bz = Math.floor(l.z); let yaw = 180, player; try { player = dim.getPlayers({ location: l, closest: 1, maxDistance: 6 })[0]; if (player) yaw = Math.round(player.getRotation().y / 90) * 90 + 180; } catch {} const cells = [[bx, by, bz]]; if (TWO_CELL.has(station)) { const [lx, lz] = leftCell(yaw); cells.push([bx + lx, by, bz + lz]); } const clear = cells.every(([cx, cy, cz]) => replaceable(dim, cx, cy, cz)); let dup = false; try { dup = dim.getEntities({ location: l, type: station, maxDistance: 8 }).length > 0; } catch {} if (!clear || dup) { // DecoCraft: refuse -> refund the item + reject sound try { dim.spawnItem(new ItemStack(ITEM_FOR[station], 1), { x: bx + 0.5, y: by + 0.5, z: bz + 0.5 }); } catch {} try { player?.playSound("note.didgeridoo", { volume: 0.8 }); } catch {} try { e.remove(); } catch {} return; } const ht = TWO_CELL.has(station) ? ` rzb_dcs:ht_${((yaw % 360) + 360) % 360}` : ""; // facing-oriented 2-cell hit box (bench only) try { dim.runCommand(`summon ${station} ${bx + 0.5} ${by} ${bz + 0.5} ${yaw} 0${ht}`); } catch {} // pre-rotated try { e.remove(); } catch {} return; } if (STATIONS.has(e?.typeId) && ev.cause !== "Loaded") { // real station placed -> fill its invisible collision footprint try { e.runCommand(`fill ${FILL[e.typeId]} rzb_dcs:invisible_block`); } catch {} } }); // hit to break -> clear footprint, return stored materials + the item (no item in creative), remove world.afterEvents.entityHitEntity?.subscribe((ev) => { const t = ev.hitEntity; if (!STATIONS.has(t?.typeId)) return; const dmg = ev.damagingEntity; if (dmg?.typeId !== "minecraft:player") return; try { t.runCommand(`fill ${FILL[t.typeId]} air replace rzb_dcs:invisible_block`); } catch {} try { dropStored(t); } catch {} if (!isCreative(dmg)) { try { t.dimension.spawnItem(new ItemStack(ITEM_FOR[t.typeId], 1), t.location); } catch {} } try { t.remove(); } catch {} });