Compare commits

...

7 Commits

Author SHA1 Message Date
17a9faf206 feat(scripts): sync-world-pins.py — pre-deploy pin/dep audit + writer
All checks were successful
Deploy Addons / deploy (push) Successful in 14s
Single Python utility that catches the silent failure modes responsible
for today's debug session:

  --audit-only   walk every BP→RP manifest dependency, flag drift
  (default)      dry-run pin diff per world (resolves the active world
                 from server.properties so we never write to a stale dir)
  --apply        write corrected world_behavior_packs.json /
                 world_resource_packs.json over SSH
  --restart      after --apply, restart only containers whose pins
                 actually changed

Reads docker-compose.yml to discover what's mounted on each service,
preserves any pin whose pack_id isn't in our managed addon set
(vanilla packs, Security_Sandbox), and never hardcodes credentials —
takes --ssh-pass or env MC_SSH_PASS.

Drop into the deploy muscle memory:
  python3 scripts/sync-world-pins.py --audit-only        # CI-friendly
  MC_SSH_PASS=… python3 scripts/sync-world-pins.py --apply --restart

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:01:15 +01:00
9789906277 fix(addons): align BP→RP manifest deps + minecraft:geometry on placeholder blocks
Two silent failure modes hit eight addons today:

1. BP→RP dep drift. When an RP version was bumped, dependent BPs kept
   naming the old version. Bedrock loaded both packs but disconnected
   the texture pipeline, so blocks rendered as map_color cubes in the
   inventory. Aligned spark_pet, heyhe_pet, camping_supplies, dynamite,
   home_sign, postal_service, private_chest BP deps to actual RP
   versions. Bumped postal/private_chest RP versions to 1.0.1 to bust
   client RP caches.

2. Missing minecraft:geometry. Bedrock 1.21+ silently fails to render
   custom blocks in inventory unless geometry is declared — even for
   plain full cubes, no warning logged. Added
   minecraft:geometry.full_block to post_office, mailbox, and
   private_chest. Same fix already applied to sun_lamp and the wild
   cherry tree blocks in their respective addon commits.

Saved both failure modes to project memory so they're easy to recognise
next time someone sees "all my custom blocks show as solid coloured
cubes in the inventory".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:01:00 +01:00
6bda72598d feat(dynamite): throwable banger/bundle/dynamite entity assets
Entity definitions and projectile textures for the three throwables
(thrown_banger, thrown_bundle, thrown_dynamite). RP version bumped to
1.0.2 and BP→RP dependency aligned in lockstep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:00:42 +01:00
af9d37462c feat(hub-return): subtitle nav HUD, share waypoints, !nav fallback
Move the directional waypoint HUD off the action bar (which fights mount
saddle/jump UI for screen space) into the title/subtitle slot — large
rotating arrow + distance up top, label underneath, refreshed every 5
ticks so it stays pinned. Active waypoint now persists across container
restarts via per-player dynamic property instead of an in-memory Map.

New:
- !share command + 📤 button on the compass form: pick a waypoint, pick
  a recipient, send them an Accept/Decline prompt; copies into their
  list as "Label (from sender)" with capacity check.
- !nav chat fallback: list waypoints with distances, switch active
  with !nav <n>, !nav off to clear.
- hub_return_transfer_RP scaffold for future asset overrides.

docker-compose: mount the new RP on jamie/lyla/mya.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 22:00:31 +01:00
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
b9e3380f6c feat(camping): three-tier ore detector with private-chest faraday cage
New basic/improved/advanced detectors (8/16/32 block range). Aim and
right-click to ping the nearest ore on the view ray; pitch-coded sound
and action-bar text show distance and ore type. Any ore within 4 blocks
of a silverlabs:private_chest is hidden — chests act as faraday cages
so claimed bases stay private from neighbours' detectors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:59:27 +01:00
145f5d9beb feat(spark): time-of-day sleep cycle + needy smoke particle fix
Switch sleep transitions from day_light_level (broken indoors) to
time_of_day, and fix the mood_needy particle reference from the
non-existent minecraft:large_smoke to minecraft:basic_smoke_particle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:59:13 +01:00
1023 changed files with 193770 additions and 79 deletions

View File

@@ -2,22 +2,38 @@
"format_version": 2,
"header": {
"name": "Hey Hey Chicken Pet",
"description": "Hey Hey the loyal Moana chicken tameable, following pet with waddly personality.",
"description": "Hey Hey the loyal Moana chicken \u2014 tameable, following pet with waddly personality.",
"uuid": "fc811a53-dbdb-4701-bc63-c3ca1d793c47",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "f7aae24a-06d8-432b-85dc-359c4fda9dfd",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
}
],
"dependencies": [
{
"uuid": "45ec17df-0d84-456f-b2cc-2d990f96e6d5",
"version": [1, 0, 0]
"version": [
1,
0,
1
]
}
]
}

View File

@@ -2,22 +2,38 @@
"format_version": 2,
"header": {
"name": "Pets 4 Jamie's STARS",
"description": "Tameable dragon pets with eggs, gravestones and personality built for Jamie's STARS!",
"description": "Tameable dragon pets with eggs, gravestones and personality \u2014 built for Jamie's STARS!",
"uuid": "7cf924ce-e246-4c8c-998c-f420edb26451",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "6806e843-c0b4-4a4d-9cb3-d5352e396fe5",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
}
],
"dependencies": [
{
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
"version": [1, 0, 0]
"version": [
1,
0,
1
]
}
]
}

View File

@@ -30,7 +30,7 @@
{ "fire_breathing": "query.property('silverlabs:firing')" },
{ "flying": "!query.is_on_ground && query.property('silverlabs:growth_stage') == 2" },
{ "walking": "query.modified_move_speed > 0.1" },
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" },
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" },
{ "sitting": "query.is_sitting" },
{ "grooming": "query.is_on_ground && query.modified_move_speed <= 0.05 && math.mod(math.floor((query.life_time + math.mod(math.abs(math.floor(query.position(0) + query.position(2))), 5) * 36.0) / 36.0), 5) == 1" },
{ "sniffing": "query.is_on_ground && query.modified_move_speed <= 0.15 && math.mod(math.floor((query.life_time + math.mod(math.abs(math.floor(query.position(0) + query.position(2))), 5) * 36.0) / 36.0), 5) == 2" },
@@ -52,14 +52,14 @@
"animations": ["sit"],
"transitions": [
{ "idle": "!query.is_sitting" },
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" }
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" }
]
},
"sleeping": {
"animations": ["sleep"],
"transitions": [
{ "idle": "!query.is_sitting" },
{ "sitting": "query.is_sitting && (query.day_light_level >= 4)" }
{ "sitting": "query.is_sitting && (query.time_of_day <= 0.5)" }
]
},
"flying": {

View File

@@ -41,7 +41,7 @@
"particle_effects": {
"beacon_beam": "minecraft:endrod",
"mood_happy": "minecraft:villager_happy",
"mood_needy": "minecraft:large_smoke"
"mood_needy": "minecraft:basic_smoke_particle"
},
"spawn_egg": {
"base_color": "#7B2FBE",

View File

@@ -4,14 +4,14 @@
"name": "Pets 4 Jamie's STARS Resources",
"description": "Textures, models and animations for Pets 4 Jamie's STARS",
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
"version": [1, 0, 0],
"version": [1, 0, 1],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "resources",
"uuid": "6adc20cd-25a5-4ca5-aa12-f5e43ee9ea22",
"version": [1, 0, 0]
"version": [1, 0, 1]
}
],
"dependencies": [

View File

@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:ore_detector_advanced",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": "ore_detector_advanced",
"minecraft:hand_equipped": true
}
}
}

View File

@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:ore_detector_basic",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": "ore_detector_basic",
"minecraft:hand_equipped": true
}
}
}

View File

@@ -0,0 +1,17 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:ore_detector_improved",
"menu_category": {
"category": "equipment",
"group": "itemGroup.name.miscellaneous"
}
},
"components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": "ore_detector_improved",
"minecraft:hand_equipped": true
}
}
}

View File

@@ -4,20 +4,36 @@
"name": "Camping Supplies",
"description": "Craftable tent and hammock for overnight camping without setting your spawn point",
"uuid": "bcf569fa-8b2c-403e-9f75-6b405132c5cd",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "f306e1d8-3c13-4554-9715-4799ce6d41d8",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
},
{
"type": "script",
"language": "javascript",
"uuid": "1e496657-0c83-4707-a1e8-29b757dcce79",
"version": [1, 0, 0],
"version": [
1,
0,
0
],
"entry": "scripts/main.js"
}
],
@@ -28,7 +44,11 @@
},
{
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
"version": [1, 0, 0]
"version": [
1,
0,
1
]
}
]
}

View File

@@ -0,0 +1,27 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": {
"identifier": "silverlabs:ore_detector_advanced_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "silverlabs:ore_detector_improved"
}
],
"ingredients": [
{ "item": "silverlabs:ore_detector_improved" },
{ "item": "minecraft:gold_ingot" },
{ "item": "minecraft:gold_ingot" },
{ "item": "minecraft:amethyst_shard" },
{ "item": "minecraft:amethyst_shard" }
],
"result": {
"item": "silverlabs:ore_detector_advanced",
"count": 1
}
}
}

View File

@@ -0,0 +1,36 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:ore_detector_basic_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "minecraft:redstone"
}
],
"pattern": [
" R ",
"CSC",
" C "
],
"key": {
"R": {
"item": "minecraft:redstone"
},
"C": {
"item": "minecraft:cobblestone"
},
"S": {
"item": "minecraft:stick"
}
},
"result": {
"item": "silverlabs:ore_detector_basic",
"count": 1
}
}
}

View File

@@ -0,0 +1,28 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": {
"identifier": "silverlabs:ore_detector_improved_recipe"
},
"tags": [
"crafting_table"
],
"unlock": [
{
"item": "silverlabs:ore_detector_basic"
}
],
"ingredients": [
{ "item": "silverlabs:ore_detector_basic" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:iron_ingot" },
{ "item": "minecraft:redstone" }
],
"result": {
"item": "silverlabs:ore_detector_improved",
"count": 1
}
}
}

View File

@@ -703,6 +703,116 @@ function dismantleHammockAt(loc, dimId, player) {
if (player) player.sendMessage("§7[Camping] Hammock taken down.");
}
// ─── Ore Detector ───────────────────────────────────────────
// Three tiered items share one scan handler. Tier → max scan range.
// Faraday: any ore within 4 blocks of a silverlabs:private_chest is hidden
// (mirrors the source DetectOre faraday-cage mechanic, but built into private chests).
const DETECTOR_RANGES = {
"silverlabs:ore_detector_basic": 8,
"silverlabs:ore_detector_improved": 16,
"silverlabs:ore_detector_advanced": 32,
};
const PRIVATE_CHEST_ID = "silverlabs:private_chest";
const FARADAY_RADIUS = 4; // 9³ cube around each ore candidate
const ORE_IDS = new Set([
"minecraft:coal_ore", "minecraft:deepslate_coal_ore",
"minecraft:iron_ore", "minecraft:deepslate_iron_ore",
"minecraft:copper_ore", "minecraft:deepslate_copper_ore",
"minecraft:gold_ore", "minecraft:deepslate_gold_ore", "minecraft:nether_gold_ore",
"minecraft:redstone_ore", "minecraft:deepslate_redstone_ore",
"minecraft:lit_redstone_ore", "minecraft:lit_deepslate_redstone_ore",
"minecraft:lapis_ore", "minecraft:deepslate_lapis_ore",
"minecraft:emerald_ore", "minecraft:deepslate_emerald_ore",
"minecraft:diamond_ore", "minecraft:deepslate_diamond_ore",
"minecraft:nether_quartz_ore",
"minecraft:ancient_debris",
]);
function prettyOreName(typeId) {
const bare = typeId.replace(/^minecraft:/, "")
.replace(/^lit_/, "")
.replace(/^deepslate_/, "Deepslate ")
.replace(/^nether_/, "Nether ");
return bare.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
}
function isPrivateChestNearby(dim, cx, cy, cz) {
for (let dx = -FARADAY_RADIUS; dx <= FARADAY_RADIUS; dx++) {
for (let dy = -FARADAY_RADIUS; dy <= FARADAY_RADIUS; dy++) {
for (let dz = -FARADAY_RADIUS; dz <= FARADAY_RADIUS; dz++) {
const b = dim.getBlock({ x: cx + dx, y: cy + dy, z: cz + dz });
if (b && b.typeId === PRIVATE_CHEST_ID) return true;
}
}
}
return false;
}
function runOreScan(player, range) {
const dim = player.dimension;
const head = player.getHeadLocation();
const dir = player.getViewDirection();
// Walk integer-block steps along the view ray. Resolution 0.5 blocks
// catches ores the cardinal-aligned steps would skip on diagonals.
const stepCount = Math.floor(range * 2);
const seen = new Set();
let foundOre = null;
let foundDist = 0;
for (let i = 1; i <= stepCount; i++) {
const t = i * 0.5;
const px = head.x + dir.x * t;
const py = head.y + dir.y * t;
const pz = head.z + dir.z * t;
const bx = Math.floor(px);
const by = Math.floor(py);
const bz = Math.floor(pz);
const key = `${bx},${by},${bz}`;
if (seen.has(key)) continue;
seen.add(key);
let block;
try { block = dim.getBlock({ x: bx, y: by, z: bz }); } catch (_) { continue; }
if (!block) continue;
if (ORE_IDS.has(block.typeId)) {
if (isPrivateChestNearby(dim, bx, by, bz)) continue; // faraday
foundOre = block.typeId;
foundDist = t;
break;
}
}
if (foundOre) {
const distRounded = Math.round(foundDist * 10) / 10;
const pitch = Math.max(0.8, Math.min(2.0, 2.0 - (foundDist / range)));
try {
player.playSound("random.orb", { pitch, volume: 0.7 });
} catch (_) {}
try {
player.onScreenDisplay.setActionBar(`§a● §f${prettyOreName(foundOre)} §7at §b${distRounded}m`);
} catch (_) {}
} else {
try {
player.playSound("note.bass", { pitch: 0.7, volume: 0.5 });
} catch (_) {}
try {
player.onScreenDisplay.setActionBar(`§7○ No ores within §f${range}m`);
} catch (_) {}
}
}
world.afterEvents.itemUse.subscribe((event) => {
const player = event.source;
const stack = event.itemStack;
if (!stack || !player) return;
const range = DETECTOR_RANGES[stack.typeId];
if (!range) return;
system.run(() => runOreScan(player, range));
});
// ─── Boot ───────────────────────────────────────────────────
system.run(() => {
loadState();

View File

@@ -4,14 +4,14 @@
"name": "Camping Supplies Resources",
"description": "Textures and lang for camping supplies (tent, hammock, canvas, cloth)",
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
"version": [1, 0, 0],
"version": [1, 0, 1],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "resources",
"uuid": "c9ee429f-9374-4083-843b-4b195e8db130",
"version": [1, 0, 0]
"version": [1, 0, 1]
}
]
}

