New addons: - hemp-addon: silverlabs:hemp_crop (5 ages, indoor sun-lamp grown vs outdoor sky-lit), shears harvest, cauldron tincture, brownie food, bonemeal, sun-lamp redstone-lit block (light_dampening: 0 so crops beneath stay lit), grass-seed bootstrap, wandering-trader buyback, pillager raid stealing. - trees-features-addon: ods_orch wild cherry tree — log/leaves/planks/ stripped/sapling/fruit blocks with seasonal fruit states, structure- spawn worldgen. - naturalist-lite-addon: 13-mob subset of Naturalist (deer, fox, owl, skunk, snake, hedgehog, red panda, capybara, elephant, kangaroo, moose, tiger, firefly), trimmed for Switch joinability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
4.1 KiB
JavaScript
114 lines
4.1 KiB
JavaScript
import { world, system } from "@minecraft/server";
|
|
|
|
const SAPLING_BLOCK = "ods_orch:wild_cherry_tree_sapling";
|
|
const LOG = "ods_orch:wild_cherry_tree_log";
|
|
const LEAVES = "ods_orch:wild_cherry_tree_leaves";
|
|
const FLOWER = "ods_orch:wild_cherry_tree_fruit_side";
|
|
|
|
function rand(n) { return Math.floor(Math.random() * n); }
|
|
function chance(p) { return Math.random() < p; }
|
|
|
|
// Build a wisteria tree procedurally. Variant 0/1/2 = subtle canopy width tweaks.
|
|
// Roughly 25% larger than the prior 5-tall structures: 7-block trunk, 7x7 base canopy.
|
|
function buildWisteria(dim, x, y, z, variant) {
|
|
const cmds = [];
|
|
const trunkH = 6 + (variant === 1 ? 1 : 0); // 6 or 7
|
|
const wide = variant === 2 ? 4 : 3; // half-extent of widest canopy ring
|
|
const mid = wide - 1;
|
|
const narrow = mid - 1;
|
|
|
|
// Clear sapling tile, then place trunk.
|
|
cmds.push(`setblock ${x} ${y} ${z} air`);
|
|
for (let dy = 0; dy < trunkH; dy++) {
|
|
cmds.push(`setblock ${x} ${y + dy} ${z} ${LOG}`);
|
|
}
|
|
|
|
const canopyBase = y + trunkH - 2; // canopy starts 2 below the very top so the trunk pokes up
|
|
// Wide ring (full square minus corners)
|
|
fillSquareRing(cmds, x, canopyBase, z, wide, true);
|
|
fillSquareRing(cmds, x, canopyBase + 1, z, wide, true);
|
|
// Mid ring
|
|
fillSquareRing(cmds, x, canopyBase + 2, z, mid, false);
|
|
// Narrow cap
|
|
fillSquareRing(cmds, x, canopyBase + 3, z, narrow, false);
|
|
cmds.push(`setblock ${x} ${canopyBase + 4} ${z} ${LEAVES} [] replace air`);
|
|
|
|
// Hanging wisteria flower tassels: drop FLOWER blocks 1-3 below the wide ring edges.
|
|
const edgeOffsets = [
|
|
[-wide, 0], [wide, 0], [0, -wide], [0, wide],
|
|
[-mid, -mid], [-mid, mid], [mid, -mid], [mid, mid],
|
|
[-wide, -1], [-wide, 1], [wide, -1], [wide, 1],
|
|
[-1, -wide], [1, -wide], [-1, wide], [1, wide],
|
|
];
|
|
for (const [dx, dz] of edgeOffsets) {
|
|
if (!chance(0.7)) continue;
|
|
const drop = 1 + rand(3); // 1-3 blocks
|
|
for (let i = 1; i <= drop; i++) {
|
|
cmds.push(`setblock ${x + dx} ${canopyBase - i} ${z + dz} ${FLOWER} [] replace air`);
|
|
}
|
|
}
|
|
|
|
return cmds;
|
|
}
|
|
|
|
// Fill a hollow square (or solid) ring of LEAVES at level y with given half-extent r.
|
|
// `clipCorners` true = drop the four extreme corners for a softer outline.
|
|
function fillSquareRing(cmds, cx, cy, cz, r, clipCorners) {
|
|
const x1 = cx - r, x2 = cx + r;
|
|
const z1 = cz - r, z2 = cz + r;
|
|
cmds.push(`fill ${x1} ${cy} ${z1} ${x2} ${cy} ${z2} ${LEAVES} replace air`);
|
|
if (clipCorners) {
|
|
cmds.push(`setblock ${x1} ${cy} ${z1} air`);
|
|
cmds.push(`setblock ${x2} ${cy} ${z1} air`);
|
|
cmds.push(`setblock ${x1} ${cy} ${z2} air`);
|
|
cmds.push(`setblock ${x2} ${cy} ${z2} air`);
|
|
}
|
|
}
|
|
|
|
function growSapling(block, player) {
|
|
const dim = block.dimension;
|
|
const { x, y, z } = block.location;
|
|
const variant = rand(3);
|
|
const cmds = buildWisteria(dim, x, y, z, variant);
|
|
let i = 0;
|
|
// Throttle to ~30 commands per tick to avoid command-rate spikes.
|
|
const step = () => {
|
|
const end = Math.min(i + 30, cmds.length);
|
|
for (; i < end; i++) {
|
|
try { dim.runCommand(cmds[i]); } catch (_) {}
|
|
}
|
|
if (i < cmds.length) system.runTimeout(step, 1);
|
|
else if (player) player.sendMessage("§5[Trees] §7A wisteria blooms.");
|
|
};
|
|
step();
|
|
}
|
|
|
|
// Bonemeal usage on sapling → grow tree
|
|
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
|
const block = event.block;
|
|
if (!block || block.typeId !== SAPLING_BLOCK) return;
|
|
const stack = event.itemStack;
|
|
if (!stack || stack.typeId !== "minecraft:bone_meal") return;
|
|
event.cancel = true;
|
|
const player = event.player;
|
|
system.run(() => {
|
|
growSapling(block, player);
|
|
// Consume one bonemeal
|
|
try {
|
|
const inv = player.getComponent("minecraft:inventory")?.container;
|
|
if (inv) {
|
|
const sel = player.selectedSlotIndex;
|
|
const cur = inv.getItem(sel);
|
|
if (cur && cur.typeId === "minecraft:bone_meal") {
|
|
if (cur.amount > 1) { cur.amount -= 1; inv.setItem(sel, cur); }
|
|
else { inv.setItem(sel, undefined); }
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
});
|
|
});
|
|
|
|
system.run(() => {
|
|
world.sendMessage("§5[Trees] §7Wisteria pack loaded.");
|
|
});
|