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.

136 lines
9.2 KiB
JavaScript

// features/interact.js right-click lids/toggles + the interact fall-through dispatcher.
import { world, system, BlockPermutation, getDir, safeGetBlock } from "../core/mc.js";
import { tubeOverWater } from "../core/grid.js";
import { debounced } from "../core/fx.js";
import { registerVariants } from "../core/variants.js";
import { LT_CELLS, ltParse, ltCellWorld, ltAnchorFrom, LT_CELLS_W, ltParseW, ltCellWorldW, ltAnchorFromW } from "./mb_trees.js";
import { sit } from "./seating.js";
import { EMBER_BLOCKS, emberPos, clearEmberAt, spawnEmberFor } from "./maintenance.js";
const _boxRecent = new Set();
// Box of Chocolates: right-click swaps CLOSED (sp51, base/anchor) <-> OPEN (sp51_open, hidden state), keeping facing.
function boxToggle(block) {
const id = block?.typeId; if (id !== "rzb_dcs:sp51" && id !== "rzb_dcs:sp51_open") return false;
if (debounced(_boxRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
const to = id === "rzb_dcs:sp51" ? "rzb_dcs:sp51_open" : "rzb_dcs:sp51";
let dir; try { dir = block.permutation.getState("minecraft:cardinal_direction"); } catch {}
try { block.setPermutation(BlockPermutation.resolve(to, dir ? { "minecraft:cardinal_direction": dir } : {})); } catch {}
return true;
}
const _coolerRecent = new Set();
function coolerToggle(block) {
const from = block?.typeId; const m = from?.match(/^rzb_dcs:(su14|su19)_(closed_)?(.+)$/); if (!m) return false;
if (debounced(_coolerRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
const to = m[2] ? "rzb_dcs:" + m[1] + "_" + m[3] : "rzb_dcs:" + m[1] + "_closed_" + m[3];
let dir; try { dir = block.permutation.getState("minecraft:cardinal_direction"); } catch {}
try { block.setPermutation(BlockPermutation.resolve(to, dir ? { "minecraft:cardinal_direction": dir } : {})); } catch {}
// open-grill smoke follows the state (setPermutation doesn't fire onPlace/onBreak): spawn the emitter on open, clear on close
try {
const dim = block.dimension, base = { x: block.x, y: block.y, z: block.z };
clearEmberAt(dim, emberPos({ ...base, typeId: (from in EMBER_BLOCKS) ? from : to }));
if (to in EMBER_BLOCKS) dim.spawnEntity(EMBER_BLOCKS[to].ent, emberPos({ ...base, typeId: to }));
} catch {}
return true;
}
registerVariants(["rzb_dcs:su14_black", "rzb_dcs:su14_orange", "rzb_dcs:su14_gray", "rzb_dcs:su14_light_gray", "rzb_dcs:su14_blue", "rzb_dcs:su14_green", "rzb_dcs:su14_red"]); // cooler (open)
registerVariants(["rzb_dcs:su14_closed_black", "rzb_dcs:su14_closed_orange", "rzb_dcs:su14_closed_gray", "rzb_dcs:su14_closed_light_gray", "rzb_dcs:su14_closed_blue", "rzb_dcs:su14_closed_green", "rzb_dcs:su14_closed_red"]); // cooler (closed)
registerVariants(["rzb_dcs:su19_black", "rzb_dcs:su19_blue", "rzb_dcs:su19_red", "rzb_dcs:su19_silver"]); // grill (open)
registerVariants(["rzb_dcs:su19_closed_black", "rzb_dcs:su19_closed_blue", "rzb_dcs:su19_closed_red", "rzb_dcs:su19_closed_silver"]); // grill (closed)
// ---- Christmas lights: right-click toggles a light's flicker<->cycle STATE (rzb_dcs:mode). Brush swaps the colour. ----
const XMAS_MODES = { "rzb_dcs:wi05_color": 2, "rzb_dcs:wi05_white": 2, "rzb_dcs:wi05_rainbow": 2, "rzb_dcs:wi06_rainbow": 2, "rzb_dcs:wi09_rainbow": 2, "rzb_dcs:wi09_white": 2, "rzb_dcs:wi09_yellow": 2 };
registerVariants(["rzb_dcs:wi05_rainbow", "rzb_dcs:wi05_white", "rzb_dcs:wi05_color"]); // christmas lights style 1
registerVariants(["rzb_dcs:wi06_rainbow", "rzb_dcs:wi06_white", "rzb_dcs:wi06_color"]); // christmas lights style 2
registerVariants(["rzb_dcs:wi07_rainbow", "rzb_dcs:wi07_white", "rzb_dcs:wi07_color"]); // ground lights centre strip
registerVariants(["rzb_dcs:wi08_rainbow", "rzb_dcs:wi08_white", "rzb_dcs:wi08_color"]); // ground lights edge strip
registerVariants(["rzb_dcs:wi09_rainbow", "rzb_dcs:wi09_yellow", "rzb_dcs:wi09_white"]); // meteor lights
registerVariants(["rzb_dcs:wi16_chocolate_chip", "rzb_dcs:wi16_mmm", "rzb_dcs:wi16_raisins"]); // cookie plate (shared-geo types)
registerVariants(["rzb_dcs:wi18_rainbow", "rzb_dcs:wi18_white"]); // tinsel 1
registerVariants(["rzb_dcs:wi19_bg", "rzb_dcs:wi19_rb", "rzb_dcs:wi19_rg", "rzb_dcs:wi19_sg"]); // tinsel 2
const _xmasRecent = new Set();
function xmasLightCycle(block) {
const n = XMAS_MODES[block?.typeId]; if (!n) return false;
if (debounced(_xmasRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
let mode = 0; try { mode = block.permutation.getState("rzb_dcs:mode") ?? 0; } catch {}
try { block.setPermutation(block.permutation.withState("rzb_dcs:mode", (mode + 1) % n)); } catch {}
return true;
}
const _ltFlickerRecent = new Set();
function ltFlickerToggle(block) {
const rainbow = ltParse(block?.typeId), cell = rainbow || ltParseW(block?.typeId); if (!cell) return false; // rainbow OR white light tree
if (debounced(_ltFlickerRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
const CELLS = rainbow ? LT_CELLS : LT_CELLS_W, parse = rainbow ? ltParse : ltParseW, cellWorld = rainbow ? ltCellWorld : ltCellWorldW, anchorFrom = rainbow ? ltAnchorFrom : ltAnchorFromW;
const dir = getDir(block.permutation), a = anchorFrom(cell, block.x, block.y, block.z, dir), dim = block.dimension;
let fl = 0; try { fl = block.permutation.getState("rzb_dcs:fl") ?? 0; } catch {}
const next = (fl + 1) % 2;
system.runTimeout(() => { for (const c of Object.keys(CELLS)) { const p = cellWorld(c, a.x, a.y, a.z, dir), nb = safeGetBlock(dim, p); if (nb && parse(nb.typeId) === c) { try { nb.setPermutation(nb.permutation.withState("rzb_dcs:fl", next)); } catch {} } } }, 1);
return true;
}
const _emberRecent = new Set();
function emberToggle(block) {
if (!(block?.typeId in EMBER_BLOCKS)) return false;
if (debounced(_emberRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
let lit = 1; try { lit = block.permutation.getState("rzb_dcs:lit") ?? 1; } catch {}
const next = lit ? 0 : 1;
try { block.setPermutation(block.permutation.withState("rzb_dcs:lit", next)); } catch {}
clearEmberAt(block.dimension, emberPos(block));
if (next) spawnEmberFor(block); // re-lit -> fresh emitter at the flame
return true;
}
const LIGHT_TOGGLE = new Set(["rzb_dcs:sp34", "rzb_dcs:su24", "rzb_dcs:su28"]); // garden + camping lantern + jellyfish lamp
const _lanternRecent = new Set();
function lanternToggle(block) {
if (!LIGHT_TOGGLE.has(block?.typeId)) return false;
if (debounced(_lanternRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
let lit = 1; try { lit = block.permutation.getState("rzb_dcs:lit") ?? 1; } catch {}
try { block.setPermutation(block.permutation.withState("rzb_dcs:lit", lit ? 0 : 1)); } catch {}
return true;
}
const _jolRecent = new Set();
function jolToggle(block) {
const id = block?.typeId; if (!id || !/^rzb_dcs:fa3[34](_[1-4])?(_on)?$/.test(id)) return false;
if (debounced(_jolRecent, block.x + "," + block.y + "," + block.z, 4)) return true;
const to = id.endsWith("_on") ? id.slice(0, -3) : id + "_on";
try { block.setPermutation(BlockPermutation.resolve(to, { "minecraft:cardinal_direction": getDir(block.permutation) })); } catch {}
return true;
}
world.afterEvents.playerInteractWithBlock?.subscribe((ev) => {
if (ev.itemStack?.typeId === "rzb_dcs:brush_color") return; // brush handled in beforeEvents (brush.js)
if (coolerToggle(ev.block)) return;
if (boxToggle(ev.block)) return;
if (xmasLightCycle(ev.block)) return;
if (ltFlickerToggle(ev.block)) return;
if (emberToggle(ev.block)) return;
if (lanternToggle(ev.block)) return;
if (jolToggle(ev.block)) return;
// su02 tube / su03 lounger over water are boats (ride_* component boards them) don't also sit.
const id = ev.block?.typeId;
if ((id?.startsWith("rzb_dcs:su02_") || id?.startsWith("rzb_dcs:su03_")) && tubeOverWater(ev.block)) return;
sit(ev.player, ev.block);
});
system.beforeEvents.startup.subscribe((init) => {
const reg = init.blockComponentRegistry;
reg.registerCustomComponent("rzb_dcs:toggle_open", { onPlayerInteract(e) { const b = e.block; try { b.setPermutation(b.permutation.withState("rzb_dcs:open", !b.permutation.getState("rzb_dcs:open"))); } catch {} } });
reg.registerCustomComponent("rzb_dcs:grill", { onPlayerInteract(e) { coolerToggle(e.block); } });
reg.registerCustomComponent("rzb_dcs:cooler", { onPlayerInteract(e) { coolerToggle(e.block); } });
reg.registerCustomComponent("rzb_dcs:box_toggle", { onPlayerInteract(e) { boxToggle(e.block); } });
reg.registerCustomComponent("rzb_dcs:xmas_cycle", { onPlayerInteract(e) { xmasLightCycle(e.block); } });
reg.registerCustomComponent("rzb_dcs:lt_flicker", { onPlayerInteract(e) { ltFlickerToggle(e.block); } });
reg.registerCustomComponent("rzb_dcs:firepit", { onPlayerInteract(e) { emberToggle(e.block); } }); // firepit on<->off
reg.registerCustomComponent("rzb_dcs:torch", { onPlayerInteract(e) { emberToggle(e.block); } }); // bamboo torch on<->off
reg.registerCustomComponent("rzb_dcs:lantern", { onPlayerInteract(e) { lanternToggle(e.block); } }); // garden/camping lantern on<->off
reg.registerCustomComponent("rzb_dcs:jol", { onPlayerInteract(e) { // jack 'o lantern off<->on swap (brush cycles the carving instead)
const inv = e.player?.getComponent("minecraft:inventory")?.container;
if (inv?.getItem(e.player?.selectedSlotIndex ?? 0)?.typeId === "rzb_dcs:brush_color") return; // brush -> carving cycle (brush.js), not a toggle
jolToggle(e.block);
} });
});