// features/containers.js (presents). import { world, system, isCreative, BlockPermutation } from "../core/mc.js"; import { onPlace, onBreak } from "../core/events.js"; import { brushFx, particleFor, breakFx } from "../core/fx.js"; const FAMILY = "rzb_dcs_present_inv"; const OFF_Y = 0.1; const BRUSH = "rzb_dcs:brush_color"; const heldItem = (e) => { try { return e.getComponent("minecraft:inventory")?.container?.getItem(e.selectedSlotIndex)?.typeId; } catch { return undefined; } }; const PRESENT_SEQ = {}; const _reg = (ids) => { for (const id of ids) PRESENT_SEQ[id] = ids; }; const RAINBOW = ["white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "cyan", "purple", "blue", "green", "red"]; // DecoCraft canonical order for (const t of ["wi30", "wi31", "wi32", "wi33"]) _reg(RAINBOW.map((c) => "rzb_dcs:" + t + "_" + c)); // 12-colour presents _reg(["wi34_1", "wi34_2", "wi34_3", "wi34_4", "wi34_5"].map((x) => "rzb_dcs:" + x)); // present trio (designs) _reg(["wi35_6", "wi35_7", "wi35_8", "wi35_9", "wi35_10", "wi35_11"].map((x) => "rzb_dcs:" + x)); // present trio (designs) function recolorPresent(dim, bx, by, bz, player) { let blk = null; try { blk = dim.getBlock({ x: bx, y: by, z: bz }); } catch {} const seq = blk && PRESENT_SEQ[blk.typeId]; if (!seq) return false; const next = seq[(seq.indexOf(blk.typeId) + 1) % seq.length]; let dir; try { dir = blk.permutation.getState("minecraft:cardinal_direction"); } catch {} try { blk.setPermutation(BlockPermutation.resolve(next, dir !== undefined ? { "minecraft:cardinal_direction": dir } : {})); } catch { try { blk.setType(next); } catch {} } brushFx(dim, { x: bx, y: by, z: bz }, particleFor(next), player); return true; } const PRESENTS = { "rzb_dcs:wi30_": "rzb_dcs:present_inv", // Present "rzb_dcs:wi31_": "rzb_dcs:present_inv", // Present Long "rzb_dcs:wi32_": "rzb_dcs:present_inv", // Present Round "rzb_dcs:wi33_": "rzb_dcs:present_inv", // Present Small "rzb_dcs:wi34_": "rzb_dcs:present_inv", // Present Trio "rzb_dcs:wi35_": "rzb_dcs:present_inv" // Present Trio }; function presentEntity(typeId) { if (!typeId) return null; for (const p in PRESENTS) if (typeId.startsWith(p)) return PRESENTS[p]; return null; } const invEntsAt = (dim, at, max = 0.5) => { try { return dim.getEntities({ families: [FAMILY], location: at, maxDistance: max }); } catch { return []; } }; function dropContents(dim, ent, at) { try { const c = ent.getComponent("minecraft:inventory")?.container; if (!c) return; for (let i = 0; i < c.size; i++) { const it = c.getItem(i); if (it) { try { dim.spawnItem(it, at); } catch {} } } } catch {} } // Place a present -> spawn its container entity, once. onPlace((ev) => { const b = ev.block; if (!b) return; const entId = presentEntity(b.typeId); if (!entId) return; const at = { x: b.x + 0.5, y: b.y + OFF_Y, z: b.z + 0.5 }; if (invEntsAt(b.dimension, at, 0.4).length) return; // don't double up try { b.dimension.spawnEntity(entId, at); } catch {} }); // Break a present > drop the container's contents + despawn it. onBreak((ev) => { const id = ev.brokenBlockPermutation?.type?.id; if (!presentEntity(id)) return; const b = ev.block, at = { x: b.x + 0.5, y: b.y + OFF_Y, z: b.z + 0.5 }; for (const e of invEntsAt(b.dimension, at)) { dropContents(b.dimension, e, at); try { e.remove(); } catch {} } }); world.afterEvents.entityHitEntity?.subscribe((ev) => { const t = ev.hitEntity; if (!t || !t.typeId.startsWith("rzb_dcs:present_inv")) return; const dim = t.dimension, l = t.location, bx = Math.floor(l.x), by = Math.floor(l.y), bz = Math.floor(l.z); if (heldItem(ev.damagingEntity) === BRUSH) { recolorPresent(dim, bx, by, bz, ev.damagingEntity); return; } const at = { x: bx + 0.5, y: by + 0.3, z: bz + 0.5 }; dropContents(dim, t, at); let blk = null; try { blk = dim.getBlock({ x: bx, y: by, z: bz }); } catch {} if (blk && presentEntity(blk.typeId)) { if (!isCreative(ev.damagingEntity)) { try { const is = blk.getItemStack(1, false); if (is) dim.spawnItem(is, at); } catch {} } try { blk.setType("minecraft:air"); } catch {} } try { t.remove(); } catch {} breakFx(dim, bx + 0.5, by + 0.5, bz + 0.5); // entity attack-to-pickup: block is script-removed (no vanilla particle) -> puff breakdeco }); world.beforeEvents.playerInteractWithEntity?.subscribe((ev) => { const t = ev.target; if (!t || !t.typeId.startsWith("rzb_dcs:present_inv")) return; if (ev.itemStack?.typeId !== BRUSH) return; ev.cancel = true; const dim = t.dimension, l = t.location, bx = Math.floor(l.x), by = Math.floor(l.y), bz = Math.floor(l.z); system.run(() => recolorPresent(dim, bx, by, bz, ev.player)); }); system.runInterval(() => { for (const name of ["overworld", "nether", "the_end"]) { let dim; try { dim = world.getDimension(name); } catch { continue; } let ents; try { ents = dim.getEntities({ families: [FAMILY] }); } catch { continue; } for (const e of ents) { const l = e.location; let blk = null; try { blk = dim.getBlock({ x: Math.floor(l.x), y: Math.floor(l.y), z: Math.floor(l.z) }); } catch {} if (blk && !presentEntity(blk.typeId)) { dropContents(dim, e, l); try { e.remove(); } catch {} } } } }, 40);