View File

@@ -1,4 +1,7 @@
item.silverlabs:tent.name=Tent
item.silverlabs:hammock.name=Hammock
item.silverlabs:ore_detector_basic.name=Ore Detector
item.silverlabs:ore_detector_improved.name=Ore Detector (Improved)
item.silverlabs:ore_detector_advanced.name=Ore Detector (Advanced)
tile.silverlabs:tent_canvas.name=Tent Canvas
tile.silverlabs:hammock_cloth.name=Hammock Cloth

View File

@@ -7,6 +7,15 @@
},
"hammock_item": {
"textures": "textures/items/hammock"
},
"ore_detector_basic": {
"textures": "textures/items/ore_detector_basic"
},
"ore_detector_improved": {
"textures": "textures/items/ore_detector_improved"
},
"ore_detector_advanced": {
"textures": "textures/items/ore_detector_advanced"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

View File

@@ -61,6 +61,7 @@ services:
volumes:
- jamie-data:/data
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
- ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP
@@ -76,6 +77,10 @@ services:
- ./dynamite-addon/dynamite_RP:/data/resource_packs/dynamite_RP
- ./tow-boat-addon/tow_boat_BP:/data/behavior_packs/tow_boat_BP
- ./tow-boat-addon/tow_boat_RP:/data/resource_packs/tow_boat_RP
- ./trees-features-addon/trees_features_BP:/data/behavior_packs/trees_features_BP
- ./trees-features-addon/trees_features_RP:/data/resource_packs/trees_features_RP
- ./hemp-addon/hemp_BP:/data/behavior_packs/hemp_BP
- ./hemp-addon/hemp_RP:/data/resource_packs/hemp_RP
restart: unless-stopped
mem_limit: 1500m
memswap_limit: 2500m
@@ -94,6 +99,7 @@ services:
volumes:
- lyla-data:/data
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
@@ -135,6 +141,7 @@ services:
volumes:
- mya-data:/data
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
@@ -157,7 +164,13 @@ services:
- ./dynamite-addon/dynamite_RP:/data/resource_packs/dynamite_RP
- ./tow-boat-addon/tow_boat_BP:/data/behavior_packs/tow_boat_BP
- ./tow-boat-addon/tow_boat_RP:/data/resource_packs/tow_boat_RP
- ./naturalist-lite-addon/naturalist_lite_BP:/data/behavior_packs/naturalist_lite_BP
- ./naturalist-lite-addon/naturalist_lite_RP:/data/resource_packs/naturalist_lite_RP
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
- ./trees-features-addon/trees_features_BP:/data/behavior_packs/trees_features_BP
- ./trees-features-addon/trees_features_RP:/data/resource_packs/trees_features_RP
- ./hemp-addon/hemp_BP:/data/behavior_packs/hemp_BP
- ./hemp-addon/hemp_RP:/data/resource_packs/hemp_RP
restart: unless-stopped
mem_limit: 1500m
memswap_limit: 2500m

View File

@@ -4,20 +4,36 @@
"name": "Dynamite",
"description": "Throwable explosives: bangers, dynamite sticks, and bundles.",
"uuid": "fac83943-16bc-4790-aa05-631894f59a03",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "1354002c-fdd5-4f7e-b89b-f5dd2c38799c",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
}
],
"dependencies": [
{
"uuid": "a18bdde1-53f8-49aa-b06d-6f0ec6c45b46",
"version": [1, 0, 0]
"version": [
1,
0,
2
]
}
]
}

View File

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

View File

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

View File

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

View File

@@ -4,14 +4,14 @@
"name": "Dynamite Resources",
"description": "Textures and language for the Dynamite addon.",
"uuid": "a18bdde1-53f8-49aa-b06d-6f0ec6c45b46",
"version": [1, 0, 0],
"version": [1, 0, 2],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "resources",
"uuid": "587281f2-f159-4ad9-85a6-d20ff4899717",
"version": [1, 0, 0]
"version": [1, 0, 2]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

View File

@@ -0,0 +1,74 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:hemp_crop",
"menu_category": { "category": "nature" },
"states": {
"silverlabs:hemp_age": { "values": { "min": 0, "max": 5 } },
"silverlabs:hemp_top": { "values": [false, true] }
}
},
"components": {
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0.1 },
"minecraft:destructible_by_explosion": { "explosion_resistance": 0.1 },
"minecraft:flammable": { "destroy_chance_modifier": 60, "catch_chance_modifier": 60 },
"minecraft:light_dampening": 0,
"minecraft:map_color": "#5d8a3c",
"minecraft:collision_box": false,
"minecraft:selection_box": { "origin": [-6, 0, -6], "size": [12, 14, 12] },
"minecraft:geometry": "minecraft:geometry.cross",
"minecraft:material_instances": {
"*": {
"texture": "hemp_crop_0",
"render_method": "alpha_test",
"ambient_occlusion": false,
"face_dimming": false
}
},
"minecraft:loot": "loot_tables/blocks/hemp_crop.json"
},
"permutations": [
{
"condition": "query.block_state('silverlabs:hemp_age') == 1",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_1", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 2",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_2", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 3",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_3", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 4",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_4", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
},
{
"condition": "query.block_state('silverlabs:hemp_age') == 5",
"components": {
"minecraft:material_instances": {
"*": { "texture": "hemp_crop_5", "render_method": "alpha_test", "ambient_occlusion": false, "face_dimming": false }
}
}
}
]
}
}

View File

@@ -0,0 +1,50 @@
{
"format_version": "1.21.0",
"minecraft:block": {
"description": {
"identifier": "silverlabs:sun_lamp",
"menu_category": {
"category": "items"
},
"states": {
"silverlabs:powered": [
false,
true
]
}
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 0.3
},
"minecraft:destructible_by_explosion": {
"explosion_resistance": 0.5
},
"minecraft:map_color": "#ffd24d",
"minecraft:light_emission": 0,
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "sun_lamp_off",
"render_method": "opaque"
}
}
},
"permutations": [
{
"condition": "query.block_state('silverlabs:powered') == true",
"components": {
"minecraft:light_emission": 15,
"minecraft:light_dampening": 0,
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "sun_lamp",
"render_method": "opaque"
}
}
}
}
]
}
}

View File

@@ -0,0 +1,25 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_brownie",
"menu_category": {
"category": "nature"
}
},
"components": {
"minecraft:icon": "hemp_brownie",
"minecraft:max_stack_size": 16,
"minecraft:use_animation": "eat",
"minecraft:use_modifiers": {
"use_duration": 1.6,
"movement_modifier": 0.35
},
"minecraft:food": {
"nutrition": 4,
"saturation_modifier": 0.6,
"can_always_eat": false
}
}
}
}

View File

@@ -0,0 +1,15 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_bud",
"menu_category": {
"category": "nature"
}
},
"components": {
"minecraft:icon": "hemp_bud",
"minecraft:max_stack_size": 64
}
}
}

View File

@@ -0,0 +1,29 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_seeds",
"menu_category": {
"category": "nature",
"group": "itemGroup.name.seed"
}
},
"components": {
"minecraft:icon": "hemp_seeds",
"minecraft:max_stack_size": 64,
"minecraft:block_placer": {
"block": "silverlabs:hemp_crop",
"use_on": [
"minecraft:dirt",
"minecraft:grass_block",
"minecraft:farmland",
"minecraft:podzol",
"minecraft:coarse_dirt",
"minecraft:rooted_dirt",
"minecraft:moss_block",
"minecraft:flower_pot"
]
}
}
}
}

View File

@@ -0,0 +1,26 @@
{
"format_version": "1.21.0",
"minecraft:item": {
"description": {
"identifier": "silverlabs:hemp_tincture",
"menu_category": {
"category": "nature"
}
},
"components": {
"minecraft:icon": "hemp_tincture",
"minecraft:max_stack_size": 16,
"minecraft:use_animation": "drink",
"minecraft:use_modifiers": {
"use_duration": 1.5,
"movement_modifier": 0.35
},
"minecraft:food": {
"nutrition": 1,
"saturation_modifier": 0.1,
"can_always_eat": true,
"using_converts_to": "minecraft:glass_bottle"
}
}
}
}

View File

@@ -0,0 +1,17 @@
{
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "item",
"name": "silverlabs:hemp_seeds",
"weight": 1,
"functions": [
{ "function": "set_count", "count": { "min": 0, "max": 1 } }
]
}
]
}
]
}

View File

@@ -0,0 +1,54 @@
{
"format_version": 2,
"header": {
"name": "Hemp Plant",
"description": "Plantable hemp crop with sheers harvesting, cauldron tincture, brownie food, sun-lamp block",
"uuid": "910fafaf-bcb0-4f1a-8a05-bd235a537c3b",
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "a10b077f-703b-46b6-b45e-71ee2044c07c",
"version": [
1,
0,
0
]
},
{
"type": "script",
"language": "javascript",
"uuid": "6e6ef947-60fc-49d2-97c1-b3cab6661ece",
"version": [
1,
0,
0
],
"entry": "scripts/main.js"
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.17.0"
},
{
"uuid": "8e5c46ac-3b16-4f51-89f7-673bd06600f4",
"version": [
1,
0,
5
]
}
]
}

View File

@@ -0,0 +1,24 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shapeless": {
"description": {
"identifier": "silverlabs:hemp_brownie_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{ "item": "silverlabs:hemp_bud" },
{ "item": "minecraft:cocoa_beans" }
],
"ingredients": [
{ "item": "silverlabs:hemp_bud" },
{ "item": "silverlabs:hemp_bud" },
{ "item": "minecraft:cocoa_beans" },
{ "item": "minecraft:wheat" },
{ "item": "minecraft:sugar" }
],
"result": {
"item": "silverlabs:hemp_brownie",
"count": 2
}
}
}

View File

@@ -0,0 +1,26 @@
{
"format_version": "1.21.0",
"minecraft:recipe_shaped": {
"description": {
"identifier": "silverlabs:sun_lamp_recipe"
},
"tags": ["crafting_table"],
"unlock": [
{ "item": "minecraft:glowstone" }
],
"pattern": [
"WIW",
"IGI",
"WIW"
],
"key": {
"W": { "item": "minecraft:yellow_wool" },
"I": { "item": "minecraft:iron_nugget" },
"G": { "item": "minecraft:glowstone" }
},
"result": {
"item": "silverlabs:sun_lamp",
"count": 1
}
}
}

View File

