added decobench assets added backend logic to best of my ability

main
MomokoKoigakubo 1 month ago
parent 4a48f0bae9
commit 5d1d0a5c21

@ -1 +1,32 @@
{"format_version":"1.21.100","minecraft:block":{"description":{"identifier":"rzb_dcs:invisible_block"},"components":{"minecraft:selection_box":{"origin":[-7.95,0,-7.95],"size":[15.9,15.9,15.9]},"minecraft:collision_box":{"origin":[-7.95,0,-7.95],"size":[15.9,15.9,15.9]},"minecraft:destructible_by_mining":false,"minecraft:destructible_by_explosion":false,"minecraft:movable":{"movement_type":"immovable"},"tag:rzb_dcs:inv_block_little":{},"minecraft:liquid_detection":{"can_contain_liquid":true,"detection_rules":[{"liquid_type":"water","on_liquid_touches":"no_reaction"}]},"minecraft:geometry":"geometry.rzb_dcs.none","minecraft:material_instances":{"*":{"texture":"rzb_dcs_none","render_method":"alpha_test"}},"rzb_dcs:breakblock_particle":{}}}}
{
"format_version": "1.21.60",
"minecraft:block": {
"description": {
"identifier": "rzb_dcs:invisible_block"
},
"components": {
"minecraft:material_instances": {
"*": {
"texture": "rzb_dcs_none",
"render_method": "alpha_test"
}
},
"minecraft:collision_box": {
"origin": [
-7.95,
0,
-7.95
],
"size": [
15.9,
15.9,
15.9
]
},
"minecraft:selection_box": false,
"minecraft:destructible_by_mining": false,
"minecraft:destructible_by_explosion": false,
"minecraft:light_dampening": 0
}
}
}

@ -0,0 +1,208 @@
{
"format_version": "1.21.60",
"minecraft:entity": {
"description": {
"identifier": "rzb_dcs:decobench",
"is_spawnable": true,
"is_summonable": true,
"is_experimental": false
},
"components": {
"minecraft:type_family": {
"family": [
"decobench",
"inanimate"
]
},
"minecraft:health": {
"value": 10,
"max": 10
},
"minecraft:physics": {
"has_gravity": false,
"has_collision": false
},
"minecraft:collision_box": {
"width": 1,
"height": 1
},
"minecraft:custom_hit_test": {
"hitboxes": [
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
0
]
}
]
},
"minecraft:pushable": {
"is_pushable": false,
"is_pushable_by_piston": false
},
"minecraft:knockback_resistance": {
"value": 1
},
"minecraft:persistent": {},
"minecraft:fire_immune": true,
"minecraft:nameable": {
"allow_name_tag_renaming": false
},
"minecraft:damage_sensor": {
"triggers": [
{
"cause": "all",
"deals_damage": "no"
}
]
},
"minecraft:interact": {
"interactions": [
{
"on_interact": {
"filters": {
"test": "is_family",
"subject": "other",
"value": "player"
},
"event": "rzb_dcs:open"
},
"swing": true
}
]
}
},
"events": {
"rzb_dcs:open": {},
"rzb_dcs:ht_0": {
"add": {
"component_groups": [
"rzb_dcs:ht_0"
]
}
},
"rzb_dcs:ht_90": {
"add": {
"component_groups": [
"rzb_dcs:ht_90"
]
}
},
"rzb_dcs:ht_180": {
"add": {
"component_groups": [
"rzb_dcs:ht_180"
]
}
},
"rzb_dcs:ht_270": {
"add": {
"component_groups": [
"rzb_dcs:ht_270"
]
}
}
},
"component_groups": {
"rzb_dcs:ht_0": {
"minecraft:custom_hit_test": {
"hitboxes": [
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
0
]
},
{
"width": 1,
"height": 1,
"pivot": [
1,
0.5,
0
]
}
]
}
},
"rzb_dcs:ht_90": {
"minecraft:custom_hit_test": {
"hitboxes": [
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
0
]
},
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
1
]
}
]
}
},
"rzb_dcs:ht_180": {
"minecraft:custom_hit_test": {
"hitboxes": [
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
0
]
},
{
"width": 1,
"height": 1,
"pivot": [
-1,
0.5,
0
]
}
]
}
},
"rzb_dcs:ht_270": {
"minecraft:custom_hit_test": {
"hitboxes": [
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
0
]
},
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
-1
]
}
]
}
}
}
}
}

