From eb82c8307bbeac5877602bc8646bd6bb17d6b7de Mon Sep 17 00:00:00 2001 From: SysAdmin Date: Mon, 27 Apr 2026 23:14:54 +0100 Subject: [PATCH] feat(hemp): wild patches in plains/forest + chest seed injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new ways to find hemp seeds without already having any: 1. Worldgen: minecraft:scatter_feature spawns 1-3 mature silverlabs:hemp_crop blocks on grass/dirt in plains/forest/birch/ flower_forest biomes (~14% scatter chance per chunk surface pass). 2. Chest injection: 8% chance per chest first-open to plant 1-3 seeds in a random empty slot. Tracked per-chest via world dynamic property (rolling cap of 500 entries) so each chest only contributes once. Bedrock has no loot-table merge mechanism so we can't add seeds to vanilla village chests without losing their vanilla loot — script injection sidesteps that and stays version-independent. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../feature_rules/hemp_patch_rule.json | 35 ++++++++++++ .../hemp_BP/features/hemp_patch_feature.json | 12 ++++ .../hemp_BP/features/hemp_single_feature.json | 27 +++++++++ hemp-addon/hemp_BP/scripts/main.js | 56 +++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 hemp-addon/hemp_BP/feature_rules/hemp_patch_rule.json create mode 100644 hemp-addon/hemp_BP/features/hemp_patch_feature.json create mode 100644 hemp-addon/hemp_BP/features/hemp_single_feature.json diff --git a/hemp-addon/hemp_BP/feature_rules/hemp_patch_rule.json b/hemp-addon/hemp_BP/feature_rules/hemp_patch_rule.json new file mode 100644 index 0000000..45125ad --- /dev/null +++ b/hemp-addon/hemp_BP/feature_rules/hemp_patch_rule.json @@ -0,0 +1,35 @@ +{ + "format_version": "1.21.0", + "minecraft:feature_rules": { + "description": { + "identifier": "silverlabs:hemp_patch_rule", + "places_feature": "silverlabs:hemp_patch_feature" + }, + "conditions": { + "placement_pass": "surface_pass", + "minecraft:biome_filter": [ + { + "any_of": [ + { "test": "has_biome_tag", "value": "plains" }, + { "test": "has_biome_tag", "value": "forest" }, + { "test": "has_biome_tag", "value": "birch" }, + { "test": "has_biome_tag", "value": "flower_forest" } + ] + }, + { "test": "has_biome_tag", "operator": "!=", "value": "monster" } + ] + }, + "distribution": { + "iterations": 1, + "scatter_chance": 14, + "x": { "distribution": "uniform", "extent": [0, 16] }, + "y": { + "distribution": "fixed_grid", + "extent": [0, 64], + "grid_offset": 0, + "step_size": 1 + }, + "z": { "distribution": "uniform", "extent": [0, 16] } + } + } +} diff --git a/hemp-addon/hemp_BP/features/hemp_patch_feature.json b/hemp-addon/hemp_BP/features/hemp_patch_feature.json new file mode 100644 index 0000000..2bdb66a --- /dev/null +++ b/hemp-addon/hemp_BP/features/hemp_patch_feature.json @@ -0,0 +1,12 @@ +{ + "format_version": "1.21.0", + "minecraft:scatter_feature": { + "description": { "identifier": "silverlabs:hemp_patch_feature" }, + "iterations": 3, + "scatter_chance": 65, + "x": { "distribution": "uniform", "extent": [-3, 4] }, + "y": 0, + "z": { "distribution": "uniform", "extent": [-3, 4] }, + "places_feature": "silverlabs:hemp_single_feature" + } +} diff --git a/hemp-addon/hemp_BP/features/hemp_single_feature.json b/hemp-addon/hemp_BP/features/hemp_single_feature.json new file mode 100644 index 0000000..89c435d --- /dev/null +++ b/hemp-addon/hemp_BP/features/hemp_single_feature.json @@ -0,0 +1,27 @@ +{ + "format_version": "1.21.0", + "minecraft:single_block_feature": { + "description": { "identifier": "silverlabs:hemp_single_feature" }, + "places_block": { + "name": "silverlabs:hemp_crop", + "states": { + "silverlabs:hemp_age": 4, + "silverlabs:hemp_top": false + } + }, + "enforce_survivability_rules": true, + "enforce_placement_rules": true, + "may_attach_to": { + "min_sides_must_attach": 1, + "top": [ + "minecraft:grass_block", + "minecraft:dirt", + "minecraft:podzol", + "minecraft:coarse_dirt" + ] + }, + "may_replace": [ + "minecraft:air" + ] + } +} diff --git a/hemp-addon/hemp_BP/scripts/main.js b/hemp-addon/hemp_BP/scripts/main.js index cffdb18..46415d7 100644 --- a/hemp-addon/hemp_BP/scripts/main.js +++ b/hemp-addon/hemp_BP/scripts/main.js @@ -681,6 +681,62 @@ world.afterEvents.itemCompleteUse.subscribe((event) => { } }); +// --- Chest loot injection: first-time-open chests sometimes contain seeds --- +// Bedrock has no merge-loot-table mechanism, so we hook chest opens and +// deposit hemp_seeds with low probability into a random empty slot. +// Tracking is per-chest via a world dynamic property holding a JSON list +// of "dim:x:y:z" keys; pruned to a rolling cap to bound storage. +const CHEST_TYPES = new Set(["minecraft:chest", "minecraft:trapped_chest"]); +const CHEST_SEED_CHANCE = 0.08; +const SEEDED_PROP = "hemp_seeded_chests_v1"; +const SEEDED_CAP = 500; + +function chestKey(block) { + const l = block.location; + return `${block.dimension.id}:${l.x}:${l.y}:${l.z}`; +} +function loadSeededChests() { + try { + const raw = world.getDynamicProperty(SEEDED_PROP); + return new Set(raw ? JSON.parse(raw) : []); + } catch (_) { return new Set(); } +} +function saveSeededChests(set) { + // Prune oldest if over cap (Set preserves insertion order in JS) + if (set.size > SEEDED_CAP) { + const arr = Array.from(set).slice(set.size - SEEDED_CAP); + set = new Set(arr); + } + try { + world.setDynamicProperty(SEEDED_PROP, JSON.stringify(Array.from(set))); + } catch (_) {} +} + +world.afterEvents.playerInteractWithBlock.subscribe((event) => { + const block = event.block; + if (!block || !CHEST_TYPES.has(block.typeId)) return; + // Only on the open hand (not while holding an item, to avoid placing-into events firing twice) + const stack = event.itemStack; + if (stack) return; + const key = chestKey(block); + const seeded = loadSeededChests(); + if (seeded.has(key)) return; + seeded.add(key); + saveSeededChests(seeded); + if (!chance(CHEST_SEED_CHANCE)) return; + // Try to insert into a random empty slot + let inv; + try { inv = block.getComponent("minecraft:inventory")?.container; } catch (_) { return; } + if (!inv) return; + const empties = []; + for (let i = 0; i < inv.size; i++) if (!inv.getItem(i)) empties.push(i); + if (empties.length === 0) return; + const slot = empties[rand(empties.length)]; + const count = 1 + rand(3); // 1-3 seeds + try { inv.setItem(slot, new ItemStack(SEEDS, count)); } catch (_) {} + // No chat ping — let the player discover the seeds organically. +}); + system.run(() => { world.sendMessage("§a[Hemp] §7Hemp pack loaded."); });