@@ -0,0 +1,686 @@
import { world, system, ItemStack, BlockPermutation } from "@minecraft/server";
const CROP = "silverlabs:hemp_crop";
const SUN_LAMP = "silverlabs:sun_lamp";
const SEEDS = "silverlabs:hemp_seeds";
const BUD = "silverlabs:hemp_bud";
const TINCTURE = "silverlabs:hemp_tincture";
const BROWNIE = "silverlabs:hemp_brownie";
const AGE = "silverlabs:hemp_age";
const TOP = "silverlabs:hemp_top";
const GROWTH_INTERVAL_TICKS = 100; // 5s between growth ticks
const RABBIT_INTERVAL_TICKS = 100; // 5s between rabbit checks
const SCAN_RADIUS = 5; // ±5 horizontal — dense scan, every block looked at
const SCAN_VERT = 2; // ±2 vertical
const SUN_LAMP_RANGE = 4; // blocks searched for a lamp around indoor crop
const GROWTH_CHANCE_OUTDOOR = 0.017; // per 5s tick — ~20 min for 0→4 (prime)
const GROWTH_CHANCE_INDOOR = 0.011; // per 5s tick — ~30 min for 0→4 (sun lamp required)
const OVERRIPE_CHANCE_MULT = 0.33; // age 4→5 transition is 3x slower so prime lingers
function rand(n) { return Math.floor(Math.random() * n); }
function chance(p) { return Math.random() < p; }
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
function getInv(player) {
return player.getComponent("minecraft:inventory")?.container ?? null;
}
function consumeOneOfType(player, typeId) {
const inv = getInv(player);
if (!inv) return false;
const sel = player.selectedSlotIndex;
const cur = inv.getItem(sel);
if (cur && cur.typeId === typeId) {
if (cur.amount > 1) { cur.amount -= 1; inv.setItem(sel, cur); }
else inv.setItem(sel, undefined);
return true;
}
for (let i = 0; i < inv.size; i++) {
const it = inv.getItem(i);
if (it && it.typeId === typeId) {
if (it.amount > 1) { it.amount -= 1; inv.setItem(i, it); }
else inv.setItem(i, undefined);
return true;
}
}
return false;
}
function giveItem(player, typeId, count = 1) {
const inv = getInv(player);
if (!inv) return false;
try {
inv.addItem(new ItemStack(typeId, count));
return true;
} catch (_) {
return false;
}
}
// Block ids that don't block sky access — the plant grows around these like
// a vanilla tree pushes through leaves. Anything else above stops growth.
function isSkyTransparent(typeId) {
if (!typeId) return true;
if (typeId === CROP) return true; // ignore our own top half
if (typeId === "minecraft:air") return true;
// Glass / panes / bars
if (typeId.includes("glass")) return true;
if (typeId === "minecraft:iron_bars") return true;
// Leaves and saplings (canopy-style obstructions)
if (typeId.includes("leaves")) return true;
if (typeId.includes("sapling")) return true;
// Other plants / vines / hanging stuff
if (typeId === "minecraft:vine" || typeId === "minecraft:weeping_vines"
|| typeId === "minecraft:twisting_vines") return true;
return false;
}
function isAirAbove(dim, loc) {
// Walk up from y+1; sky-transparent blocks don't count as obstructions.
for (let dy = 1; dy < 64; dy++) {
let b;
try { b = dim.getBlock({ x: loc.x, y: loc.y + dy, z: loc.z }); } catch (_) { return true; }
if (!b) return true;
if (b.isAir || b.isLiquid) continue;
if (isSkyTransparent(b.typeId)) continue;
return false;
}
return true;
}
function findSunLampNear(dim, loc) {
const r = SUN_LAMP_RANGE;
for (let dy = -1; dy <= r; dy++) {
for (let dx = -r; dx <= r; dx++) {
for (let dz = -r; dz <= r; dz++) {
let b;
try { b = dim.getBlock({ x: loc.x + dx, y: loc.y + dy, z: loc.z + dz }); } catch (_) { continue; }
if (b && b.typeId === SUN_LAMP) {
try {
if (b.permutation.getState("silverlabs:powered") === true) return true;
} catch (_) {}
}
}
}
}
return false;
}
function setAge(block, newAge, outdoor) {
try {
const perm = BlockPermutation.resolve(CROP, {
[AGE]: clamp(newAge, 0, 5),
[TOP]: false,
});
block.setPermutation(perm);
if (outdoor && newAge >= 3) {
// Place top half above for the visual "tall outdoor crop"
const above = block.dimension.getBlock({ x: block.location.x, y: block.location.y + 1, z: block.location.z });
if (above && above.isAir) {
try {
const topPerm = BlockPermutation.resolve(CROP, { [AGE]: clamp(newAge, 0, 5), [TOP]: true });
above.setPermutation(topPerm);
} catch (_) {}
}
}
} catch (_) {}
}
function clearTopAbove(block) {
const above = block.dimension.getBlock({ x: block.location.x, y: block.location.y + 1, z: block.location.z });
if (above && above.typeId === CROP) {
try { above.setType("minecraft:air"); } catch (_) {}
}
}
// --- Growth: dense scan of an 11×11×5 box around each player ---
// Every hemp_crop base in range gets a roll on every tick — no random sampling.
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
const dim = player.dimension;
const px = Math.floor(player.location.x);
const py = Math.floor(player.location.y);
const pz = Math.floor(player.location.z);
for (let dy = -SCAN_VERT; dy <= SCAN_VERT; dy++) {
for (let dx = -SCAN_RADIUS; dx <= SCAN_RADIUS; dx++) {
for (let dz = -SCAN_RADIUS; dz <= SCAN_RADIUS; dz++) {
let b;
try { b = dim.getBlock({ x: px + dx, y: py + dy, z: pz + dz }); } catch (_) { continue; }
if (!b || b.typeId !== CROP) continue;
if (b.permutation.getState(TOP) === true) continue; // only base ticks
const age = b.permutation.getState(AGE) ?? 0;
if (age >= 5) continue; // overripe stops
const outdoor = isAirAbove(dim, b.location);
let baseChance = 0;
if (outdoor) baseChance = GROWTH_CHANCE_OUTDOOR;
else if (findSunLampNear(dim, b.location)) baseChance = GROWTH_CHANCE_INDOOR;
else continue;
// Prime (age 4) lingers — slow the transition into overripe
const c = age === 4 ? baseChance * OVERRIPE_CHANCE_MULT : baseChance;
if (chance(c)) setAge(b, age + 1, outdoor);
}
}
}
}
}, GROWTH_INTERVAL_TICKS);
// --- Redstone power: scan known sources adjacent to a sun_lamp ---
const REDSTONE_FACES = [
[1, 0, 0], [-1, 0, 0],
[0, 1, 0], [0, -1, 0],
[0, 0, 1], [0, 0, -1],
];
function isPowerSource(b) {
if (!b) return false;
const t = b.typeId;
if (t === "minecraft:redstone_block") return true;
if (t === "minecraft:lit_redstone_torch" || t === "minecraft:redstone_torch") {
// torch lit state
try { return b.permutation.getState("toggle_bit") !== false; } catch (_) {}
return t === "minecraft:lit_redstone_torch";
}
if (t === "minecraft:powered_repeater") return true;
if (t === "minecraft:powered_comparator") return true;
if (t === "minecraft:lever") {
try { return b.permutation.getState("open_bit") === true; } catch (_) { return false; }
}
if (t.endsWith("_button") || t.includes(":wooden_button") || t.includes(":stone_button")) {
try { return b.permutation.getState("button_pressed_bit") === true; } catch (_) { return false; }
}
if (t === "minecraft:redstone_wire") {
try {
const p = b.permutation.getState("redstone_signal") ?? 0;
return p > 0;
} catch (_) { return false; }
}
if (t === "minecraft:daylight_detector_inverted") return true;
return false;
}
function lampShouldBePowered(lamp) {
// Try the script API redstone power first (newer servers expose it)
try {
const p = lamp.getRedstonePower();
if (typeof p === "number" && p > 0) return true;
} catch (_) {}
const dim = lamp.dimension;
for (const [dx, dy, dz] of REDSTONE_FACES) {
let n;
try { n = dim.getBlock({ x: lamp.location.x + dx, y: lamp.location.y + dy, z: lamp.location.z + dz }); } catch (_) { continue; }
if (isPowerSource(n)) return true;
}
return false;
}
const LAMP_SCAN_INTERVAL_TICKS = 20; // 1s
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
const dim = player.dimension;
const px = Math.floor(player.location.x);
const py = Math.floor(player.location.y);
const pz = Math.floor(player.location.z);
for (let dy = -SCAN_VERT; dy <= SCAN_VERT; dy++) {
for (let dx = -SCAN_RADIUS; dx <= SCAN_RADIUS; dx++) {
for (let dz = -SCAN_RADIUS; dz <= SCAN_RADIUS; dz++) {
let b;
try { b = dim.getBlock({ x: px + dx, y: py + dy, z: pz + dz }); } catch (_) { continue; }
if (!b || b.typeId !== SUN_LAMP) continue;
let cur = false;
try { cur = b.permutation.getState("silverlabs:powered") === true; } catch (_) {}
const want = lampShouldBePowered(b);
if (cur !== want) {
try {
const perm = BlockPermutation.resolve(SUN_LAMP, { "silverlabs:powered": want });
b.setPermutation(perm);
} catch (_) {}
}
}
}
}
}
}, LAMP_SCAN_INTERVAL_TICKS);
// --- Rabbit threat: damage outdoor hemp ---
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
const dim = player.dimension;
let rabbits;
try {
rabbits = dim.getEntities({ type: "minecraft:rabbit", location: player.location, maxDistance: 48 });
} catch (_) { continue; }
for (const rabbit of rabbits) {
// For each rabbit, look at a small box around its feet for hemp_crop.
const rx = Math.floor(rabbit.location.x);
const ry = Math.floor(rabbit.location.y);
const rz = Math.floor(rabbit.location.z);
for (let dx = -1; dx <= 1; dx++) {
for (let dz = -1; dz <= 1; dz++) {
let b;
try { b = dim.getBlock({ x: rx + dx, y: ry, z: rz + dz }); } catch (_) { continue; }
if (!b || b.typeId !== CROP) continue;
const isTop = b.permutation.getState(TOP) === true;
if (isTop) continue;
if (chance(0.35)) {
const age = b.permutation.getState(AGE) ?? 0;
if (age <= 0) {
clearTopAbove(b);
try { b.setType("minecraft:air"); } catch (_) {}
} else {
clearTopAbove(b);
setAge(b, age - 1, isAirAbove(dim, b.location));
}
try { dim.runCommand(`particle minecraft:crit_particle ${rx + dx + 0.5} ${ry + 0.5} ${rz + dz + 0.5}`); } catch (_) {}
}
}
}
}
}
}, RABBIT_INTERVAL_TICKS);
// --- Pillager raid threat: illagers carrying a banner steal hemp during raids ---
// Gate on either an active raid (detected via a nearby ominous_banner) or a
// pillager_captain so isolated tower pillagers don't constantly cull farms
// in render distance. When triggered, drops the crop's age by 1 (or removes
// it if age 0) and spawns 1 hemp_bud at the illager's feet so the player
// can recover the loot by killing the raider.
const RAID_INTERVAL_TICKS = 100;
const ILLAGER_TYPES = new Set([
"minecraft:pillager",
"minecraft:vindicator",
"minecraft:evocation_illager",
]);
const ILLAGER_HEMP_STEAL_CHANCE = 0.30;
function illagerIsRaiding(dim, ill) {
// Cheapest: check if the illager is wearing/carrying an ominous_banner
// (raid captain marker). Also accept any nearby ominous_banner block within
// 8 blocks as evidence of an ongoing raid the illager is participating in.
const equip = (() => { try { return ill.getComponent("minecraft:equippable"); } catch (_) { return null; } })();
if (equip) {
try {
const head = equip.getEquipmentSlot("Head")?.getItem();
if (head && head.typeId === "minecraft:ominous_banner") return true;
} catch (_) {}
}
const ix = Math.floor(ill.location.x);
const iy = Math.floor(ill.location.y);
const iz = Math.floor(ill.location.z);
for (let dx = -8; dx <= 8; dx += 4) {
for (let dz = -8; dz <= 8; dz += 4) {
let b;
try { b = dim.getBlock({ x: ix + dx, y: iy, z: iz + dz }); } catch (_) { continue; }
if (b && (b.typeId === "minecraft:standing_banner" || b.typeId === "minecraft:wall_banner")) {
// Banners can be ominous via their NBT; we can't read it, so any banner
// within 8 blocks of an illager is treated as raid evidence. Still rare.
return true;
}
}
}
return false;
}
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
const dim = player.dimension;
let illagers;
try {
illagers = dim.getEntities({ families: [], location: player.location, maxDistance: 48 });
} catch (_) { continue; }
for (const ill of illagers) {
if (!ILLAGER_TYPES.has(ill.typeId)) continue;
if (!illagerIsRaiding(dim, ill)) continue;
const ix = Math.floor(ill.location.x);
const iy = Math.floor(ill.location.y);
const iz = Math.floor(ill.location.z);
for (let dx = -1; dx <= 1; dx++) {
for (let dz = -1; dz <= 1; dz++) {
let b;
try { b = dim.getBlock({ x: ix + dx, y: iy, z: iz + dz }); } catch (_) { continue; }
if (!b || b.typeId !== CROP) continue;
if (b.permutation.getState(TOP) === true) continue;
if (!chance(ILLAGER_HEMP_STEAL_CHANCE)) continue;
const age = b.permutation.getState(AGE) ?? 0;
const outdoor = isAirAbove(dim, b.location);
if (age <= 0) {
clearTopAbove(b);
try { b.setType("minecraft:air"); } catch (_) {}
} else {
clearTopAbove(b);
setAge(b, age - 1, outdoor);
}
// Drop a hemp_bud at the illager's feet — recoverable when killed.
spawnDrop(dim, ill.location, BUD, 1);
try { dim.runCommand(`particle minecraft:villager_angry ${ix + 0.5} ${iy + 1.0} ${iz + 0.5}`); } catch (_) {}
}
}
}
}
}, RAID_INTERVAL_TICKS);
// --- Wandering trader buyback: hand off hemp products for emeralds ---
// Right-click a wandering_trader while holding hemp_bud / hemp_tincture /
// hemp_seeds. Avoids overriding vanilla trade tables (which would pin us to
// one Bedrock version) by acting as an off-menu interaction. Trader animates
// a "happy villager" particle and the player's inventory swaps the items
// for emeralds at the rates below.
const TRADER_BUYBACK = {
[BUD]: { perTrade: 2, emeralds: 1 },
[TINCTURE]: { perTrade: 1, emeralds: 3 },
[SEEDS]: { perTrade: 8, emeralds: 1 },
};
world.beforeEvents.playerInteractWithEntity.subscribe((event) => {
const target = event.target;
if (!target || target.typeId !== "minecraft:wandering_trader") return;
const stack = event.itemStack;
if (!stack) return;
const deal = TRADER_BUYBACK[stack.typeId];
if (!deal) return;
event.cancel = true; // suppress the vanilla trade window for this interaction
const player = event.player;
system.run(() => {
// Count what the player has of this item across the inventory
const inv = getInv(player);
if (!inv) return;
let have = 0;
for (let i = 0; i < inv.size; i++) {
const it = inv.getItem(i);
if (it && it.typeId === stack.typeId) have += it.amount;
}
if (have < deal.perTrade) {
player.sendMessage(`§7[Trader] Brings me at least §f${deal.perTrade}§7 of those and I'll pay you in emeralds.`);
return;
}
const trades = Math.floor(have / deal.perTrade);
let consumed = 0;
for (let n = 0; n < trades * deal.perTrade; n++) {
if (!consumeOneOfType(player, stack.typeId)) break;
consumed++;
}
const actualTrades = Math.floor(consumed / deal.perTrade);
if (actualTrades <= 0) return;
giveItem(player, "minecraft:emerald", actualTrades * deal.emeralds);
const loc = target.location;
try { target.dimension.runCommand(`particle minecraft:villager_happy ${loc.x} ${loc.y + 1.5} ${loc.z}`); } catch (_) {}
try { target.dimension.runCommand(`playsound mob.villager.yes @a ${loc.x} ${loc.y} ${loc.z} 0.8 1.1`); } catch (_) {}
player.sendMessage(`§a[Trader] §fTraded §e${actualTrades * deal.perTrade}§f for §a${actualTrades * deal.emeralds} emerald${actualTrades * deal.emeralds === 1 ? "" : "s"}§f.`);
});
});
// --- Outdoor detection on placement ---
world.afterEvents.playerPlaceBlock.subscribe((event) => {
const block = event.block;
if (!block || block.typeId !== CROP) return;
// newly-placed crop is age 0 by default — we don't need to set anything,
// outdoor status is computed live during ticks.
});
// --- Cleanup: breaking either half of a tall plant removes the other ---
// Also: rare hemp_seeds drop from breaking vanilla grass / tall_grass /
// short_grass — gives players a way to bootstrap into hemp without
// already having seeds. Mirrors how vanilla wheat seeds work.
const GRASS_BLOCK_IDS = new Set([
"minecraft:short_grass",
"minecraft:tall_grass",
"minecraft:fern",
"minecraft:large_fern",
// Pre-1.21 alias kept for safety on mixed-version worlds
"minecraft:grass",
]);
const GRASS_HEMP_SEED_CHANCE = 0.04; // ~1 in 25 grass tufts
world.afterEvents.playerBreakBlock.subscribe((event) => {
const brokenType = event.brokenBlockPermutation?.type?.id;
// Hemp_crop tall-plant cleanup
if (brokenType === CROP) {
const block = event.block;
const dim = block.dimension;
const wasTop = event.brokenBlockPermutation.getState(TOP) === true;
const dy = wasTop ? -1 : 1;
let neighbor;
try { neighbor = dim.getBlock({ x: block.location.x, y: block.location.y + dy, z: block.location.z }); } catch (_) { return; }
if (!neighbor || neighbor.typeId !== CROP) return;
const neighborIsTop = neighbor.permutation.getState(TOP) === true;
if (wasTop === neighborIsTop) return;
try { neighbor.setType("minecraft:air"); } catch (_) {}
return;
}
// Hemp seed bootstrap from grass blocks
if (brokenType && GRASS_BLOCK_IDS.has(brokenType)) {
if (!chance(GRASS_HEMP_SEED_CHANCE)) return;
const block = event.block;
const loc = block.location;
spawnDrop(block.dimension, loc, SEEDS, 1);
}
});
// Per-player debounce so a single click that fires the event twice
// (a known Bedrock quirk for some interact paths) only does work once.
const recentInteract = new Map(); // key: playerId|x|y|z -> system.currentTick
function consumeInteractToken(player, block) {
const key = `${player.id}|${block.location.x}|${block.location.y}|${block.location.z}`;
const last = recentInteract.get(key) ?? -999;
const now = system.currentTick;
if (now - last < 4) return false; // <200ms — treat as duplicate
recentInteract.set(key, now);
// Lazy cleanup: prune any entries older than ~4s on each successful claim
if (recentInteract.size > 64) {
for (const [k, t] of recentInteract) {
if (now - t > 80) recentInteract.delete(k);
}
}
return true;
}
// --- Bonemeal on hemp_crop bumps growth ---
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
const block = event.block;
const stack = event.itemStack;
const player = event.player;
if (!block || block.typeId !== CROP) return;
// Sheers harvest path
if (stack && stack.typeId === "minecraft:shears") {
event.cancel = true;
if (!consumeInteractToken(player, block)) return;
system.run(() => harvestWithShears(player, block, stack));
return;
}
// Bone meal path
if (stack && stack.typeId === "minecraft:bone_meal") {
event.cancel = true;
if (!consumeInteractToken(player, block)) return;
system.run(() => {
// If the player clicked the top half, redirect to the base
let target = block;
if (block.permutation.getState(TOP) === true) {
const below = block.dimension.getBlock({ x: block.location.x, y: block.location.y - 1, z: block.location.z });
if (below && below.typeId === CROP) target = below;
else return;
}
const age = target.permutation.getState(AGE) ?? 0;
// Cap bone meal at age 4 (prime) — overripe (5) only via neglect
if (age >= 4) {
player.sendMessage("§e[Hemp] Already mature — harvest with shears.");
return;
}
// Always consume the bone meal, but only 50% chance to advance
consumeOneOfType(player, "minecraft:bone_meal");
const loc = target.location;
try { target.dimension.runCommand(`particle minecraft:crop_growth_emitter ${loc.x + 0.5} ${loc.y + 0.5} ${loc.z + 0.5}`); } catch (_) {}
if (!chance(0.5)) return;
const outdoor = isAirAbove(target.dimension, loc);
setAge(target, age + 1, outdoor);
try { target.dimension.runCommand(`playsound random.fizz @a ${loc.x + 0.5} ${loc.y + 0.5} ${loc.z + 0.5} 0.6 1.6`); } catch (_) {}
});
return;
}
});
function harvestWithShears(player, block, shearsStack) {
if (!block || block.typeId !== CROP) return; // already harvested / replaced
// Read state on the BASE block (not top)
let base = block;
if (block.permutation.getState(TOP) === true) {
const below = block.dimension.getBlock({ x: block.location.x, y: block.location.y - 1, z: block.location.z });
if (below && below.typeId === CROP) base = below;
}
const age = base.permutation.getState(AGE) ?? 0;
const dim = base.dimension;
const loc = base.location;
const outdoor = isAirAbove(dim, loc);
// Too early — leave the plant alone, don't damage shears
if (age <= 1) {
player.sendMessage("§7[Hemp] Too early — leave it to grow.");
return;
}
const yields = computeYield(age, outdoor);
if (yields.msg) player.sendMessage(yields.msg);
if (yields.bud > 0) spawnDrop(dim, loc, BUD, yields.bud);
if (yields.seeds > 0) spawnDrop(dim, loc, SEEDS, yields.seeds);
// Fully remove the plant — player replants from seeds
clearTopAbove(base);
try { base.setType("minecraft:air"); } catch (_) {}
damageShears(player, shearsStack);
try { dim.runCommand(`playsound mob.sheep.shear @a ${loc.x + 0.5} ${loc.y + 0.5} ${loc.z + 0.5}`); } catch (_) {}
}
function computeYield(age, outdoor) {
// Outdoor gets a bud bonus; indoor (sun-lamp grown) is baseline.
const out = outdoor ? 1 : 0;
switch (age) {
case 2: return { bud: 1, seeds: 1 + out };
case 3: return { bud: 2 + out, seeds: 1 + out };
case 4: return { bud: 3 + out * 2, seeds: 1 + out, msg: "§a[Hemp] Prime harvest." }; // peak
case 5: return { bud: rand(3), seeds: 3 + rand(3), msg: "§7[Hemp] Overripe — mostly seeds now." };
default: return { bud: 0, seeds: 0 };
}
}
function spawnDrop(dim, loc, typeId, count) {
try {
const it = new ItemStack(typeId, count);
dim.spawnItem(it, { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
} catch (_) {}
}
function damageShears(player, shearsStack) {
try {
const equippable = player.getComponent("minecraft:equippable");
if (!equippable) return;
const slot = equippable.getEquipmentSlot("Mainhand");
if (!slot) return;
const item = slot.getItem();
if (!item || item.typeId !== "minecraft:shears") return;
const dur = item.getComponent("minecraft:durability");
if (!dur) return;
const damage = dur.damage + 1;
if (damage >= dur.maxDurability) {
slot.setItem(undefined);
try { player.dimension.runCommand(`playsound random.break @a ${player.location.x} ${player.location.y} ${player.location.z}`); } catch (_) {}
} else {
dur.damage = damage;
slot.setItem(item);
}
} catch (_) {}
}
// --- Cauldron tincture brewing: hemp_bud + water cauldron + glass bottle => tincture ---
world.afterEvents.itemUse.subscribe((event) => {
const player = event.source;
const stack = event.itemStack;
if (!stack || stack.typeId !== BUD) return;
system.run(() => {
const target = player.getBlockFromViewDirection({ maxDistance: 6 });
const block = target?.block;
if (!block) return;
const isCauldron = block.typeId === "minecraft:cauldron" || block.typeId === "minecraft:water_cauldron";
if (!isCauldron) return;
// Need at least 3 buds total in inventory and 1 glass bottle
const inv = getInv(player);
if (!inv) return;
let budCount = 0, bottleCount = 0;
for (let i = 0; i < inv.size; i++) {
const it = inv.getItem(i);
if (!it) continue;
if (it.typeId === BUD) budCount += it.amount;
else if (it.typeId === "minecraft:glass_bottle") bottleCount += it.amount;
}
if (budCount < 3) {
player.sendMessage("§c[Hemp] Need 3 hemp buds to brew tincture.");
return;
}
if (bottleCount < 1) {
player.sendMessage("§c[Hemp] Need an empty glass bottle.");
return;
}
// Check water level (water cauldron has fill_level state 1-3; vanilla cauldron = empty)
let level = 0;
try { level = block.permutation.getState("fill_level") ?? 0; } catch (_) {}
if (block.typeId === "minecraft:cauldron") {
player.sendMessage("§c[Hemp] Cauldron must contain water.");
return;
}
if (level <= 0) {
player.sendMessage("§c[Hemp] Cauldron is empty.");
return;
}
// Consume 3 buds, 1 bottle
for (let i = 0; i < 3; i++) consumeOneOfType(player, BUD);
consumeOneOfType(player, "minecraft:glass_bottle");
// Decrement water level
try {
const newLevel = level - 1;
if (newLevel <= 0) {
block.setType("minecraft:cauldron");
} else {
const perm = BlockPermutation.resolve("minecraft:water_cauldron", { fill_level: newLevel });
block.setPermutation(perm);
}
} catch (_) {}
// Give tincture
giveItem(player, TINCTURE, 1);
const loc = block.location;
try { block.dimension.runCommand(`particle minecraft:water_splash_particle_manual ${loc.x + 0.5} ${loc.y + 1.0} ${loc.z + 0.5}`); } catch (_) {}
try { block.dimension.runCommand(`playsound bucket.fill_water @a ${loc.x + 0.5} ${loc.y + 0.5} ${loc.z + 0.5}`); } catch (_) {}
player.sendMessage("§a[Hemp] Hemp tincture brewed.");
});
});
// --- Consumption effects ---
world.afterEvents.itemCompleteUse.subscribe((event) => {
const player = event.source;
const stack = event.itemStack;
if (!stack) return;
if (stack.typeId === TINCTURE) {
try {
player.addEffect("regeneration", 100, { amplifier: 1, showParticles: true });
player.addEffect("slowness", 200, { amplifier: 0, showParticles: true });
} catch (_) {}
player.sendMessage("§a[Hemp] You feel a warm, calming wave.");
} else if (stack.typeId === BROWNIE) {
try {
player.addEffect("regeneration", 200, { amplifier: 0, showParticles: true });
player.addEffect("slowness", 400, { amplifier: 0, showParticles: true });
} catch (_) {}
player.sendMessage("§a[Hemp] Mmm. You feel relaxed.");
}
});
system.run(() => {
world.sendMessage("§a[Hemp] §7Hemp pack loaded.");
});

View File

@@ -0,0 +1,5 @@
{
"format_version": [1, 1, 0],
"silverlabs:hemp_crop": { "sound": "grass" },
"silverlabs:sun_lamp": { "sound": "glass" }
}

View File

@@ -0,0 +1,29 @@
{
"format_version": 2,
"header": {
"name": "Hemp Plant Resources",
"description": "Textures, models, lang for the Hemp Plant addon",
"uuid": "8e5c46ac-3b16-4f51-89f7-673bd06600f4",
"version": [
1,
0,
5
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "resources",
"uuid": "5fcc737f-d224-409d-85e9-99274afd72a9",
"version": [
1,
0,
5
]
}
]
}

View File

@@ -0,0 +1,6 @@
item.silverlabs:hemp_seeds.name=Hemp Seeds
item.silverlabs:hemp_bud.name=Hemp Bud
item.silverlabs:hemp_tincture.name=Hemp Tincture
item.silverlabs:hemp_brownie.name=Hemp Brownie
tile.silverlabs:hemp_crop.name=Hemp Plant
tile.silverlabs:sun_lamp.name=Sun Lamp

View File

@@ -0,0 +1 @@
["en_US"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

View File

@@ -0,0 +1,10 @@
{
"resource_pack_name": "hemp_RP",
"texture_name": "atlas.items",
"texture_data": {
"hemp_seeds": { "textures": "textures/items/hemp_seeds" },
"hemp_bud": { "textures": "textures/items/hemp_bud" },
"hemp_tincture": { "textures": "textures/items/hemp_tincture" },
"hemp_brownie": { "textures": "textures/items/hemp_brownie" }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

View File

@@ -0,0 +1,16 @@
{
"resource_pack_name": "hemp_RP",
"texture_name": "atlas.terrain",
"padding": 8,
"num_mip_levels": 4,
"texture_data": {
"hemp_crop_0": { "textures": "textures/blocks/hemp_crop_0" },
"hemp_crop_1": { "textures": "textures/blocks/hemp_crop_1" },
"hemp_crop_2": { "textures": "textures/blocks/hemp_crop_2" },
"hemp_crop_3": { "textures": "textures/blocks/hemp_crop_3" },
"hemp_crop_4": { "textures": "textures/blocks/hemp_crop_4" },
"hemp_crop_5": { "textures": "textures/blocks/hemp_crop_5" },
"sun_lamp": { "textures": "textures/blocks/sun_lamp" },
"sun_lamp_off": { "textures": "textures/blocks/sun_lamp_off" }
}
}

View File

@@ -4,20 +4,36 @@
"name": "Home Sweet Home",
"description": "Defines a 32-block home zone for tamed cats; quieter cat meows",
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b80",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
"version": [
1,
0,
0
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "data",
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b81",
"version": [1, 0, 0]
"version": [
1,
0,
0
]
},
{
"type": "script",
"language": "javascript",
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b82",
"version": [1, 0, 0],
"version": [
1,
0,
0
],
"entry": "scripts/main.js"
}
],
@@ -28,7 +44,11 @@
},
{
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b83",
"version": [1, 0, 1]
"version": [
1,
0,
0
]
}
]
}

View File

@@ -17,6 +17,33 @@ const ARRIVAL_RADIUS = 3;
const HUD_TICK_INTERVAL = 5;
const ARROWS = ["\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199", "\u2190", "\u2196"];
// Navigation HUD lives in the title/subtitle slot so it doesn't fight the
// vanilla mount UI for the action bar. The directional content goes in the
// TITLE position (large, central) with the waypoint label in the subtitle as
// a secondary line. Empty-string title gets suppressed by some Bedrock client
// versions, so we always pass a non-empty title — that's what causes the
// arrival message to render but a setActionBar-replacement subtitle not to.
function showNav(player, mainLine, subLine) {
try {
player.onScreenDisplay.setTitle(mainLine, {
subtitle: subLine ?? "",
fadeInDuration: 0,
stayDuration: HUD_TICK_INTERVAL * 4,
fadeOutDuration: 0,
});
} catch (_) {}
}
function clearNav(player) {
try {
player.onScreenDisplay.setTitle(" ", {
subtitle: "",
fadeInDuration: 0,
stayDuration: 1,
fadeOutDuration: 0,
});
} catch (_) {}
}
const recentTransfers = new Map();
const spawnTimes = new Map();
@@ -24,8 +51,22 @@ const spawnTimes = new Map();
// Persisted: markers per player { [ownerId]: [ {x,y,z,dim,label,key}, ... ] }
let markers = {};
// Session-only: which marker each player is actively guiding toward
const active = new Map();
// Per-player active waypoint index, persisted via player dynamic property so
// it survives container restarts and reconnects.
const ACTIVE_PROP = "hub_active_waypoint_v1";
function getActiveIndex(player) {
try {
const v = player.getDynamicProperty(ACTIVE_PROP);
return typeof v === "number" ? v : null;
} catch (_) { return null; }
}
function setActiveIndex(player, idx) {
try {
if (idx == null) player.setDynamicProperty(ACTIVE_PROP, undefined);
else player.setDynamicProperty(ACTIVE_PROP, idx);
} catch (_) {}
}
function clearActiveIndex(player) { setActiveIndex(player, null); }
function keyOf(loc, dimensionId) {
return `${Math.floor(loc.x)},${Math.floor(loc.y)},${Math.floor(loc.z)},${dimensionId}`;
@@ -67,10 +108,16 @@ function findMarkerByKey(key) {
function removeMarker(ownerId, index) {
const list = getMarkers(ownerId);
list.splice(index, 1);
const activeIdx = active.get(ownerId);
// Active index is stored on the owner's player dynamic property; update only
// if they're online (offline owner will revalidate their own index next tick).
for (const p of world.getAllPlayers()) {
if (p.id !== ownerId) continue;
const activeIdx = getActiveIndex(p);
if (activeIdx != null) {
if (activeIdx === index) active.delete(ownerId);
else if (activeIdx > index) active.set(ownerId, activeIdx - 1);
if (activeIdx === index) clearActiveIndex(p);
else if (activeIdx > index) setActiveIndex(p, activeIdx - 1);
}
break;
}
saveWaypoints();
}
@@ -149,11 +196,8 @@ world.afterEvents.playerSpawn.subscribe((event) => {
}, 20);
});
world.afterEvents.playerLeave.subscribe((event) => {
// Clear session HUD state for whoever left (keyed by id)
// event.playerId exists; event.playerName for compat
if (event.playerId) active.delete(event.playerId);
});
// Active waypoint is persisted as a player dynamic property, so playerLeave
// does not need to clear any in-memory session state.
// ─── Compass Menu ───────────────────────────────────────────────
@@ -183,7 +227,7 @@ async function openCompassMenu(player) {
actions.push({ kind: "select", index: i });
}
const activeIdx = active.get(player.id);
const activeIdx = getActiveIndex(player);
if (activeIdx != null && list[activeIdx]) {
form.button(`§c\u274C Clear active waypoint`);
actions.push({ kind: "clear" });
@@ -192,6 +236,9 @@ async function openCompassMenu(player) {
if (list.length > 0) {
form.button("\uD83D\uDDD1 Delete a waypoint…");
actions.push({ kind: "delete_menu" });
form.button("\uD83D\uDCE4 Share with another player…");
actions.push({ kind: "share" });
}
form.button("\uD83E\uDDED Get a marker block");
@@ -212,14 +259,14 @@ async function openCompassMenu(player) {
return;
}
if (a.kind === "select") {
active.set(player.id, a.index);
setActiveIndex(player, a.index);
const m = list[a.index];
player.sendMessage(`§b[Waypoints] §fGuiding you to §d${m.label}§f.`);
player.sendMessage(`§b[Waypoints] §fGuiding you to §d${m.label}§f. §8(arrow appears at the top of your screen)`);
return;
}
if (a.kind === "clear") {
active.delete(player.id);
player.onScreenDisplay.setActionBar("");
clearActiveIndex(player);
clearNav(player);
player.sendMessage("§b[Waypoints] §fActive waypoint cleared.");
return;
}
@@ -227,6 +274,10 @@ async function openCompassMenu(player) {
await openDeleteMenu(player);
return;
}
if (a.kind === "share") {
await openShareMenu(player);
return;
}
if (a.kind === "give_marker") {
giveMarkerBlock(player);
return;
@@ -286,6 +337,102 @@ async function openDeleteMenu(player) {
player.sendMessage(`§b[Waypoints] §fDeleted §d${chosen.label}§f.`);
}
// ─── Share Waypoint ─────────────────────────────────────────────
// Pick one of your waypoints, pick an online player in this world, send
// them a confirm prompt. On accept, the waypoint is copied into the
// recipient's list with a `(from <sender>)` suffix so it's clearly
// attributed. Cross-world shares are blocked here — a player on the lyla
// server can't be reached from jamie. Same dimension is preferred but
// cross-dim is allowed; the HUD already labels "different dimension".
async function openShareMenu(sender) {
const list = getMarkers(sender.id);
if (list.length === 0) {
sender.sendMessage("§7[Share] You have no waypoints to share.");
return;
}
const others = world.getAllPlayers().filter((p) => p.id !== sender.id);
if (others.length === 0) {
sender.sendMessage("§7[Share] No other players are online in this world.");
return;
}
const wpForm = new ActionFormData()
.title("Share a Waypoint")
.body("§7Pick which waypoint to share:");
for (const m of list) {
wpForm.button(`§f${m.label}\n§7${m.x}, ${m.y}, ${m.z}`);
}
wpForm.button("§7Cancel");
let wpRes;
try { wpRes = await wpForm.show(sender); } catch (_) { return; }
if (wpRes.canceled || wpRes.selection === undefined) return;
if (wpRes.selection >= list.length) return;
const marker = list[wpRes.selection];
const recipForm = new ActionFormData()
.title(`Share "${marker.label}"`)
.body("§7Who should receive this waypoint?");
for (const p of others) recipForm.button(`§f${p.name}`);
recipForm.button("§7Cancel");
let rRes;
try { rRes = await recipForm.show(sender); } catch (_) { return; }
if (rRes.canceled || rRes.selection === undefined) return;
if (rRes.selection >= others.length) return;
const recipient = others[rRes.selection];
// Receiver capacity check before bothering them with a prompt
const recList = getMarkers(recipient.id);
if (recList.length >= MAX_MARKERS_PER_PLAYER) {
sender.sendMessage(`§c[Share] §f${recipient.name}'s waypoint slots are full.`);
return;
}
sender.sendMessage(`§b[Share] §fSent §d${marker.label}§f to §e${recipient.name}§f — waiting for them to accept…`);
const offer = new MessageFormData()
.title("Waypoint Shared")
.body(`§e${sender.name}§r wants to share a waypoint with you:\n\n§f${marker.label}\n§7${marker.x}, ${marker.y}, ${marker.z}\n\nAdd to your compass?`)
.button1("§aAccept")
.button2("§cDecline");
let oRes;
try { oRes = await offer.show(recipient); } catch (_) {
sender.sendMessage(`§c[Share] §f${recipient.name} couldn't be reached.`);
return;
}
if (oRes.canceled || oRes.selection !== 0) {
sender.sendMessage(`§7[Share] §f${recipient.name} declined.`);
recipient.sendMessage(`§7[Share] §fDeclined waypoint from §e${sender.name}§f.`);
return;
}
// Re-check capacity after the await — markers may have changed
if (recList.length >= MAX_MARKERS_PER_PLAYER) {
sender.sendMessage(`§c[Share] §f${recipient.name}'s waypoint slots are full.`);
recipient.sendMessage(`§c[Share] §fYour waypoint slots are full — couldn't accept §d${marker.label}§f.`);
return;
}
const sharedLabel = `${marker.label} (from ${sender.name})`.slice(0, 40);
// Use a key that won't collide with the receiver's own lodestones
const sharedKey = `shared:${sender.id}:${marker.key}`;
recList.push({
x: marker.x,
y: marker.y,
z: marker.z,
dim: marker.dim,
label: sharedLabel,
key: sharedKey,
});
saveWaypoints();
sender.sendMessage(`§a[Share] §f${recipient.name} accepted §d${marker.label}§f.`);
recipient.sendMessage(`§a[Share] §fAdded §d${sharedLabel}§f. Type §e!nav§f to find it.`);
}
// ─── Placement → Label Prompt ───────────────────────────────────
world.afterEvents.playerPlaceBlock.subscribe((event) => {
@@ -402,17 +549,17 @@ function distanceXZ(a, b) {
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
const idx = active.get(player.id);
const idx = getActiveIndex(player);
if (idx == null) continue;
const list = markers[player.id];
const m = list && list[idx];
if (!m) {
active.delete(player.id);
clearActiveIndex(player);
continue;
}
if (m.dim !== player.dimension.id) {
player.onScreenDisplay.setActionBar(`§b\u27A4 §f${m.label} §7• §cdifferent dimension`);
showNav(player, `§c⚠ Different Dimension`, `§7${m.label}`);
continue;
}
@@ -422,10 +569,9 @@ system.runInterval(() => {
const distXZ = Math.sqrt(dx * dx + dz * dz);
if (distXZ <= ARRIVAL_RADIUS) {
active.delete(player.id);
clearActiveIndex(player);
try {
player.onScreenDisplay.setTitle(`§aArrived`, { subtitle: `§f${m.label}`, fadeInDuration: 5, stayDuration: 30, fadeOutDuration: 10 });
player.onScreenDisplay.setActionBar("");
} catch (_) {}
continue;
}
@@ -439,11 +585,7 @@ system.runInterval(() => {
const bucket = ((Math.round(relative / 45) % 8) + 8) % 8;
const arrow = ARROWS[bucket];
try {
player.onScreenDisplay.setActionBar(
`§b\u27A4 §f${m.label} §7• §f${Math.round(distXZ)}m §8[§a${arrow}§8]`
);
} catch (_) {}
showNav(player, `§a${arrow} §f${Math.round(distXZ)}m`, `§7${m.label}`);
}
}, HUD_TICK_INTERVAL);
@@ -515,6 +657,52 @@ function handleChatCommand(player, msg) {
return true;
}
if (msg === "!share") {
system.run(() => openShareMenu(player));
return true;
}
if (msg === "!nav" || msg.startsWith("!nav ")) {
const list = getMarkers(player.id);
const arg = msg.slice(4).trim();
if (arg === "off" || arg === "clear") {
clearActiveIndex(player);
clearNav(player);
player.sendMessage("§b[Waypoints] §fNavigation cleared.");
return true;
}
if (arg) {
const n = Number.parseInt(arg, 10);
if (Number.isFinite(n) && n >= 1 && n <= list.length) {
setActiveIndex(player, n - 1);
const m = list[n - 1];
player.sendMessage(`§b[Waypoints] §fGuiding you to §d${m.label}§f. §8(arrow at top of screen)`);
return true;
}
player.sendMessage(`§c[Waypoints] §fNo waypoint #${arg} — type §e!nav§f to list yours.`);
return true;
}
if (list.length === 0) {
player.sendMessage("§7[Waypoints] No waypoints yet — place a §dlodestone§7 to set one.");
return true;
}
const idx = getActiveIndex(player);
const cur = idx != null ? list[idx] : null;
if (cur) {
const dist = Math.round(distanceXZ(player.location, cur));
player.sendMessage(`§b[Waypoints] §fActive: §d${cur.label}§f (${dist}m). Type §e!nav off§f to clear, or §e!nav <number>§f to switch.`);
} else {
player.sendMessage("§b[Waypoints] §fNo active waypoint. Pick one:");
}
for (let i = 0; i < list.length; i++) {
const m = list[i];
const here = m.dim === player.dimension.id;
const tag = here ? `§7${Math.round(distanceXZ(player.location, m))}m` : "§8(other dim)";
player.sendMessage(` §e${i + 1}§7. §f${m.label} ${tag}`);
}
return true;
}
if (msg === "!clearwaypoints") {
system.run(async () => {
const confirm = new MessageFormData()
@@ -526,7 +714,7 @@ function handleChatCommand(player, msg) {
try { res = await confirm.show(player); } catch (_) { return; }
if (res.canceled || res.selection !== 0) return;
markers[player.id] = [];
active.delete(player.id);
clearActiveIndex(player);
saveWaypoints();
player.sendMessage("§b[Waypoints] §fAll your waypoints were cleared.");
});
@@ -606,5 +794,5 @@ system.runTimeout(() => {
system.run(() => {
loadWaypoints();
world.sendMessage("§b[World] §fHub return system loaded! Place a §dlodestone§f to set a waypoint; right-click your compass to navigate.");
world.sendMessage("§b[World] §fHub return system loaded! Place a §dlodestone§f to set a waypoint, then right-click your compass and pick one — the §anavigation arrow§f shows at the §atop§f of your screen. (Type §e!nav§f for chat fallback.)");
});

View File

@@ -0,0 +1,29 @@
{
"format_version": 2,
"header": {
"name": "Hub Return Resources",
"description": "Recovery compass icon override + future hub-return assets",
"uuid": "b1f7e2a4-3c5d-49b8-9d22-6a4f0c87e511",
"version": [
1,
0,
1
],
"min_engine_version": [
1,
21,
0
]
},
"modules": [
{
"type": "resources",
"uuid": "c4a13e85-2f96-4d1a-b772-9e0f3b4d6c21",
"version": [
1,
0,
1
]
}
]
}

View File

@@ -0,0 +1,27 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.alligator.evict_riders": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"eject_rider": "query.has_target"
}
]
},
"eject_rider": {
"transitions": [
{
"default": "(1.0)"
}
],
"on_entry": [
"/ride @s evict_riders"
]
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.anteater": {
"initial_state": "default",
"states": {
"default": {
"on_entry": ["/effect @s clear slowness"],
"transitions": [
{
"not_move": "q.is_interested || q.timer_flag_1 || query.property('silverlabs_nat:on_defensive_mode') == 'enabled'"
}
]
},
"not_move": {
"on_entry": ["/effect @s slowness infinite 255 true"],
"transitions": [
{
"default": "!q.is_interested && !q.timer_flag_1 && query.property('silverlabs_nat:on_defensive_mode') != 'enabled'"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,71 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.badger.daylight_controller": {
"initial_state": "init",
"states": {
"init": {
"transitions": [
{
"daylight_tamed": "((q.time_of_day - 0.25) <= 0.5) && q.is_tamed"
},
{
"daylight_untamed": "((q.time_of_day - 0.25) <= 0.5)"
},
{
"night": "((q.time_of_day - 0.25) > 0.5)"
}
]
},
"daylight_tamed": {
"on_entry": [
"@s silverlabs_nat:badger_daytime_tamed"
],
"transitions": [
{
"daylight_tamed": "((q.time_of_day - 0.25) <= 0.5) && q.is_tamed"
},
{
"daylight_untamed": "((q.time_of_day - 0.25) <= 0.5)"
},
{
"night": "((q.time_of_day - 0.25) > 0.5)"
}
]
},
"daylight_untamed": {
"on_entry": [
"@s silverlabs_nat:badger_daytime_wild"
],
"transitions": [
{
"daylight_tamed": "((q.time_of_day - 0.25) <= 0.5) && q.is_tamed"
},
{
"daylight_untamed": "((q.time_of_day - 0.25) <= 0.5)"
},
{
"night": "((q.time_of_day - 0.25) > 0.5)"
}
]
},
"night": {
"on_entry": [
"@s silverlabs_nat:badger_nighttime"
],
"transitions": [
{
"daylight_tamed": "((q.time_of_day - 0.25) <= 0.5) && q.is_tamed"
},
{
"daylight_untamed": "((q.time_of_day - 0.25) <= 0.5)"
},
{
"night": "((q.time_of_day - 0.25) > 0.5)"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.bear.eat": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"can_use": "query.property('silverlabs_nat:is_sitting')"
}
]
},
"can_use": {
"transitions": [
{
"default": "!query.property('silverlabs_nat:is_sitting')"
},
{
"configure_eating": "!math.random_integer(0, 79)"
}
]
},
"configure_eating": {
"on_entry": [
"@s silverlabs_nat:configure_eating"
],
"transitions": [
{
"default": "!query.property('silverlabs_nat:is_sitting')"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.beaver.chew_watcher": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "chewing": "query.property('silverlabs_nat:is_chewing')" }
]
},
"chewing": {
"on_exit": [
"/scriptevent silverlabs_nat:beaver_chew"
],
"transitions": [
{ "default": "!query.property('silverlabs_nat:is_chewing')" }
]
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.bird.landing_check": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"v.switch_chance = 200;"
],
"transitions": [
{
"walking": "query.property('silverlabs_nat:movement_mode') == 'walk'"
},
{
"flying": "query.property('silverlabs_nat:movement_mode') == 'fly'"
}
]
},
"walking": {
"on_entry": [
"v.switch_delay = query.life_time + Math.random_integer(5, 10);"
],
"transitions": [
{
"start_flying": "!q.is_baby && (Math.random_integer(0, (v.switch_chance ?? 1)) == (v.switch_chance ?? 1) && (query.life_time > (v.switch_delay ?? 0))) && !query.property('silverlabs_nat:is_tempted') && !q.is_sitting && !q.is_in_love"
},
{
"flying": "query.property('silverlabs_nat:movement_mode') == 'fly'"
}
]
},
"start_flying": {
"on_entry": [
"@s silverlabs_nat:bird_set_flying_mode"
],
"transitions": [
{
"flying": "query.property('silverlabs_nat:movement_mode') == 'fly'"
}
]
},
"flying": {
"on_entry": [
"v.switch_delay = query.life_time + Math.random_integer(5, 10);"
],
"transitions": [
{
"landing_check": "q.is_baby || (Math.random_integer(0, (v.switch_chance ?? 1)) == (v.switch_chance ?? 1) && (query.life_time > (v.switch_delay ?? 0)) && !q.has_target) || query.property('silverlabs_nat:is_tempted') || q.is_sitting || q.is_in_love"
},
{
"walking": "query.property('silverlabs_nat:movement_mode') == 'walk'"
}
]
},
"landing_check": {
"on_entry": [
"/scriptevent silverlabs_nat:bird_landing_check"
],
"transitions": [
{
"default": "1"
}
]
}
}
},
"controller.animation.silverlabs_nat.bird.tempt_check": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"@s silverlabs_nat:not_tempted"
],
"transitions": [
{
"tempted": "q.is_interested"
}
]
},
"tempted": {
"on_entry": [
"@s silverlabs_nat:is_tempted"
],
"transitions": [
{
"default": "!q.is_interested"
}
]
}
}
},
"controller.animation.silverlabs_nat.bird.smooth_landing": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"tempted": "!q.is_on_ground"
}
]
},
"tempted": {
"on_entry": [
"/effect @s slow_falling 2 1 true"
],
"transitions": [
{
"default": "1"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.blobfish": {
"initial_state": "init",
"states": {
"init": {
"transitions": [
{
"normal_blobfish": "query.is_in_water && q.position(1) < 30"
},
{
"land_blobfish": "q.position(1) > 30"
}
]
},
"normal_blobfish": {
"animations": [
"blobfish.duration"
],
"on_entry": [
"@s silverlabs_nat:blobfish_normal"
],
"transitions": [
{
"land_blobfish": "q.anim_time > 3.0 && q.position(1) > 30"
}
]
},
"land_blobfish": {
"animations": [
"blobfish.duration"
],
"on_entry": [
"@s silverlabs_nat:blobfish_land"
],
"transitions": [
{
"normal_blobfish": "q.anim_time > 3.0 && query.is_in_water && q.position(1) < 30"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.bucketable_entity": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"on_bucket": "q.has_any_family('silverlabs_nat:bucketable_entity') && query.property('silverlabs_nat:on_bucket')"
}
]
},
"on_bucket": {
"on_entry": [
"/scriptevent silverlabs_nat:bucketable_entity_interaction @initiator"
]
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.clam.launch": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"before_open": "query.property('silverlabs_nat:before_open')"
}
]
},
"before_open": {
"on_entry": [
"/scriptevent silverlabs_nat:clam_launch"
],
"transitions": [
{
"default": "!query.property('silverlabs_nat:before_open')"
}
]
}
}
},
"controller.animation.silverlabs_nat.clam.took_item": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"has_item": "query.property('silverlabs_nat:has_item')"
}
]
},
"has_item": {
"transitions": [
{
"took_item": "query.property('silverlabs_nat:took_item')"
}
]
},
"took_item": {
"on_entry": [
"/replaceitem entity @s slot.weapon.mainhand 0 minecraft:air"
]
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.crab": {
"initial_state": "on_load",
"states": {
"on_load": {
"on_entry": ["@s silverlabs_nat:crab_is_not_dancing"],
"transitions": [
{
"try_collect_sand": "query.property('silverlabs_nat:sand_block_interaction_state') == 'try_collect_sand'"
},
{
"try_place_sand": "query.property('silverlabs_nat:sand_block_interaction_state') == 'try_place_sand'"
}
]
},
"try_collect_sand": {
"on_entry": ["/scriptevent silverlabs_nat:crab_try_collect_sand"],
"transitions": [
{
"on_load": "query.property('silverlabs_nat:sand_block_interaction_state') != 'try_collect_sand'"
}
]
},
"try_place_sand": {
"on_entry": ["/scriptevent silverlabs_nat:crab_try_place_sand"],
"transitions": [
{
"on_load": "query.property('silverlabs_nat:sand_block_interaction_state') != 'try_place_sand'"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.eel_water_land": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"land": "!q.is_in_water"
},
{
"water": "q.is_in_water"
}
]
},
"land": {
"on_entry": ["@s silverlabs_nat:eel_on_land"],
"transitions": [
{
"water": "q.is_in_water"
}
]
},
"water": {
"on_entry": ["@s silverlabs_nat:eel_in_water"],
"transitions": [
{
"land": "!q.is_in_water"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.emperor_penguin.fall": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"fall": "query.property('silverlabs_nat:slip_anim') == 'none' && q.is_on_ground && !q.is_in_water && q.modified_move_speed > 0.05 && !query.property('silverlabs_nat:egg_protector') && math.random_integer(0, 333) == 0"
}
]
},
"fall": {
"on_entry": [
"/scriptevent silverlabs_nat:emperor_penguin_fall",
"v.cooldown_timer = query.life_time;"
],
"transitions": [
{
"default": "query.life_time >= (v.cooldown_timer + 16.0)"
}
]
}
}
},
"controller.animation.silverlabs_nat.emperor_penguin.egg_check": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"v.cooldown_timer = 0;"
],
"transitions": [
{
"remove": "query.property('silverlabs_nat:egg_protector') && q.has_rider"
},
{
"cooldown": "query.property('silverlabs_nat:egg_protector') && !q.has_target"
}
]
},
"cooldown": {
"on_entry": [
"v.cooldown_timer = query.life_time;"
],
"transitions": [
{
"remove": "q.has_rider"
},
{
"default": "!query.property('silverlabs_nat:egg_protector') || q.has_target"
},
{
"egg_check": "query.life_time >= (v.cooldown_timer + 6.0)"
}
]
},
"egg_check": {
"on_entry": [
"/execute unless entity @e[type=silverlabs_nat:emperor_penguin_egg,c=1,r=24] run event entity @s silverlabs_nat:remove_egg_protector"
],
"transitions": [
{
"default": "1"
}
]
},
"remove": {
"on_entry": [
"@s silverlabs_nat:remove_egg_protector"
],
"transitions": [
{
"default": "1"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,133 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.light": {
"states": {
"default": {
"on_entry": [
"@s silverlabs_nat:reset_all"
],
"transitions": [
{
"reset": "q.is_shaking"
},
{
"on": "!q.is_shaking"
}
]
},
"on": {
"on_entry": [
"/tag @s add silverlabs_nat:light_source",
"/function sf/nba/lighting/setup"
],
"transitions": [
{
"default": "query.property('silverlabs_nat:furniture_variant') == 1"
},
{
"reset": "q.is_shaking"
}
]
},
"reset": {
"on_entry": [
"/function sf/nba/lighting/remove",
"/tag @s remove silverlabs_nat:light_source"
]
}
}
},
"controller.animation.silverlabs_nat.furniture_moved": {
"initial_state": "not_moving",
"states": {
"not_moving": {
"transitions": [
{
"moving": "math.abs(q.position_delta(0)) > 0.01 || math.abs(q.position_delta(1)) > 0.01 || math.abs(q.position_delta(2)) > 0.01 || q.is_in_lava"
}
]
},
"moving": {
"on_entry": [
"@s silverlabs_nat:drop_egg"
]
}
}
},
"controller.animation.silverlabs_nat.furniture_hit": {
"initial_state": "not_hit",
"states": {
"not_hit": {
"transitions": [
{
"hit": "query.property('silverlabs_nat:furniture_hit')"
},
{
"destroy": "query.is_shaking"
}
]
},
"hit": {
"on_entry": [
"@s silverlabs_nat:furniture_not_hit",
"/playsound dig.stone @a[r=16] ~ ~ ~ 0.75 1.45"
],
"transitions": [
{
"destroy": "query.is_shaking"
},
{
"not_hit": "1"
}
]
},
"destroy": {}
}
},
"controller.animation.silverlabs_nat.furniture_spawn": {
"states": {
"default": {
"transitions": [
{
"just_spawned": "query.property('silverlabs_nat:just_spawned')"
}
]
},
"just_spawned": {
"on_entry": [
"@s silverlabs_nat:finish_spawned",
"/playsound dig.stone @a[r=16] ~ ~ ~ 0.75 1.45"
]
}
}
},
"controller.animation.silverlabs_nat.firefly_jar.furniture_remove": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"egg_drop": "1"
}
]
},
"egg_drop": {
"on_entry": [
"/loot spawn ~0.5 ~0.5 ~0.5 loot \"sf/nba/items/firefly_jar.loot\""
],
"transitions": [
{
"dropped_egg": "1"
}
]
},
"dropped_egg": {
"on_entry": [
"@s silverlabs_nat:remove"
]
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.giraffe.head_collision": {
"initial_state": "head_normal",
"states": {
"head_normal": {
"on_entry": [
"@s silverlabs_nat:set_head_normal"
],
"transitions": [
{
"head_low": "q.has_rider && q.rider_head_x_rotation(0) >= 45"
}
]
},
"head_low": {
"on_entry": [
"@s silverlabs_nat:set_head_low"
],
"transitions": [
{
"head_normal": "!q.has_rider || q.rider_head_x_rotation(0) < 45"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.hamster.riding_check": {
"states": {
"default": {
"on_entry": ["@s silverlabs_nat:remove_move_away"],
"transitions": [
{ "riding": "q.is_riding" }
]
},
"riding": {
"transitions": [
{ "unriding": "!q.is_riding" }
],
"on_exit": ["@s silverlabs_nat:move_away"]
},
"unriding": {
"transitions": [
{ "default": "q.state_time >= 3.0" }
]
}
}
},
"controller.animation.silverlabs_nat.hamster.variant_fixer": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "variant_0": "q.had_component_group('silverlabs_nat:variant_0')" },
{ "variant_1": "q.had_component_group('silverlabs_nat:variant_1')" },
{ "variant_2": "q.had_component_group('silverlabs_nat:variant_2')" },
{ "variant_3": "q.had_component_group('silverlabs_nat:variant_3')" },
{ "variant_4": "q.had_component_group('silverlabs_nat:variant_4')" },
{ "variant_5": "q.had_component_group('silverlabs_nat:variant_5')" },
{ "variant_6": "q.had_component_group('silverlabs_nat:variant_6')" }
]
},
"variant_0": {
"on_entry": ["@s silverlabs_nat:set_variant_black"]
},
"variant_1": {
"on_entry": ["@s silverlabs_nat:set_variant_blackwhite"]
},
"variant_2": {
"on_entry": ["@s silverlabs_nat:set_variant_brown"]
},
"variant_3": {
"on_entry": ["@s silverlabs_nat:set_variant_grey"]
},
"variant_4": {
"on_entry": ["@s silverlabs_nat:set_variant_orange"]
},
"variant_5": {
"on_entry": ["@s silverlabs_nat:set_variant_peach"]
},
"variant_6": {
"on_entry": ["@s silverlabs_nat:set_variant_white"]
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.hamster_wheel.initialize": {
"states": {
"default": {
"transitions": [
{
"initialize": "!query.property('silverlabs_nat:initialized')"
}
]
},
"initialize": {
"on_entry": [
"/function sf/nba/cardinal_orientation",
"@s silverlabs_nat:initialized"
]
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.hedgehog.hit": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"hit": "query.property('silverlabs_nat:hit')"
}
]
},
"hit": {
"on_entry": [
"/scriptevent silverlabs_nat:hedgehog_hit",
"@s silverlabs_nat:remove_projectile"
],
"transitions": [
{
"fallback": "q.state_time > 3"
}
]
},
"fallback": {
"on_entry": [
"/scriptevent silverlabs_nat:hedgehog_hit",
"@s silverlabs_nat:remove_projectile"
]
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.block_animals.hippo.ram_watcher": {
"states": {
"default": {
"transitions": [
{ "ram_attack": "q.is_casting" }
]
},
"ram_attack": {
"on_entry": [
"/scriptevent silverlabs_nat:hippo_ram_attack",
"v.ram_attack_delay = query.life_time + 1.5;"
],
"transitions": [
{ "default": "query.life_time > (v.ram_attack_delay ?? 0)" }
]
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.info_book.orient": {
"initial_state": "not_oriented",
"states": {
"not_oriented": {
"on_entry": [
"/function sf/nba/cardinal_orientation"
],
"transitions": [
{
"oriented": "1"
}
]
},
"oriented": {
"on_entry": [
"@s silverlabs_nat:oriented"
]
}
}
},
"controller.animation.silverlabs_nat.info_book.hit": {
"initial_state": "not_hit",
"states": {
"not_hit": {
"transitions": [
{
"hit": "query.property('silverlabs_nat:hit')"
}
]
},
"hit": {
"on_entry": [
"@s silverlabs_nat:not_hit"
],
"transitions": [
{
"not_hit": "1"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,133 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.kiwi.digging_control": {
"states": {
"default": {
"transitions": [
{
"init": "!q.is_tamed"
}
]
},
"init": {
"on_entry": [
"/event entity @s silverlabs_nat:end_feeling_happy",
"/event entity @s silverlabs_nat:scenting"
],
"transitions": [
{
"test_digging_success": "query.property('silverlabs_nat:digging_states') == 'test_digging_success'"
}
]
},
"test_digging_success": {
"on_entry": [
"/function sf/nba/gameplay/digging/init_digging"
],
"on_exit": [
"v.stand_delay_trigger = query.life_time + 2;"
],
"transitions": [
{
"digging_successful": "query.property('silverlabs_nat:digging_states') == 'successful'"
},
{
"digging_unsuccessful": "query.property('silverlabs_nat:digging_states') == 'unsuccessful'"
}
]
},
"digging_successful": {
"on_entry": [
"/function sf/nba/gameplay/digging/digging_loot"
],
"transitions": [
{
"feeling_happy": "query.life_time >= v.stand_delay_trigger"
}
]
},
"digging_unsuccessful": {
"transitions": [
{
"default": "query.life_time >= v.stand_delay_trigger"
}
]
},
"feeling_happy": {
"on_entry": [
"/event entity @s silverlabs_nat:feeling_happy"
],
"transitions": [
{
"default": "1"
}
]
}
}
},
"controller.animation.silverlabs_nat.kiwi.sniffing_control": {
"states": {
"default": {
"transitions": [
{
"start_sniffing": "!q.is_tamed && (query.property('silverlabs_nat:digging_states') == 'trigger_digging')"
}
]
},
"start_sniffing": {
"on_entry": [
"/function sf/nba/gameplay/digging/init_sniffing"
],
"transitions": [
{
"default": "query.property('silverlabs_nat:digging_states') != 'trigger_digging'"
}
]
}
}
},
"controller.animation.silverlabs_nat.kiwi.pickup_control": {
"states": {
"default": {
"transitions": [
{
"start_sniffing": "q.is_tamed && !q.is_item_name_any('slot.weapon.mainhand', 'minecraft:wheat')"
}
]
},
"start_sniffing": {
"on_entry": [
"/replaceitem entity @s slot.weapon.mainhand 0 wheat"
],
"transitions": [
{
"start_sniffing": "!q.is_tamed || q.is_item_name_any('slot.weapon.mainhand', 'minecraft:wheat')"
}
]
}
}
},
"controller.animation.silverlabs_nat.kiwi.following_owner_check": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"@s silverlabs_nat:not_following_owner"
],
"transitions": [
{ "following": "!q.is_sitting && q.is_tamed" }
]
},
"following": {
"on_entry": [
"@s silverlabs_nat:is_following_owner"
],
"transitions": [
{ "default": "q.is_sitting || !q.is_tamed" }
]
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.reptile_tail.flop": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"flop": "q.is_on_ground && !q.is_in_water"
}
]
},
"flop": {
"on_entry": [
"/scriptevent silverlabs_nat:reptile_tail_flop"
],
"transitions": [
{
"default": "!q.is_on_ground || q.is_in_water"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.mole.trail": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"state_check": "(math.abs(q.position_delta(0)) > 0.01|| math.abs(q.position_delta(2)) > 0.01) && query.property('silverlabs_nat:mole_state') != 'unrolled'"
},
{
"state_check_baby": "(math.abs(q.position_delta(0)) > 0.01|| math.abs(q.position_delta(2)) > 0.01) && query.property('silverlabs_nat:mole_state') != 'unrolled' && q.is_baby"
}
]
},
"state_check": {
"on_entry": [
"/execute positioned ~~~ unless entity @e[type=silverlabs_nat:dirt_trail,r=0.5] run summon silverlabs_nat:dirt_trail ~ ~ ~"
],
"transitions": [
{
"default": "(1.0)"
}
]
},
"state_check_baby": {
"on_entry": [
"/execute positioned ~~~ unless entity @e[type=silverlabs_nat:dirt_trail,r=0.5] run summon silverlabs_nat:dirt_trail ~ ~ ~ ~ ~ silverlabs_nat:baby"
],
"transitions": [
{
"default": "(1.0)"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,81 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.ostrich.egg_check": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"v.cooldown_timer = 0;"
],
"transitions": [
{
"remove": "query.property('silverlabs_nat:egg_protector') && q.has_rider"
},
{
"cooldown": "query.property('silverlabs_nat:egg_protector') && !q.has_target"
}
]
},
"cooldown": {
"on_entry": [
"v.cooldown_timer = query.life_time;"
],
"transitions": [
{
"remove": "q.has_rider"
},
{
"default": "!query.property('silverlabs_nat:egg_protector') || q.has_target"
},
{
"egg_check": "query.life_time >= (v.cooldown_timer + 6.0)"
}
]
},
"egg_check": {
"on_entry": [
"/execute unless entity @e[type=silverlabs_nat:ostrich_egg,c=1,r=24] run event entity @s silverlabs_nat:remove_egg_protector"
],
"transitions": [
{
"default": "1"
}
]
},
"remove": {
"on_entry": [
"@s silverlabs_nat:remove_egg_protector"
],
"transitions": [
{
"default": "1"
}
]
}
}
},
"controller.animation.silverlabs_nat.ostrich.flap": {
"initial_state": "grounded",
"states": {
"grounded": {
"transitions": [
{
"flapping": "query.property('silverlabs_nat:has_rider') && !q.is_on_ground"
}
]
},
"flapping": {
"on_entry": [
"/effect @s slow_falling 2 0 true"
],
"transitions": [
{
"grounded": "q.is_on_ground || !query.property('silverlabs_nat:has_rider')"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.ostrich_egg.on_hatch": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"on_hatch": "q.is_transforming"
}
]
},
"on_hatch": {
"on_entry": [
"/event entity @e[c=1,type=silverlabs_nat:ostrich,r=12] silverlabs_nat:remove_egg_protector"
]
}
}
}
}
}