@ -0,0 +1,32 @@
{
"format_version": "1.21.60",
"minecraft:entity": {
"description": {
"identifier": "rzb_dcs:decobench_init",
"is_spawnable": true,
"is_summonable": true,
"is_experimental": false
},
"components": {
"minecraft:physics": {
"has_gravity": false,
"has_collision": false
},
"minecraft:collision_box": {
"width": 0.1,
"height": 0.1
},
"minecraft:damage_sensor": {
"triggers": [
{
"cause": "all",
"deals_damage": "no"
}
]
},
"minecraft:nameable": {
"allow_name_tag_renaming": false
}
}
}
}

@ -0,0 +1,82 @@
{
"format_version": "1.21.60",
"minecraft:entity": {
"description": {
"identifier": "rzb_dcs:decomposer",
"is_spawnable": true,
"is_summonable": true,
"is_experimental": false
},
"components": {
"minecraft:type_family": {
"family": [
"decomposer",
"inanimate"
]
},
"minecraft:health": {
"value": 10,
"max": 10
},
"minecraft:physics": {
"has_gravity": false,
"has_collision": false
},
"minecraft:collision_box": {
"width": 1,
"height": 1
},
"minecraft:custom_hit_test": {
"hitboxes": [
{
"width": 1,
"height": 1,
"pivot": [
0,
0.5,
0
]
}
]
},
"minecraft:pushable": {
"is_pushable": false,
"is_pushable_by_piston": false
},
"minecraft:knockback_resistance": {
"value": 1
},
"minecraft:persistent": {},
"minecraft:fire_immune": true,
"minecraft:nameable": {
"allow_name_tag_renaming": false
},
"minecraft:damage_sensor": {
"triggers": [
{
"cause": "all",
"deals_damage": "no"
}
]
},
"minecraft:interact": {
"interactions": [
{
"on_interact": {
"filters": {
"test": "is_family",
"subject": "other",
"value": "player"
},
"event": "rzb_dcs:open"
},
"swing": true
}
]
}
},
"events": {
"rzb_dcs:open": {}
}
}
}

@ -0,0 +1,32 @@
{
"format_version": "1.21.60",
"minecraft:entity": {
"description": {
"identifier": "rzb_dcs:decomposer_init",
"is_spawnable": true,
"is_summonable": true,
"is_experimental": false
},
"components": {
"minecraft:physics": {
"has_gravity": false,
"has_collision": false
},
"minecraft:collision_box": {
"width": 0.1,
"height": 0.1
},
"minecraft:damage_sensor": {
"triggers": [
{
"cause": "all",
"deals_damage": "no"
}
]
},
"minecraft:nameable": {
"allow_name_tag_renaming": false
}
}
}
}

@ -0,0 +1,27 @@
{
"format_version": "1.21.60",
"minecraft:item": {
"description": {
"identifier": "rzb_dcs:deco_seasons_decomp",
"menu_category": {
"category": "items",
"group": "rzb_dcs:group_decostation"
}
},
"components": {
"minecraft:icon": {
"textures": {
"default": "rzb_dcs_deco_seasons_decomp"
}
},
"minecraft:max_stack_size": 16,
"minecraft:hand_equipped": true,
"minecraft:entity_placer": {
"entity": "rzb_dcs:decomposer_init"
},
"minecraft:display_name": {
"value": "item.rzb_dcs:deco_seasons_decomp.name"
}
}
}
}

@ -0,0 +1,27 @@
{
"format_version": "1.21.60",
"minecraft:item": {
"description": {
"identifier": "rzb_dcs:seasons_bench",
"menu_category": {
"category": "items",
"group": "rzb_dcs:group_decostation"
}
},
"components": {
"minecraft:icon": {
"textures": {
"default": "rzb_dcs_seasons_bench"
}
},
"minecraft:max_stack_size": 16,
"minecraft:hand_equipped": true,
"minecraft:entity_placer": {
"entity": "rzb_dcs:decobench_init"
},
"minecraft:display_name": {
"value": "item.rzb_dcs:seasons_bench.name"
}
}
}
}

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
// features/break_particle.js custom block break particle
// features/break_particle.js custom block break particle
import { system } from "../core/mc.js";

@ -1,4 +1,4 @@
// features/brush.js the Color Brush recolour
// features/brush.js the Color Brush recolour
import { world, system, BlockPermutation, getDir, safeGetBlock } from "../core/mc.js";
import { VARIANT_SEQ } from "../core/variants.js";
import { particleFor, brushFx, debounced } from "../core/fx.js";

