Files
SysAdmin 7c8cd5b075 feat(addons): hemp plant, wild cherry tree, naturalist-lite
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>
2026-04-27 22:00:06 +01:00

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.");
});