View File

@@ -0,0 +1,103 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.otter.eat": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"v.eat_step = 0;",
"v.float_delay = query.life_time + 1;",
"@s silverlabs_nat:not_floating"
],
"transitions": [
{
"float": "(query.life_time > (v.float_delay ?? 0)) && q.is_item_equipped(0) && q.is_in_water && (math.random_integer(1,100) == 100)"
},
{
"give_to_player": "(query.life_time > (v.float_delay ?? 0)) && q.is_item_equipped(0) && (math.random_integer(1,100) == 100)"
}
]
},
"float": {
"on_entry": [
"@s silverlabs_nat:is_floating",
"v.eat_delay = query.life_time + math.random(2,5);"
],
"transitions": [
{
"default": "!q.is_item_equipped(0) || !q.is_in_water || (q.modified_move_speed >= 0.075)"
},
{
"eat": "(query.life_time > (v.eat_delay ?? 0))"
}
]
},
"eat": {
"on_entry": [
"v.eat_step = (v.eat_step ?? 0) + 1;",
"v.eat_delay = query.life_time + 0.15;"
],
"transitions": [
{
"munch": "((v.eat_step ?? 0) < 3) && (query.life_time > (v.eat_delay ?? 0))"
},
{
"digest": "(query.life_time > (v.eat_delay ?? 0))"
}
]
},
"munch": {
"on_entry": ["/playsound mob.fox.eat @a ~ ~ ~"],
"transitions": [
{
"eat": "1"
}
]
},
"digest": {
"on_entry": [
"/playsound mob.fox.eat @a ~ ~ ~ 1 1.2",
"/replaceitem entity @s slot.inventory 0 air",
"/replaceitem entity @s slot.weapon.mainhand 0 air",
"@s silverlabs_nat:otter_on_finish_eat",
"@s silverlabs_nat:not_floating"
],
"transitions": [
{
"default": "1"
}
]
},
"give_to_player": {
"on_entry": ["@s silverlabs_nat:give_item_to_player"],
"transitions": [
{
"default": "!q.is_item_equipped(0)"
}
]
}
}
},
"controller.animation.silverlabs_nat.otter.item_check": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"test": "q.is_item_equipped(0)"
}
]
},
"test": {
"on_entry": ["/scriptevent silverlabs_nat:otter_check"],
"transitions": [
{
"default": "!q.is_item_equipped(0)"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.piranha.attack": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"v.attack_start = 0.0;"
],
"transitions": [
{ "attack": "q.is_delayed_attacking" }
]
},
"attack": {
"on_entry": [
"v.attack_start = q.life_time;"
],
"transitions": [
{ "apply_damage": "q.life_time >= v.attack_start + 0.3" }
]
},
"apply_damage": {
"on_entry": [
"/damage @e[family=!silverlabs_nat:piranha, r=2, c=1] 1 none",
"v.despawn_time = q.life_time;"
],
"transitions": [
{ "despawn": "q.life_time >= v.despawn_time + 0.25" }
]
},
"despawn": {
"on_entry": [
"@s silverlabs_nat:despawn"
],
"transitions": [
{ "default": "1" }
]
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.raccoon": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"is_searching_item": "query.property('silverlabs_nat:search_state') == 'searching_item'"
}
]
},
"is_searching_item": {
"on_entry": ["/scriptevent silverlabs_nat:raccoon_searching_item"],
"transitions": [
{
"cancel_searching_item": "query.property('silverlabs_nat:search_state') == 'cancel_searching_item'"
},
{
"default": "query.property('silverlabs_nat:search_state') != 'searching_item'"
}
]
},
"cancel_searching_item": {
"on_entry": ["/scriptevent silverlabs_nat:raccoon_cancel_searching_item"],
"transitions": [
{
"default": "query.property('silverlabs_nat:search_state') != 'cancel_searching_item'"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.ray.main": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"@s silverlabs_nat:complete_attack"
],
"transitions": [
{ "escape": "!q.is_powered && q.is_avoiding_mobs" }
]
},
"escape": {
"on_entry": [
"v.escape_timer = query.life_time + 3;"
],
"transitions": [
{ "hide": "query.life_time > (v.escape_timer ?? 0)" }
]
},
"hide": {
"on_entry": [
"v.hide_timer = query.life_time + 3;",
"@s silverlabs_nat:start_hide"
],
"transitions": [
{
"default": "(query.life_time > (v.hide_timer ?? 0)) || !q.is_powered"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,105 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.red_panda.sleep_stand": {
"initial_state": "init",
"states": {
"init": {
"transitions": [
{
"wake_up": "query.property('silverlabs_nat:red_panda_energy') >= 10"
},
{
"fall_asleep": "query.property('silverlabs_nat:red_panda_energy') < 10 && !query.property('silverlabs_nat:red_panda_jolt_awake') && !q.is_moving && !q.is_in_water_or_rain"
}
]
},
"wake_up": {
"on_entry": [
"@s silverlabs_nat:awake"
],
"transitions": [
{
"awake": "1"
}
]
},
"awake": {
"animations": [
"duration"
],
"transitions": [
{
"fall_asleep": "query.property('silverlabs_nat:red_panda_energy') < 10 && !query.property('silverlabs_nat:red_panda_jolt_awake') && !q.is_moving && !q.is_in_water_or_rain"
},
{
"awake_2": "q.anim_time >= 1"
}
]
},
"awake_2": {
"on_entry": [
"@s silverlabs_nat:decrement_energy"
],
"transitions": [
{
"standing": "math.random_integer(0, 20) == 0"
},
{
"awake": "1"
}
]
},
"standing": {
"on_entry": [
"@s silverlabs_nat:start_standing"
],
"on_exit": [
"@s silverlabs_nat:stop_standing"
],
"animations": [
"duration"
],
"transitions": [
{
"awake": "q.anim_time >= math.die_roll(3, 0.5, 4) || q.is_moving"
}
]
},
"fall_asleep": {
"on_entry": [
"@s silverlabs_nat:sleeping"
],
"transitions": [
{
"sleeping": "1"
}
]
},
"sleeping": {
"animations": [
"duration"
],
"transitions": [
{
"wake_up": "query.property('silverlabs_nat:red_panda_energy') >= 960 || query.property('silverlabs_nat:red_panda_jolt_awake')"
},
{
"sleeping_2": "q.anim_time >= 1"
}
]
},
"sleeping_2": {
"on_entry": [
"@s silverlabs_nat:increment_energy"
],
"transitions": [
{
"sleeping": "1"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.block_animals.rhino.ram_watcher": {
"states": {
"default": {
"on_entry": [
"/tag @s remove ram_attack",
"@s silverlabs_nat:stop_ram"
],
"transitions": [
{ "start_ram_attack": "q.is_casting" }
]
},
"start_ram_attack": {
"on_entry": [
"v.ram_attack_delay = query.life_time + 2.2;"
],
"transitions": [
{ "default": "!q.is_casting" },
{
"using_ram_attack": "query.life_time > (v.ram_attack_delay ?? 0)"
}
]
},
"using_ram_attack": {
"on_entry": [
"/scriptevent silverlabs_nat:rhino_ram_attack",
"/tag @s add ram_attack",
"@s silverlabs_nat:start_ram",
"v.ram_attack_end_delay = query.life_time + 1.95;"
],
"transitions": [
{ "default": "query.life_time > (v.ram_attack_end_delay ?? 0)" }
]
}
}
},
"controller.animation.silverlabs_nat.block_animals.rhino.angry_watcher": {
"states": {
"default": {
"transitions": [
{ "scared": "q.has_target" }
]
},
"scared": {
"on_entry": [
"/scriptevent silverlabs_nat:rhino_angry"
],
"transitions": [
{ "default": "!q.has_target" }
]
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.skunk_spray": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"spraying": "query.property('silverlabs_nat:is_spraying') == true"
}
]
},
"spraying": {
"transitions": [
{
"default": "query.property('silverlabs_nat:is_spraying') == false"
}
],
"on_entry": [
"/scriptevent silverlabs_nat:skunk_spray"
]
}
}
},
"controller.animation.silverlabs_nat.skunk.breed_check": {
"initial_state": "default",
"states": {
"default": {
"on_entry": [
"@s silverlabs_nat:set_not_breeding"
],
"transitions": [
{ "breeding": "q.is_in_love" }
]
},
"breeding": {
"on_entry": [
"@s silverlabs_nat:set_is_breeding"
],
"transitions": [
{ "default": "!q.is_in_love" }
]
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.slug.bounce": {
"initial_state": "default",
"states": {
"default": {
"on_entry": ["@s silverlabs_nat:can_detect_bounce"],
"transitions": [
{ "bounce": "query.property('silverlabs_nat:bounce')" }
]
},
"bounce": {
"on_entry": [
"v.delay = 2;",
"v.current_delay = query.life_time;",
"/scriptevent silverlabs_nat:should_bounce_player @p"
],
"transitions": [
{ "default": "(query.life_time - v.current_delay >= v.delay)" }
]
}
}
}
}
}

View File

@@ -0,0 +1,64 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.snail.crush_check": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "test": "query.property('silverlabs_nat:was_stepped_on')" }
]
},
"test": {
"on_entry": [
"/scriptevent silverlabs_nat:snail_crush_check"
],
"transitions": [
{ "default": "!query.property('silverlabs_nat:was_stepped_on')" }
]
}
}
},
"controller.animation.silverlabs_nat.snail.catch_check": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "test": "query.property('silverlabs_nat:was_caught')" }
]
},
"test": {
"on_entry": [
"/scriptevent silverlabs_nat:snail_bucket"
],
"transitions": [
{ "default": "!query.property('silverlabs_nat:was_caught')" }
]
}
}
},
"controller.animation.silverlabs_nat.snail.lay_egg": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "ready": "q.is_pregnant" }
]
},
"ready": {
"transitions": [
{ "done": "!q.is_pregnant" }
]
},
"done": {
"on_entry": [
"/scriptevent silverlabs_nat:snailegg_formed"
],
"transitions": [
{ "default": "1" }
]
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.snake.digest": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "digest": "query.property('silverlabs_nat:digested_item')" }
]
},
"digest": {
"on_entry": [
"/replaceitem entity @s slot.inventory 0 air",
"/replaceitem entity @s slot.weapon.mainhand 0 air",
"@s silverlabs_nat:snake_on_finish_eat"
],
"transitions": [
{ "default": "1" }
]
}
}
},
"controller.animation.silverlabs_nat.snake.food_check": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "test": "q.is_item_equipped(0)" }
]
},
"test": {
"on_entry": [
"/scriptevent silverlabs_nat:snake_check"
],
"transitions": [
{ "default": "!q.is_item_equipped(0)" }
]
}
}
},
"controller.animation.silverlabs_nat.snake.wake_up_check": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "wake_up": "q.is_in_love" },
{ "can_sleep": "!q.is_in_love" }
]
},
"wake_up": {
"on_entry": [
"/tag @s add woken_up"
],
"transitions": [
{ "can_sleep": "!q.is_in_love" }
]
},
"can_sleep": {
"on_entry": [
"/tag @s remove woken_up"
],
"transitions": [
{ "wake_up": "q.is_in_love" }
]
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.termite.chew_watcher": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{ "chewing": "query.property('silverlabs_nat:is_chewing')" }
]
},
"chewing": {
"on_exit": [
"/scriptevent silverlabs_nat:termite_chew"
],
"transitions": [
{ "default": "!query.property('silverlabs_nat:is_chewing')" }
]
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.vulture.item_equipped": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"on_equip": "!query.property('silverlabs_nat:is_item_equipped') && q.is_item_equipped"
},
{
"on_unequip": "query.property('silverlabs_nat:is_item_equipped') && !q.is_item_equipped"
}
]
},
"on_equip": {
"on_entry": ["@s silverlabs_nat:set_item_equipped"],
"transitions": [
{
"default": "query.property('silverlabs_nat:is_item_equipped')"
}
]
},
"on_unequip": {
"on_entry": ["@s silverlabs_nat:set_item_equipped"],
"transitions": [
{
"default": "!query.property('silverlabs_nat:is_item_equipped')"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
{
"format_version": "1.10.0",
"animation_controllers": {
"controller.animation.silverlabs_nat.water_drinkable": {
"initial_state": "default",
"states": {
"default": {
"transitions": [
{
"can_use": "!q.is_baby"
}
]
},
"can_use": {
"transitions": [
{
"can_drink": "q.is_on_ground ? {t.x_offset = -math.sin(q.body_y_rotation) * 2.5; t.z_offset = math.cos(q.body_y_rotation) * 2.5; v.can_drink = q.relative_block_has_any_tag(t.x_offset, -1.0, t.z_offset, 'water');}: {v.can_drink = 0;}; return (v.can_drink ?? 0) && !query.property('silverlabs_nat:can_drink');"
},
{
"can_move_to_water": "query.property('silverlabs_nat:can_drink') && !(v.can_drink ?? 0)"
}
]
},
"can_drink": {
"on_entry": ["@s silverlabs_nat:can_drink"],
"transitions": [
{
"can_use": "query.property('silverlabs_nat:can_drink')"
}
]
},
"can_move_to_water": {
"on_entry": ["@s silverlabs_nat:can_move_to_water"],
"transitions": [
{
"can_use": "!query.property('silverlabs_nat:can_drink')"
}
]
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
{
"format_version": "1.10.0",
"animations": {
"animation.silverlabs_nat.alligator.defensive_mode_check": {
"loop": true,
"animation_length": 1.0,
"timeline": {
"0.0": [
"/execute at @s if entity @e[family=silverlabs_nat:alligator_egg,r=10] run event entity @s silverlabs_nat:defending_eggs",
"/execute at @s unless entity @e[family=silverlabs_nat:alligator_egg,r=10] run event entity @s silverlabs_nat:not_defending_eggs"
]
}
}
}
}

View File

@@ -0,0 +1,14 @@
{
"format_version": "1.10.0",
"animations": {
"animation.silverlabs_nat.bear.harvest_sensor": {
"loop": true,
"animation_length": 3.0,
"timeline": {
"1.5": [
"@s silverlabs_nat:harvest_sensor"
]
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"format_version": "1.10.0",
"animations": {
"animation.silverlabs_nat.beaver.get_stick": {
"loop": true,
"animation_length": 1.0,
"timeline": {
"0.0": [
"/replaceitem entity @s slot.weapon.mainhand 0 stick"
]
}
},
"animation.silverlabs_nat.beaver.find_stick": {
"loop": true,
"animation_length": 1.0,
"timeline": {
"0.0": [
"@s silverlabs_nat:find_stick"
]
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More