@ -1,15 +1,15 @@
// features/decor.js standalone brush recolorable decoration blocks.
// features/decor.js standalone brush recolorable decoration blocks.
import { registerVariants } from "../core/variants.js";
registerVariants(["rzb_dcs:sp22_white", "rzb_dcs:sp22_yellow", "rzb_dcs:sp22_pink", "rzb_dcs:sp22_purple", "rzb_dcs:sp22_red", "rzb_dcs:sp22_salmon"]); // rose vase
registerVariants(["rzb_dcs:sp25_pink", "rzb_dcs:sp25_red"]); // valentine card
registerVariants(["rzb_dcs:sp21_white", "rzb_dcs:sp21_yellow", "rzb_dcs:sp21_pink", "rzb_dcs:sp21_red"]); // potted cherry branch
registerVariants(["rzb_dcs:sp12_silver", "rzb_dcs:sp12_gold", "rzb_dcs:sp12_rosegold"]); // engagement ring (metals natural order)
registerVariants(["rzb_dcs:sp12_silver", "rzb_dcs:sp12_gold", "rzb_dcs:sp12_rosegold"]); // engagement ring (metals natural order)
registerVariants(["rzb_dcs:sp11_yellow", "rzb_dcs:sp11_pink", "rzb_dcs:sp11_white"]); // easter basket
registerVariants(["rzb_dcs:sp26_tulips", "rzb_dcs:sp26_flowers1", "rzb_dcs:sp26_flowers2"]); // watering can (flower types natural order)
registerVariants(["rzb_dcs:sp27_plain", "rzb_dcs:sp27_spring"]); // welcome mat (styles natural order)
registerVariants(["rzb_dcs:sp26_tulips", "rzb_dcs:sp26_flowers1", "rzb_dcs:sp26_flowers2"]); // watering can (flower types natural order)
registerVariants(["rzb_dcs:sp27_plain", "rzb_dcs:sp27_spring"]); // welcome mat (styles natural order)
registerVariants(["rzb_dcs:sp37_white", "rzb_dcs:sp37_red"]); // spider lily bouquet
registerVariants(["rzb_dcs:sp39_yellow", "rzb_dcs:sp39_pink", "rzb_dcs:sp39_purple", "rzb_dcs:sp39_blue", "rzb_dcs:sp39_green"]); // spring armchair (canonical cycle order)
registerVariants(["rzb_dcs:sp40_yellow", "rzb_dcs:sp40_pink", "rzb_dcs:sp40_purple", "rzb_dcs:sp40_blue", "rzb_dcs:sp40_green"]); // spring couch left
@ -28,7 +28,7 @@ registerVariants(["rzb_dcs:su37_yellow", "rzb_dcs:su37_green"]); // potted bambo
registerVariants(["rzb_dcs:sp44_blue_and_purple", "rzb_dcs:sp44_pink_and_green"]); // candle lanterns (spring, 3-frame flipbook)
registerVariants(["rzb_dcs:wi49_blue", "rzb_dcs:wi49_green", "rzb_dcs:wi49_red", "rzb_dcs:wi49_white"]); // candle holder / advent candlestick (winter, 3-frame flipbook)
registerVariants(["rzb_dcs:sp45", "rzb_dcs:sp45_rainbow"]); // wind chime 2 (spring) OFF-block form; brushing the animated entity recolours via toggles.js
// rose vine wall (spring) 3 shape pieces for building vine chains, each brush-cyclable through its 11 colours
// rose vine wall (spring) 3 shape pieces for building vine chains, each brush-cyclable through its 11 colours
registerVariants(["rzb_dcs:sp47_red", "rzb_dcs:sp47_pink", "rzb_dcs:sp47_pink_alt", "rzb_dcs:sp47_orange", "rzb_dcs:sp47_yellow", "rzb_dcs:sp47_yellow_alt", "rzb_dcs:sp47_green", "rzb_dcs:sp47_blue", "rzb_dcs:sp47_purple", "rzb_dcs:sp47_white", "rzb_dcs:sp47_black"]); // middle
registerVariants(["rzb_dcs:sp48_red", "rzb_dcs:sp48_pink", "rzb_dcs:sp48_pink_alt", "rzb_dcs:sp48_orange", "rzb_dcs:sp48_yellow", "rzb_dcs:sp48_yellow_alt", "rzb_dcs:sp48_green", "rzb_dcs:sp48_blue", "rzb_dcs:sp48_purple", "rzb_dcs:sp48_white", "rzb_dcs:sp48_black"]); // diagonal left
registerVariants(["rzb_dcs:sp49_red", "rzb_dcs:sp49_pink", "rzb_dcs:sp49_pink_alt", "rzb_dcs:sp49_orange", "rzb_dcs:sp49_yellow", "rzb_dcs:sp49_yellow_alt", "rzb_dcs:sp49_green", "rzb_dcs:sp49_blue", "rzb_dcs:sp49_purple", "rzb_dcs:sp49_white", "rzb_dcs:sp49_black"]); // diagonal right
@ -74,6 +74,6 @@ registerVariants(["rzb_dcs:bl_p_orange", "rzb_dcs:bl_p_yellow", "rzb_dcs:bl_p_li
registerVariants(["rzb_dcs:bl_heart_white", "rzb_dcs:bl_heart_orange", "rzb_dcs:bl_heart_yellow", "rzb_dcs:bl_heart_pink", "rzb_dcs:bl_heart_cyan", "rzb_dcs:bl_heart_purple", "rzb_dcs:bl_heart_blue", "rzb_dcs:bl_heart_green", "rzb_dcs:bl_heart_red", "rzb_dcs:bl_heart_berry", "rzb_dcs:bl_heart_rainbow"]); // heart balloons (regular)
registerVariants(["rzb_dcs:bl_heart_p_orange", "rzb_dcs:bl_heart_p_yellow", "rzb_dcs:bl_heart_p_lime", "rzb_dcs:bl_heart_p_pink", "rzb_dcs:bl_heart_p_cyan", "rzb_dcs:bl_heart_p_purple", "rzb_dcs:bl_heart_p_blue", "rzb_dcs:bl_heart_p_green", "rzb_dcs:bl_heart_p_red"]); // heart balloons (pastel)
// (balloons_small is a 2-cell multiblock -> brush-cycled via a both-cells branch in brush.js, NOT registerVariants.)
// jack 'o lantern carvings two groups so brushing keeps the on/off state (off cycles off, lit cycles lit); facing preserved
// jack 'o lantern carvings two groups so brushing keeps the on/off state (off cycles off, lit cycles lit); facing preserved
registerVariants(["rzb_dcs:fa33_1", "rzb_dcs:fa33_2", "rzb_dcs:fa33_3", "rzb_dcs:fa33_4"]); // carvings (off)
registerVariants(["rzb_dcs:fa33_1_on", "rzb_dcs:fa33_2_on", "rzb_dcs:fa33_3_on", "rzb_dcs:fa33_4_on"]); // carvings (lit)

@ -25,6 +25,9 @@ const STATIONS = new Set(["rzb_dcs:decobench", "rzb_dcs:decomposer"]);
const OBJ = { m: "rzb_dcs_deco_mat", r: "rzb_dcs_deco_red", g: "rzb_dcs_deco_green", b: "rzb_dcs_deco_blue" };
const FEED_ITEM = { m: "rzb_dcs:raw_material", r: "minecraft:red_dye", g: "minecraft:green_dye", b: "minecraft:blue_dye" };
const CHANNELS = ["m", "r", "g", "b"];
// decomposer fuel (DecoCraft): coal only, +4 per coal up to 32, -4 consumed per block-decompose
const FUEL = { obj: "rzb_dcs_decomp_fuel", max: 32, perCoal: 4, perDecomp: 4 };
const FUEL_ITEMS = ["minecraft:coal", "minecraft:charcoal"];
// block -> how many make 1 raw_material (DecoCraft rates). dirt 8, cobblestone 4, every log/wood 2.
const DECOMP = { "minecraft:dirt": 8, "minecraft:cobblestone": 4 };
@ -44,6 +47,10 @@ const get = (station, key) => { try { return objective(key).getScore(station) ??
const setRaw = (station, key, v) => { try { objective(key).setScore(station, Math.max(0, Math.min(CAP[key], v))); } catch {} };
const addUnits = (station, key, delta) => setRaw(station, key, get(station, key) + delta);
const balance = (station) => ({ m: get(station, "m"), r: get(station, "r"), g: get(station, "g"), b: get(station, "b") });
// decomposer fuel scoreboard (participant = the decomposer entity)
const fuelObj = () => { let o = world.scoreboard.getObjective(FUEL.obj); if (!o) { try { o = world.scoreboard.addObjective(FUEL.obj, FUEL.obj); } catch { o = world.scoreboard.getObjective(FUEL.obj); } } return o; };
const fuelGet = (st) => { try { return fuelObj().getScore(st) ?? 0; } catch { return 0; } };
const fuelSet = (st, v) => { try { fuelObj().setScore(st, Math.max(0, Math.min(FUEL.max, v))); } catch {} };
// mirror the balance onto the station's meter_* props so the model can show fill
function syncMeters(station) {
@ -72,6 +79,15 @@ function takeFromPlayer(player, itemId, count) {
}
return count - need;
}
// pull up to `count` of itemId from the player's MAINHAND stack only (DecoCraft consumes the held stack, not the inventory)
function takeFromHand(player, itemId, count) {
const inv = player?.getComponent("minecraft:inventory")?.container; if (!inv) return 0;
const i = player.selectedSlotIndex ?? 0, it = inv.getItem(i);
if (it?.typeId !== itemId) return 0;
const take = Math.min(it.amount, count);
if (it.amount - take <= 0) inv.setItem(i, undefined); else { it.amount -= take; inv.setItem(i, it); }
return take;
}
function givePlayer(player, itemId, count) {
const inv = player?.getComponent("minecraft:inventory")?.container;
const stack = new ItemStack(itemId, Math.max(1, Math.min(64, count)));
@ -106,11 +122,21 @@ function decompose(station, slot) {
const d = slotDeco(slot); if (!d || !d.cost) return;
const player = nearestPlayer(station); if (!player) return;
if (takeFromPlayer(player, d.id, 1) < 1) return; // must hold the decoration to break it
for (const k of CHANNELS) if (d.cost[k]) givePlayer(player, FEED_ITEM[k], d.cost[k]); // hand its materials back as items
if (d.cost.m) givePlayer(player, FEED_ITEM.m, d.cost.m); // raw_material is 1 unit/item -> give m items
for (const k of ["r", "g", "b"]) { const items = Math.floor((d.cost[k] || 0) / UNITS[k]); if (items) givePlayer(player, FEED_ITEM[k], items); } // dye cost is in UNITS -> convert to items (/8), no 8x inflation
try { world.scoreboard.getObjective("rzb_dcs_logs_decomposed")?.addScore(station, 1); } catch {}
}
// blocks -> raw_material, from the player's held stack (floor by rate, leftover stays), like DecoCraft
// coal -> fuel (DecoCraft: +4 per coal up to 32, from the held stack)
function feedFuel(station) {
const player = nearestPlayer(station); if (!player) return;
const space = FUEL.max - fuelGet(station); if (space < FUEL.perCoal) return;
const maxCoal = Math.floor(space / FUEL.perCoal);
let took = 0; for (const it of FUEL_ITEMS) { took = takeFromHand(player, it, maxCoal); if (took) break; }
if (took) fuelSet(station, fuelGet(station) + took * FUEL.perCoal);
}
// blocks -> raw_material, from the player's held stack (floor by rate, leftover stays). Requires + consumes coal fuel (DecoCraft)
function decompBlock(station) {
if (fuelGet(station) < FUEL.perDecomp) { try { nearestPlayer(station)?.playSound("note.bass", { volume: 0.6 }); } catch {} return; } // out of fuel
const player = nearestPlayer(station); if (!player) return;
const inv = player.getComponent("minecraft:inventory")?.container; if (!inv) return;
const idx = player.selectedSlotIndex ?? 0, held = inv.getItem(idx);
@ -120,13 +146,14 @@ function decompBlock(station) {
if (held.amount - used <= 0) inv.setItem(idx, undefined);
else { held.amount -= used; inv.setItem(idx, held); }
givePlayer(player, FEED_ITEM.m, toraw);
fuelSet(station, fuelGet(station) - FUEL.perDecomp); // remove_fuel per process
}
function feed(station, key) {
const player = nearestPlayer(station); if (!player) return;
const space = CAP[key] - get(station, key); if (space <= 0) return;
const per = UNITS[key];
const maxItems = Math.floor(space / per) + (space % per ? 1 : 0); // last item may partly fill
const took = takeFromPlayer(player, FEED_ITEM[key], maxItems); if (!took) return;
const took = takeFromHand(player, FEED_ITEM[key], maxItems); if (!took) return;
addUnits(station, key, Math.min(space, took * per));
syncMeters(station);
}
@ -134,10 +161,9 @@ function feed(station, key) {
function feedCompound(station, itemId, kA, kB) {
const player = nearestPlayer(station); if (!player) return;
const per = 4, spaceA = CAP[kA] - get(station, kA), spaceB = CAP[kB] - get(station, kB);
const room = Math.max(spaceA, spaceB); if (room <= 0) return;
const took = takeFromPlayer(player, itemId, Math.ceil(room / per)); if (!took) return;
addUnits(station, kA, Math.min(spaceA, took * per));
addUnits(station, kB, Math.min(spaceB, took * per));
const maxItems = Math.min(Math.floor(spaceA / per), Math.floor(spaceB / per)); if (maxItems <= 0) return; // both channels must accept the full 4 (no dye wasted)
const took = takeFromHand(player, itemId, maxItems); if (!took) return;
addUnits(station, kA, took * per); addUnits(station, kB, took * per);
syncMeters(station);
}
function drop(station, key) {
@ -147,6 +173,11 @@ function drop(station, key) {
dropAt(station.dimension, station.location, FEED_ITEM[key], items);
syncMeters(station);
}
// return ALL stored materials as items (DecoCraft returns these when a bench is broken)
export function dropAll(station) { for (const k of CHANNELS) drop(station, k); }
function dropFuel(station) { const coal = Math.floor(fuelGet(station) / FUEL.perCoal); if (coal > 0) dropAt(station.dimension, station.location, "minecraft:coal", coal); fuelSet(station, 0); }
// on break: the bench returns its stored materials, the decomposer returns its stored fuel as coal
export function dropStored(station) { if (station?.typeId === "rzb_dcs:decomposer") dropFuel(station); else dropAll(station); }
function refresh(station, slot) { const d = slotDeco(slot); if (d?.cost) writeSlotState(slot, station, d.cost); }
// the /scriptevent router; the UI drives everything through here
@ -154,6 +185,7 @@ const HANDLERS = {
"rzb_dcs:deco_craft": (st, src) => craft(st, src),
"rzb_dcs:deco_decompose": (st, src) => decompose(st, src),
"rzb_dcs:decomp_block": (st) => decompBlock(st),
"rzb_dcs:feed_fuel": (st) => feedFuel(st),
"rzb_dcs:deco_refresh": (st, src) => refresh(st, src),
"rzb_dcs:feed_mat": (st) => feed(st, "m"),
"rzb_dcs:feed_red": (st) => feed(st, "r"),

@ -1,5 +1,5 @@
// features/display.js animated display pieces (place a block -> spawn an entity).
// features/display.js animated display pieces (place a block -> spawn an entity).
import { DISPLAY_DIR_YAW } from "../core/grid.js";
import { summonDisplay, breakFx, particleFor } from "../core/fx.js";
import { onPlace, onBreak } from "../core/events.js";

@ -1,5 +1,5 @@
// features/interact.js right-click lids/toggles + the interact fall-through dispatcher.
// 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";
@ -110,7 +110,7 @@ world.afterEvents.playerInteractWithBlock?.subscribe((ev) => {
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.
// 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);

@ -1,4 +1,4 @@
// features/maintenance.js pick-block anchor redirect + firepit embers.
// features/maintenance.js pick-block anchor redirect + firepit embers.
import { world, system, ItemStack, safeGetBlock, isCreative } from "../core/mc.js";
import { onPlace, onBreak } from "../core/events.js";
import { UMBRELLA_BAMBOO, UMBRELLA_BEACH, BAMBOO_TABLE } from "./mb_grid.js";
@ -87,7 +87,7 @@ export const EMBER_BLOCKS = {
"rzb_dcs:su13_green": { y: 0.85, ent: TORCH_ENT },
"rzb_dcs:su13_yellow": { y: 0.85, ent: TORCH_ENT },
"rzb_dcs:fa37": { y: 0.7, ent: CAULDRON_ENT },
"rzb_dcs:fa08": { y: 0.4, ent: SPARKLE_ENT }, // crystal ball always-on twinkling sparkle ring around the orb (no lit state)
"rzb_dcs:fa08": { y: 0.4, ent: SPARKLE_ENT }, // crystal ball always-on twinkling sparkle ring around the orb (no lit state)
// open grill = smoke; the CLOSED grill ids aren't here, so the sweep removes the emitter when it's shut (no lit state)
"rzb_dcs:su19_black": { y: 0.9, ent: GRILL_ENT },
"rzb_dcs:su19_blue": { y: 0.9, ent: GRILL_ENT },

@ -1,4 +1,4 @@
// features/mb_grid.js grid & canopy multiblocks, defined as data.
// features/mb_grid.js grid & canopy multiblocks, defined as data.
import { defineGridMultiblock } from "../core/multiblock.js";

@ -1,4 +1,4 @@
// features/mb_pairs.js partner / vertical / 2x2 multiblocks.
// features/mb_pairs.js partner / vertical / 2x2 multiblocks.
import { world, system, BlockPermutation, ItemStack, isCreative, getDir, safeGetBlock } from "../core/mc.js";
import { fcCanReplace } from "../core/grid.js";
import { undoPlace, breakAir } from "../core/fx.js";

@ -1,11 +1,11 @@
// features/mb_trees.js christmas / light / potted trees as multiblocks.
// features/mb_trees.js christmas / light / potted trees as multiblocks.
import { BlockPermutation, ItemStack, isCreative, getDir, safeGetBlock } from "../core/mc.js";
import { grid, fcCanReplace } from "../core/grid.js";
import { undoPlace, breakAir } from "../core/fx.js";
import { onPlace, onBreak } from "../core/events.js";
// Rainbow and white are INDEPENDENT block sets (white id = rainbow id + "_w"), each registered as a plain multiblock no
// Rainbow and white are INDEPENDENT block sets (white id = rainbow id + "_w"), each registered as a plain multiblock no
// rzb_dcs:tc state. Pick-block therefore returns the correct item (and name) natively; brush.js converts between the two.
// --- grid trees (christmas / light): a 3D cell grid placed from the anchor cell ---

@ -1,4 +1,4 @@
// features/rideables.js block <-> rideable-entity vehicles (tube, flamingo, lounger, bike, surfboard, basket bike, sled).
// features/rideables.js block <-> rideable-entity vehicles (tube, flamingo, lounger, bike, surfboard, basket bike, sled).
// Parks as a block; a ride_* interact spawns a steerable entity; riderless it reverts (color + heading kept).
// Water craft steer via applyImpulse + no-Y setRotation (engine keeps float); land bikes velocity-drive with gravity.
@ -135,7 +135,7 @@ function steerForward(p, ent, yawOff = 0) {
// Every buoyant water craft (tube, flamingo, lounger, surfboard) steers identically: impulse horizontal velocity toward
// look+WASD * speed (coast when idle), face the travel heading. Only the pose anim, speed, yaw offset, and turn style
// vary smoothTurn>0 lerps the body toward heading (the round tube), 0 snaps instantly (directional lounger/surfboard).
// vary smoothTurn>0 lerps the body toward heading (the round tube), 0 snaps instantly (directional lounger/surfboard).
function driveWaterCraft(p, boat, anim, speed, yawOff = 0, smoothTurn = 0) {
try { p.playAnimation(anim, { blendOutTime: 0, stopExpression: "!q.is_riding" }); } catch {}
let cur; try { cur = boat.getVelocity(); } catch { cur = { x: 0, y: 0, z: 0 }; }
@ -263,7 +263,7 @@ function landImpulse(veh, dim, loc, fx, fz, rx, rz, ix, spd, strafe, breakFn) {
return { nearGround, gspd: Math.hypot(vel.x, vel.z) * sg, broke };
}
// VELOCITY DRIVE: engine owns Y (gravity), collision, and carries the rider. We steer only horizontal velocity via
// applyImpulse + face with setRotation NO teleport (teleport pins position and kills gravity).
// applyImpulse + face with setRotation NO teleport (teleport pins position and kills gravity).
function driveBike(p, bike) {
const dim = bike.dimension, loc = bike.location;
let spd0 = 0; try { spd0 = bike.getDynamicProperty("spd") ?? 0; } catch {}
@ -398,14 +398,14 @@ const revertBasketBike = (t, dim) => revert2Cell(t, dim, BB_RIG);
const SLED_ENTITY = "rzb_dcs:sled";
export const SLED_B = "rzb_dcs:wi41_b_", SLED_FR = "rzb_dcs:wi41_fr_";
const SLED_COLORS = ["red", "green", "blue", "yellow"]; // entity color index do NOT reorder
const SLED_COLORS = ["red", "green", "blue", "yellow"]; // entity color index do NOT reorder
export const SLED_ORDER = ["yellow", "blue", "green", "red"]; // brush cycle order (canonical); decoupled from the index map
export const SLED_CELLS = { "0_0_0": [0, 0, 0], "0_0_1": [0, 0, 1] }; // anchor(back) + front, one block forward (+FWD)
export const SLED_PREFIX = { "0_0_0": SLED_B, "0_0_1": SLED_FR };
const SLED_MAX_SNOW = 0.5, SLED_MAX_SLOW = 0.16, SLED_ACCEL = 0.03, SLED_DECEL = 0.03, SLED_STRAFE = 0.1, SLED_STEP = 1.05;
const SLED_YAW_OFF = 180; // sled drives "backwards" -> flip the model 180 so its front points the way of travel
const isSnow = (id) => !!id && /snow|ice|powder_snow/.test(id);
const isSand = (id) => !!id && /sand|gravel/.test(id); // loose ground bikes bog down like on snow
const isSand = (id) => !!id && /sand|gravel/.test(id); // loose ground bikes bog down like on snow
registerVariants([...SLED_COLORS.map((c) => SLED_B + c), ...SLED_COLORS.map((c) => SLED_FR + c)]); // brush gate; cycling uses SLED_ORDER
const _slH = makeCellHelpers(SLED_CELLS, SLED_PREFIX);
export const sledCellWorld = _slH.cellWorld, sledAnchorFrom = _slH.anchorFrom, sledParse = _slH.parse;

@ -1,4 +1,4 @@
// features/seating.js the sit system (seat entity + per-furniture config).
// features/seating.js the sit system (seat entity + per-furniture config).
import { world, system, isCreative, getDir, safeGetBlock } from "../core/mc.js";
import { DIR_YAW, DIR_TROT, FWD } from "../core/grid.js";
import { FLOWER_CART, LOUNGER } from "./mb_grid.js";

@ -0,0 +1,55 @@
// 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 {}
});

@ -25,6 +25,7 @@
// maintenance pick block anchor redirect + firepit embers
// bounce springy blocks that launch a player who lands on them
// decostation decobench + decomposer economy backend (scoreboards, scriptevent router, block/deco to materials)
// stations place the decobench/decomposer station entities from a placer item (UI/panel = coworker's to build)
// break_particle custom block break particle
//
// To add a prop: plain recolorable block -> one line in decor.js; rotating/canopy multiblock -> one defineGridMultiblock() in mb_grid.js; anything bespoke -> its own features/ module.
@ -43,4 +44,5 @@ import "./features/brush.js";
import "./features/maintenance.js";
import "./features/bounce.js";
import "./features/decostation.js";
import "./features/stations.js";
import "./features/break_particle.js";

@ -0,0 +1,20 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "rzb_dcs:decobench",
"materials": {
"default": "entity_alphatest"
},
"geometry": {
"default": "geometry.rzb_dcs.decobench"
},
"textures": {
"default": "textures/entity/rzb_dcs/decobench"
},
"render_controllers": [
"controller.render.default"
]
}
}
}

@ -0,0 +1,20 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "rzb_dcs:decobench_init",
"materials": {
"default": "entity_alphatest"
},
"geometry": {
"default": "geometry.rzb_dcs.blank"
},
"textures": {
"default": "textures/entity/rzb_dcs/decobench"
},
"render_controllers": [
"controller.render.default"
]
}
}
}

@ -0,0 +1,20 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "rzb_dcs:decomposer",
"materials": {
"default": "entity_alphatest"
},
"geometry": {
"default": "geometry.rzb_dcs.decomposer"
},
"textures": {
"default": "textures/entity/rzb_dcs/decomposer"
},
"render_controllers": [
"controller.render.default"
]
}
}
}

@ -0,0 +1,20 @@
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "rzb_dcs:decomposer_init",
"materials": {
"default": "entity_alphatest"
},
"geometry": {
"default": "geometry.rzb_dcs.blank"
},
"textures": {
"default": "textures/entity/rzb_dcs/decomposer"
},
"render_controllers": [
"controller.render.default"
]
}
}
}

@ -0,0 +1 @@
{"format_version":"1.16.0","minecraft:geometry":[{"description":{"identifier":"geometry.rzb_dcs.blank","texture_width":16,"texture_height":16},"bones":[{"name":"root","pivot":[0,0,0]}]}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -3914,3 +3914,10 @@ item.rzb_dcs:fa48=Casket Ground
tile.rzb_dcs:fa48_b.name=Casket Ground
item.rzb_dcs:fa48_b.name=Casket Ground
item.rzb_dcs:fa48_b=Casket Ground
item.rzb_dcs:seasons_bench.name=Seasons Bench
item.rzb_dcs:seasons_bench=Seasons Bench
item.rzb_dcs:deco_seasons_decomp.name=Season Decomposer
item.rzb_dcs:deco_seasons_decomp=Season Decomposer
entity.rzb_dcs:decobench.name=Seasons Bench
entity.rzb_dcs:decomposer.name=Season Decomposer
tile.rzb_dcs:invisible_block.name=Invisible Block

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -2125,6 +2125,12 @@
},
"rzb_dcs_casket_ground": {
"textures": "textures/items/rzb_dcs/casket_ground"
},
"rzb_dcs_seasons_bench": {
"textures": "textures/items/rzb_dcs/seasons_bench"
},
"rzb_dcs_deco_seasons_decomp": {
"textures": "textures/items/rzb_dcs/deco_seasons_decomp"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Loading…
Cancel
Save