Compare commits
37 Commits
64e12603de
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b28e4d0610 | |||
| fd73ac55ec | |||
| 14043fe2a0 | |||
| eb82c8307b | |||
| cc57662468 | |||
| 17a9faf206 | |||
| 9789906277 | |||
| 6bda72598d | |||
| af9d37462c | |||
| 7c8cd5b075 | |||
| b9e3380f6c | |||
| 145f5d9beb | |||
| 77a7524917 | |||
| 9a2389e050 | |||
| 3e08a59972 | |||
| fce15ac801 | |||
| f126eeb955 | |||
| 60603ab74c | |||
| ad5c365c2c | |||
| 7e3432d868 | |||
| 5c1af25468 | |||
| 82250164ca | |||
| 3a010091e5 | |||
| 14dd6d5ce7 | |||
| 25870ef082 | |||
| c35b97afef | |||
| 81f84b56a0 | |||
| db8f37a24d | |||
| e6148a2949 | |||
| 43248820fa | |||
| 8b83e324f0 | |||
| 579dfec633 | |||
|
|
c1cd32eeb5 | ||
| d6bafb9d16 | |||
| f7aa71e9eb | |||
| cc18066c17 | |||
| c176263ec5 |
@@ -7,8 +7,13 @@ on:
|
|||||||
- 'addon/**'
|
- 'addon/**'
|
||||||
- 'lobby-addon/**'
|
- 'lobby-addon/**'
|
||||||
- 'hub-return-addon/**'
|
- 'hub-return-addon/**'
|
||||||
- 'easter-egg-addon/**'
|
|
||||||
- 'village-evolution-addon/**'
|
- 'village-evolution-addon/**'
|
||||||
|
- 'monkey-addon/**'
|
||||||
|
- 'private-chest-addon/**'
|
||||||
|
- 'home-sign-addon/**'
|
||||||
|
- 'keep-inventory-addon/**'
|
||||||
|
- 'postal-service-addon/**'
|
||||||
|
- 'camping-supplies-addon/**'
|
||||||
- 'docker-compose.yml'
|
- 'docker-compose.yml'
|
||||||
- 'scripts/**'
|
- 'scripts/**'
|
||||||
|
|
||||||
@@ -26,6 +31,7 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
set -e
|
set -e
|
||||||
APP_DIR="$HOME/minecraft-multiworld"
|
APP_DIR="$HOME/minecraft-multiworld"
|
||||||
|
PATHS="addon/ lobby-addon/ hub-return-addon/ village-evolution-addon/ monkey-addon/ private-chest-addon/ home-sign-addon/ keep-inventory-addon/ postal-service-addon/ camping-supplies-addon/ docker-compose.yml"
|
||||||
|
|
||||||
# First run: clone. Subsequent: pull.
|
# First run: clone. Subsequent: pull.
|
||||||
if [ ! -d "$APP_DIR/.git" ]; then
|
if [ ! -d "$APP_DIR/.git" ]; then
|
||||||
@@ -33,11 +39,11 @@ jobs:
|
|||||||
git init
|
git init
|
||||||
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ village-evolution-addon/ docker-compose.yml
|
git checkout -f origin/main -- $PATHS
|
||||||
else
|
else
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ village-evolution-addon/ docker-compose.yml
|
git checkout -f origin/main -- $PATHS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Recreate containers so any new docker-compose volume mounts are applied,
|
# Recreate containers so any new docker-compose volume mounts are applied,
|
||||||
|
|||||||
10
.gitignore
vendored
@@ -16,8 +16,18 @@ addon/build/
|
|||||||
# Environment
|
# Environment
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# docker-compose override, managed by mc-manager at runtime for per-service state
|
||||||
|
# (LEVEL_NAME, etc.) that must survive deploys. Never committed.
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# Local-only backup files (e.g. docker-compose.yml.bak.local)
|
||||||
|
*.bak.local
|
||||||
|
|
||||||
# Server data (Docker volumes are external, but just in case)
|
# Server data (Docker volumes are external, but just in case)
|
||||||
server-data/
|
server-data/
|
||||||
|
|
||||||
# Claude Code
|
# Claude Code
|
||||||
.claude/
|
.claude/
|
||||||
|
|
||||||
|
# Art workspace (edit-in-place mirror; real textures live in each RP)
|
||||||
|
/art/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"minecraft:entity": {
|
"minecraft:entity": {
|
||||||
"description": {
|
"description": {
|
||||||
"identifier": "silverlabs:anthrax_cat",
|
"identifier": "silverlabs:anthrax_cat",
|
||||||
"is_spawnable": false,
|
"is_spawnable": true,
|
||||||
"is_summonable": true,
|
"is_summonable": true,
|
||||||
"is_experimental": false
|
"is_experimental": false
|
||||||
},
|
},
|
||||||
@@ -12,9 +12,13 @@
|
|||||||
"minecraft:interact": {
|
"minecraft:interact": {
|
||||||
"interactions": [
|
"interactions": [
|
||||||
{
|
{
|
||||||
|
"on_interact": {
|
||||||
|
"filters": { "test": "is_family", "subject": "other", "value": "player" },
|
||||||
|
"event": "silverlabs:interacted",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
"interact_text": "action.interact.fortune",
|
"interact_text": "action.interact.fortune",
|
||||||
"play_sounds": "note.bell",
|
"play_sounds": "note.bell"
|
||||||
"event": "silverlabs:interacted"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -39,18 +43,6 @@
|
|||||||
"minecraft:knockback_resistance": {
|
"minecraft:knockback_resistance": {
|
||||||
"value": 1.0
|
"value": 1.0
|
||||||
},
|
},
|
||||||
"minecraft:damage_sensor": {
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"cause": "all",
|
|
||||||
"deals_damage": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cause": "override",
|
|
||||||
"deals_damage": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"minecraft:health": {
|
"minecraft:health": {
|
||||||
"value": 20,
|
"value": 20,
|
||||||
"max": 20
|
"max": 20
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"minecraft:max_stack_size": 16,
|
"minecraft:max_stack_size": 16,
|
||||||
"minecraft:icon": {
|
"minecraft:icon": "anthrax_cat",
|
||||||
"texture": "anthrax_cat"
|
|
||||||
},
|
|
||||||
"minecraft:entity_placer": {
|
"minecraft:entity_placer": {
|
||||||
"entity": "silverlabs:anthrax_cat"
|
"entity": "silverlabs:anthrax_cat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,11 @@
|
|||||||
"minecraft:item": {
|
"minecraft:item": {
|
||||||
"description": {
|
"description": {
|
||||||
"identifier": "silverlabs:heyhe_egg",
|
"identifier": "silverlabs:heyhe_egg",
|
||||||
"category": "Nature"
|
"menu_category": { "category": "nature" }
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"minecraft:max_stack_size": 16,
|
"minecraft:max_stack_size": 16,
|
||||||
"minecraft:icon": {
|
"minecraft:icon": "heyhe_egg",
|
||||||
"texture": "heyhe_egg"
|
|
||||||
},
|
|
||||||
"minecraft:entity_placer": {
|
"minecraft:entity_placer": {
|
||||||
"entity": "silverlabs:heyhe_egg",
|
"entity": "silverlabs:heyhe_egg",
|
||||||
"dispense_on": [
|
"dispense_on": [
|
||||||
|
|||||||
@@ -2,22 +2,38 @@
|
|||||||
"format_version": 2,
|
"format_version": 2,
|
||||||
"header": {
|
"header": {
|
||||||
"name": "Hey Hey Chicken Pet",
|
"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",
|
"uuid": "fc811a53-dbdb-4701-bc63-c3ca1d793c47",
|
||||||
"version": [1, 0, 0],
|
"version": [
|
||||||
"min_engine_version": [1, 21, 0]
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"min_engine_version": [
|
||||||
|
1,
|
||||||
|
21,
|
||||||
|
0
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"type": "data",
|
"type": "data",
|
||||||
"uuid": "f7aae24a-06d8-432b-85dc-359c4fda9dfd",
|
"uuid": "f7aae24a-06d8-432b-85dc-359c4fda9dfd",
|
||||||
"version": [1, 0, 0]
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"uuid": "45ec17df-0d84-456f-b2cc-2d990f96e6d5",
|
"uuid": "45ec17df-0d84-456f-b2cc-2d990f96e6d5",
|
||||||
"version": [1, 0, 0]
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,15 +190,14 @@
|
|||||||
"reselect_targets": true
|
"reselect_targets": true
|
||||||
},
|
},
|
||||||
"minecraft:entity_sensor": {
|
"minecraft:entity_sensor": {
|
||||||
"sensor_range": 500,
|
"subsensors": [
|
||||||
"relative_range": false,
|
{
|
||||||
"minimum_count": 1,
|
"range": [500, 500],
|
||||||
"event_filters": {
|
"minimum_count": 1,
|
||||||
"test": "is_family",
|
"event": "silverlabs:whistle_called",
|
||||||
"subject": "other",
|
"event_filters": { "test": "is_family", "subject": "other", "value": "whistle_signal" }
|
||||||
"value": "whistle_signal"
|
}
|
||||||
},
|
]
|
||||||
"event": "silverlabs:whistle_called"
|
|
||||||
},
|
},
|
||||||
"minecraft:behavior.melee_attack": {
|
"minecraft:behavior.melee_attack": {
|
||||||
"priority": 2,
|
"priority": 2,
|
||||||
|
|||||||
@@ -2,22 +2,38 @@
|
|||||||
"format_version": 2,
|
"format_version": 2,
|
||||||
"header": {
|
"header": {
|
||||||
"name": "Pets 4 Jamie's STARS",
|
"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",
|
"uuid": "7cf924ce-e246-4c8c-998c-f420edb26451",
|
||||||
"version": [1, 0, 0],
|
"version": [
|
||||||
"min_engine_version": [1, 21, 0]
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"min_engine_version": [
|
||||||
|
1,
|
||||||
|
21,
|
||||||
|
0
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"type": "data",
|
"type": "data",
|
||||||
"uuid": "6806e843-c0b4-4a4d-9cb3-d5352e396fe5",
|
"uuid": "6806e843-c0b4-4a4d-9cb3-d5352e396fe5",
|
||||||
"version": [1, 0, 0]
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
|
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
|
||||||
"version": [1, 0, 0]
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,29 @@
|
|||||||
"description": {
|
"description": {
|
||||||
"identifier": "silverlabs:dragon_basket_recipe"
|
"identifier": "silverlabs:dragon_basket_recipe"
|
||||||
},
|
},
|
||||||
"tags": ["crafting_table"],
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"S S",
|
"S S",
|
||||||
"WLW",
|
"WLW",
|
||||||
"SSS"
|
"SSS"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:stick" },
|
"S": {
|
||||||
"W": { "item": "minecraft:wool" },
|
"item": "minecraft:stick"
|
||||||
"L": { "item": "minecraft:leather" }
|
},
|
||||||
|
"W": {
|
||||||
|
"item": "minecraft:wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"item": "silverlabs:dragon_basket"
|
"item": "silverlabs:dragon_basket"
|
||||||
|
|||||||
@@ -4,16 +4,29 @@
|
|||||||
"description": {
|
"description": {
|
||||||
"identifier": "silverlabs:dragon_egg"
|
"identifier": "silverlabs:dragon_egg"
|
||||||
},
|
},
|
||||||
"tags": ["crafting_table"],
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
" B ",
|
" B ",
|
||||||
"AGA",
|
"AGA",
|
||||||
" A "
|
" A "
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"B": { "item": "minecraft:blaze_powder" },
|
"B": {
|
||||||
"A": { "item": "minecraft:amethyst_shard" },
|
"item": "minecraft:blaze_powder"
|
||||||
"G": { "item": "minecraft:gold_ingot" }
|
},
|
||||||
|
"A": {
|
||||||
|
"item": "minecraft:amethyst_shard"
|
||||||
|
},
|
||||||
|
"G": {
|
||||||
|
"item": "minecraft:gold_ingot"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"item": "silverlabs:dragon_egg",
|
"item": "silverlabs:dragon_egg",
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shapeless": {
|
"minecraft:recipe_shapeless": {
|
||||||
"description": { "identifier": "silverlabs:dragon_egg_crimson_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_egg_crimson_recipe"
|
||||||
"ingredients": [
|
},
|
||||||
{ "item": "silverlabs:dragon_egg" },
|
"unlock": [
|
||||||
{ "item": "minecraft:red_dye" }
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"result": { "item": "silverlabs:dragon_egg_crimson" }
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "silverlabs:dragon_egg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:red_dye"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_egg_crimson"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shapeless": {
|
"minecraft:recipe_shapeless": {
|
||||||
"description": { "identifier": "silverlabs:dragon_egg_jungle_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_egg_jungle_recipe"
|
||||||
"ingredients": [
|
},
|
||||||
{ "item": "silverlabs:dragon_egg" },
|
"unlock": [
|
||||||
{ "item": "minecraft:lime_dye" }
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"result": { "item": "silverlabs:dragon_egg_jungle" }
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "silverlabs:dragon_egg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:lime_dye"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_egg_jungle"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shapeless": {
|
"minecraft:recipe_shapeless": {
|
||||||
"description": { "identifier": "silverlabs:dragon_egg_oak_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_egg_oak_recipe"
|
||||||
"ingredients": [
|
},
|
||||||
{ "item": "silverlabs:dragon_egg" },
|
"unlock": [
|
||||||
{ "item": "minecraft:white_dye" }
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"result": { "item": "silverlabs:dragon_egg_oak" }
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "silverlabs:dragon_egg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:white_dye"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_egg_oak"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shapeless": {
|
"minecraft:recipe_shapeless": {
|
||||||
"description": { "identifier": "silverlabs:dragon_egg_obsidian_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_egg_obsidian_recipe"
|
||||||
"ingredients": [
|
},
|
||||||
{ "item": "silverlabs:dragon_egg" },
|
"unlock": [
|
||||||
{ "item": "minecraft:black_dye" }
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"result": { "item": "silverlabs:dragon_egg_obsidian" }
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "silverlabs:dragon_egg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:black_dye"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_egg_obsidian"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shapeless": {
|
"minecraft:recipe_shapeless": {
|
||||||
"description": { "identifier": "silverlabs:dragon_egg_spruce_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_egg_spruce_recipe"
|
||||||
"ingredients": [
|
},
|
||||||
{ "item": "silverlabs:dragon_egg" },
|
"unlock": [
|
||||||
{ "item": "minecraft:brown_dye" }
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"result": { "item": "silverlabs:dragon_egg_spruce" }
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "silverlabs:dragon_egg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:brown_dye"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_egg_spruce"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shapeless": {
|
"minecraft:recipe_shapeless": {
|
||||||
"description": { "identifier": "silverlabs:dragon_egg_warped_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_egg_warped_recipe"
|
||||||
"ingredients": [
|
},
|
||||||
{ "item": "silverlabs:dragon_egg" },
|
"unlock": [
|
||||||
{ "item": "minecraft:cyan_dye" }
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"result": { "item": "silverlabs:dragon_egg_warped" }
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"ingredients": [
|
||||||
|
{
|
||||||
|
"item": "silverlabs:dragon_egg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "minecraft:cyan_dye"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_egg_warped"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shaped": {
|
"minecraft:recipe_shaped": {
|
||||||
"description": { "identifier": "silverlabs:dragon_nest_crimson_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_nest_crimson_recipe"
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"S S",
|
"S S",
|
||||||
"WLW",
|
"WLW",
|
||||||
"PPP"
|
"PPP"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:stick" },
|
"S": {
|
||||||
"W": { "item": "minecraft:red_wool" },
|
"item": "minecraft:stick"
|
||||||
"L": { "item": "minecraft:leather" },
|
},
|
||||||
"P": { "item": "minecraft:crimson_planks" }
|
"W": {
|
||||||
|
"item": "minecraft:red_wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
"item": "minecraft:crimson_planks"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": { "item": "silverlabs:dragon_nest_crimson" }
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_nest_crimson"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shaped": {
|
"minecraft:recipe_shaped": {
|
||||||
"description": { "identifier": "silverlabs:dragon_nest_jungle_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_nest_jungle_recipe"
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"S S",
|
"S S",
|
||||||
"WLW",
|
"WLW",
|
||||||
"PPP"
|
"PPP"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:stick" },
|
"S": {
|
||||||
"W": { "item": "minecraft:lime_wool" },
|
"item": "minecraft:stick"
|
||||||
"L": { "item": "minecraft:leather" },
|
},
|
||||||
"P": { "item": "minecraft:jungle_planks" }
|
"W": {
|
||||||
|
"item": "minecraft:lime_wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
"item": "minecraft:jungle_planks"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": { "item": "silverlabs:dragon_nest_jungle" }
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_nest_jungle"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shaped": {
|
"minecraft:recipe_shaped": {
|
||||||
"description": { "identifier": "silverlabs:dragon_nest_oak_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_nest_oak_recipe"
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"S S",
|
"S S",
|
||||||
"WLW",
|
"WLW",
|
||||||
"PPP"
|
"PPP"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:stick" },
|
"S": {
|
||||||
"W": { "item": "minecraft:white_wool" },
|
"item": "minecraft:stick"
|
||||||
"L": { "item": "minecraft:leather" },
|
},
|
||||||
"P": { "item": "minecraft:oak_planks" }
|
"W": {
|
||||||
|
"item": "minecraft:white_wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
"item": "minecraft:oak_planks"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": { "item": "silverlabs:dragon_nest_oak" }
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_nest_oak"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,35 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shaped": {
|
"minecraft:recipe_shaped": {
|
||||||
"description": { "identifier": "silverlabs:dragon_nest_obsidian_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_nest_obsidian_recipe"
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"O O",
|
"O O",
|
||||||
"WLW",
|
"WLW",
|
||||||
"OOO"
|
"OOO"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"O": { "item": "minecraft:obsidian" },
|
"O": {
|
||||||
"W": { "item": "minecraft:black_wool" },
|
"item": "minecraft:obsidian"
|
||||||
"L": { "item": "minecraft:leather" }
|
},
|
||||||
|
"W": {
|
||||||
|
"item": "minecraft:black_wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": { "item": "silverlabs:dragon_nest_obsidian" }
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_nest_obsidian"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shaped": {
|
"minecraft:recipe_shaped": {
|
||||||
"description": { "identifier": "silverlabs:dragon_nest_spruce_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_nest_spruce_recipe"
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"S S",
|
"S S",
|
||||||
"WLW",
|
"WLW",
|
||||||
"PPP"
|
"PPP"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:stick" },
|
"S": {
|
||||||
"W": { "item": "minecraft:brown_wool" },
|
"item": "minecraft:stick"
|
||||||
"L": { "item": "minecraft:leather" },
|
},
|
||||||
"P": { "item": "minecraft:spruce_planks" }
|
"W": {
|
||||||
|
"item": "minecraft:brown_wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
"item": "minecraft:spruce_planks"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": { "item": "silverlabs:dragon_nest_spruce" }
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_nest_spruce"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,38 @@
|
|||||||
{
|
{
|
||||||
"format_version": "1.21.0",
|
"format_version": "1.21.0",
|
||||||
"minecraft:recipe_shaped": {
|
"minecraft:recipe_shaped": {
|
||||||
"description": { "identifier": "silverlabs:dragon_nest_warped_recipe" },
|
"description": {
|
||||||
"tags": ["crafting_table"],
|
"identifier": "silverlabs:dragon_nest_warped_recipe"
|
||||||
|
},
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
"S S",
|
"S S",
|
||||||
"WLW",
|
"WLW",
|
||||||
"PPP"
|
"PPP"
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:stick" },
|
"S": {
|
||||||
"W": { "item": "minecraft:cyan_wool" },
|
"item": "minecraft:stick"
|
||||||
"L": { "item": "minecraft:leather" },
|
},
|
||||||
"P": { "item": "minecraft:warped_planks" }
|
"W": {
|
||||||
|
"item": "minecraft:cyan_wool"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
},
|
||||||
|
"P": {
|
||||||
|
"item": "minecraft:warped_planks"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": { "item": "silverlabs:dragon_nest_warped" }
|
"result": {
|
||||||
|
"item": "silverlabs:dragon_nest_warped"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,26 @@
|
|||||||
"description": {
|
"description": {
|
||||||
"identifier": "silverlabs:dragon_toy"
|
"identifier": "silverlabs:dragon_toy"
|
||||||
},
|
},
|
||||||
"tags": ["crafting_table"],
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
"pattern": [
|
"pattern": [
|
||||||
" S ",
|
" S ",
|
||||||
"SLS",
|
"SLS",
|
||||||
" S "
|
" S "
|
||||||
],
|
],
|
||||||
"key": {
|
"key": {
|
||||||
"S": { "item": "minecraft:string" },
|
"S": {
|
||||||
"L": { "item": "minecraft:leather" }
|
"item": "minecraft:string"
|
||||||
|
},
|
||||||
|
"L": {
|
||||||
|
"item": "minecraft:leather"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"item": "silverlabs:dragon_toy",
|
"item": "silverlabs:dragon_toy",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
{ "fire_breathing": "query.property('silverlabs:firing')" },
|
{ "fire_breathing": "query.property('silverlabs:firing')" },
|
||||||
{ "flying": "!query.is_on_ground && query.property('silverlabs:growth_stage') == 2" },
|
{ "flying": "!query.is_on_ground && query.property('silverlabs:growth_stage') == 2" },
|
||||||
{ "walking": "query.modified_move_speed > 0.1" },
|
{ "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" },
|
{ "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" },
|
{ "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" },
|
{ "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"],
|
"animations": ["sit"],
|
||||||
"transitions": [
|
"transitions": [
|
||||||
{ "idle": "!query.is_sitting" },
|
{ "idle": "!query.is_sitting" },
|
||||||
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" }
|
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"sleeping": {
|
"sleeping": {
|
||||||
"animations": ["sleep"],
|
"animations": ["sleep"],
|
||||||
"transitions": [
|
"transitions": [
|
||||||
{ "idle": "!query.is_sitting" },
|
{ "idle": "!query.is_sitting" },
|
||||||
{ "sitting": "query.is_sitting && (query.day_light_level >= 4)" }
|
{ "sitting": "query.is_sitting && (query.time_of_day <= 0.5)" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"flying": {
|
"flying": {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"particle_effects": {
|
"particle_effects": {
|
||||||
"beacon_beam": "minecraft:endrod",
|
"beacon_beam": "minecraft:endrod",
|
||||||
"mood_happy": "minecraft:villager_happy",
|
"mood_happy": "minecraft:villager_happy",
|
||||||
"mood_needy": "minecraft:large_smoke"
|
"mood_needy": "minecraft:basic_smoke_particle"
|
||||||
},
|
},
|
||||||
"spawn_egg": {
|
"spawn_egg": {
|
||||||
"base_color": "#7B2FBE",
|
"base_color": "#7B2FBE",
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
"name": "Pets 4 Jamie's STARS Resources",
|
"name": "Pets 4 Jamie's STARS Resources",
|
||||||
"description": "Textures, models and animations for Pets 4 Jamie's STARS",
|
"description": "Textures, models and animations for Pets 4 Jamie's STARS",
|
||||||
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
|
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
|
||||||
"version": [1, 0, 0],
|
"version": [1, 0, 1],
|
||||||
"min_engine_version": [1, 21, 0]
|
"min_engine_version": [1, 21, 0]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"type": "resources",
|
"type": "resources",
|
||||||
"uuid": "6adc20cd-25a5-4ca5-aa12-f5e43ee9ea22",
|
"uuid": "6adc20cd-25a5-4ca5-aa12-f5e43ee9ea22",
|
||||||
"version": [1, 0, 0]
|
"version": [1, 0, 1]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:hammock_cloth"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 0.3
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 0.5
|
||||||
|
},
|
||||||
|
"minecraft:map_color": "#B43C37",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "hammock_cloth",
|
||||||
|
"render_method": "alpha_test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:collision_box": {
|
||||||
|
"origin": [-8, 0, -8],
|
||||||
|
"size": [16, 1, 16]
|
||||||
|
},
|
||||||
|
"minecraft:selection_box": {
|
||||||
|
"origin": [-8, 0, -8],
|
||||||
|
"size": [16, 4, 16]
|
||||||
|
},
|
||||||
|
"minecraft:geometry": "geometry.silverlabs.hammock_slab",
|
||||||
|
"minecraft:light_dampening": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:tent_canvas"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 0.4
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1.0
|
||||||
|
},
|
||||||
|
"minecraft:map_color": "#547A4E",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "tent_canvas",
|
||||||
|
"render_method": "alpha_test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:light_dampening": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:tent_panel_l",
|
||||||
|
"traits": {
|
||||||
|
"minecraft:placement_direction": {
|
||||||
|
"enabled_states": ["minecraft:cardinal_direction"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0.4 },
|
||||||
|
"minecraft:destructible_by_explosion": { "explosion_resistance": 1.0 },
|
||||||
|
"minecraft:map_color": "#547A4E",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "tent_canvas",
|
||||||
|
"render_method": "alpha_test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:light_dampening": 1,
|
||||||
|
"minecraft:geometry": "geometry.silverlabs.tent_panel_l"
|
||||||
|
},
|
||||||
|
"permutations": [
|
||||||
|
{
|
||||||
|
"condition": "query.block_state('minecraft:cardinal_direction') == 'south'",
|
||||||
|
"components": {
|
||||||
|
"minecraft:transformation": { "rotation": [0, 180, 0] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "query.block_state('minecraft:cardinal_direction') == 'east'",
|
||||||
|
"components": {
|
||||||
|
"minecraft:transformation": { "rotation": [0, 90, 0] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "query.block_state('minecraft:cardinal_direction') == 'west'",
|
||||||
|
"components": {
|
||||||
|
"minecraft:transformation": { "rotation": [0, 270, 0] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:tent_panel_r",
|
||||||
|
"traits": {
|
||||||
|
"minecraft:placement_direction": {
|
||||||
|
"enabled_states": ["minecraft:cardinal_direction"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": { "seconds_to_destroy": 0.4 },
|
||||||
|
"minecraft:destructible_by_explosion": { "explosion_resistance": 1.0 },
|
||||||
|
"minecraft:map_color": "#547A4E",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "tent_canvas",
|
||||||
|
"render_method": "alpha_test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:light_dampening": 1,
|
||||||
|
"minecraft:geometry": "geometry.silverlabs.tent_panel_r"
|
||||||
|
},
|
||||||
|
"permutations": [
|
||||||
|
{
|
||||||
|
"condition": "query.block_state('minecraft:cardinal_direction') == 'south'",
|
||||||
|
"components": {
|
||||||
|
"minecraft:transformation": { "rotation": [0, 180, 0] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "query.block_state('minecraft:cardinal_direction') == 'east'",
|
||||||
|
"components": {
|
||||||
|
"minecraft:transformation": { "rotation": [0, 90, 0] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "query.block_state('minecraft:cardinal_direction') == 'west'",
|
||||||
|
"components": {
|
||||||
|
"minecraft:transformation": { "rotation": [0, 270, 0] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:item": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:hammock",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "equipment",
|
||||||
|
"group": "itemGroup.name.miscellaneous"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:max_stack_size": 16,
|
||||||
|
"minecraft:icon": "hammock_item",
|
||||||
|
"minecraft:hand_equipped": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
camping-supplies-addon/camping_supplies_BP/items/tent.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:item": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:tent",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "equipment",
|
||||||
|
"group": "itemGroup.name.miscellaneous"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:max_stack_size": 16,
|
||||||
|
"minecraft:icon": "tent_item",
|
||||||
|
"minecraft:hand_equipped": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
camping-supplies-addon/camping_supplies_BP/manifest.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"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
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"uuid": "f306e1d8-3c13-4554-9715-4799ce6d41d8",
|
||||||
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "script",
|
||||||
|
"language": "javascript",
|
||||||
|
"uuid": "1e496657-0c83-4707-a1e8-29b757dcce79",
|
||||||
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"entry": "scripts/main.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server",
|
||||||
|
"version": "1.17.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
|
||||||
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
camping-supplies-addon/camping_supplies_BP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:hammock_recipe"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pattern": [
|
||||||
|
"T T",
|
||||||
|
"WWW",
|
||||||
|
"T T"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"W": {
|
||||||
|
"item": "minecraft:white_wool"
|
||||||
|
},
|
||||||
|
"T": {
|
||||||
|
"item": "minecraft:string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:hammock",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
camping-supplies-addon/camping_supplies_BP/recipes/tent.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:tent_recipe"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"crafting_table"
|
||||||
|
],
|
||||||
|
"unlock": [
|
||||||
|
{
|
||||||
|
"item": "minecraft:crafting_table"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pattern": [
|
||||||
|
" W ",
|
||||||
|
"WWW",
|
||||||
|
"S S"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"W": {
|
||||||
|
"item": "minecraft:white_wool"
|
||||||
|
},
|
||||||
|
"S": {
|
||||||
|
"item": "minecraft:stick"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:tent",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
820
camping-supplies-addon/camping_supplies_BP/scripts/main.js
Normal file
@@ -0,0 +1,820 @@
|
|||||||
|
import { world, system, ItemStack } from "@minecraft/server";
|
||||||
|
|
||||||
|
// ─── Constants ──────────────────────────────────────────────
|
||||||
|
const TENT_ITEM = "silverlabs:tent";
|
||||||
|
const HAMMOCK_ITEM = "silverlabs:hammock";
|
||||||
|
const TENT_BLOCK = "silverlabs:tent_canvas"; // legacy cube — kept so old worlds load cleanly
|
||||||
|
const TENT_PANEL_L = "silverlabs:tent_panel_l";
|
||||||
|
const TENT_PANEL_R = "silverlabs:tent_panel_r";
|
||||||
|
const TENT_BLOCK_IDS = [TENT_BLOCK, TENT_PANEL_L, TENT_PANEL_R];
|
||||||
|
const HAMMOCK_BLOCK = "silverlabs:hammock_cloth";
|
||||||
|
const STATE_PROP = "camping_state_v1";
|
||||||
|
const HAMMOCK_TAG = "camping_hammock";
|
||||||
|
const TENT_REST_PROP = "camping_tent_rest"; // per-player: "{x,y,z,dim,startTick}"
|
||||||
|
const SLEEP_TICK_INTERVAL = 20; // run sleep loop every 1s
|
||||||
|
const NIGHT_START = 12500;
|
||||||
|
const NIGHT_END = 23500;
|
||||||
|
|
||||||
|
// ─── State ──────────────────────────────────────────────────
|
||||||
|
// tents: key = "ox,oy,oz,dim" -> { ownerId, ownerName, cells: [[x,y,z]...] }
|
||||||
|
// hammocks: key = "ax,ay,az->bx,by,bz,dim" -> { ownerId, ownerName, anchorA, anchorB, cells }
|
||||||
|
let state = { tents: {}, hammocks: {} };
|
||||||
|
|
||||||
|
function loadState() {
|
||||||
|
try {
|
||||||
|
const raw = world.getDynamicProperty(STATE_PROP);
|
||||||
|
if (raw && typeof raw === "string") {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
state = {
|
||||||
|
tents: parsed.tents || {},
|
||||||
|
hammocks: parsed.hammocks || {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Camping] state load failed: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveState() {
|
||||||
|
try {
|
||||||
|
world.setDynamicProperty(STATE_PROP, JSON.stringify(state));
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Camping] state save failed: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyOf(x, y, z, dimId) {
|
||||||
|
return `${x},${y},${z},${dimId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Orientation helpers ────────────────────────────────────
|
||||||
|
function cardinalFacing(yaw) {
|
||||||
|
let y = yaw;
|
||||||
|
while (y > 180) y -= 360;
|
||||||
|
while (y < -180) y += 360;
|
||||||
|
if (y >= -45 && y < 45) return "south";
|
||||||
|
if (y >= 45 && y < 135) return "west";
|
||||||
|
if (y >= -135 && y < -45) return "east";
|
||||||
|
return "north";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map our placement facing → block-state cardinal_direction the panel rotates to.
|
||||||
|
// The geometry's "default" (cardinal_direction = "north") has the slope's outer
|
||||||
|
// edge on -X and inner apex on +X with the depth running along Z. When the
|
||||||
|
// player faces north, that aligns with the world. When they face elsewhere,
|
||||||
|
// the placement_direction trait + permutations rotate the model to match.
|
||||||
|
function blockFacingFor(playerFacing) {
|
||||||
|
return playerFacing; // 1:1 — placement_direction handles the rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
function vecsForFacing(facing) {
|
||||||
|
switch (facing) {
|
||||||
|
case "north": return { fx: 0, fz: -1, rx: 1, rz: 0 };
|
||||||
|
case "south": return { fx: 0, fz: 1, rx: -1, rz: 0 };
|
||||||
|
case "east": return { fx: 1, fz: 0, rx: 0, rz: -1 };
|
||||||
|
case "west": return { fx: -1, fz: 0, rx: 0, rz: 1 };
|
||||||
|
}
|
||||||
|
return { fx: 0, fz: 1, rx: -1, rz: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Inventory helpers ──────────────────────────────────────
|
||||||
|
function consumeOneOfType(player, typeId) {
|
||||||
|
const inv = player.getComponent("inventory")?.container;
|
||||||
|
if (!inv) return false;
|
||||||
|
const slot = player.selectedSlotIndex;
|
||||||
|
const item = inv.getItem(slot);
|
||||||
|
if (item && item.typeId === typeId) {
|
||||||
|
if (item.amount > 1) {
|
||||||
|
item.amount -= 1;
|
||||||
|
inv.setItem(slot, item);
|
||||||
|
} else {
|
||||||
|
inv.setItem(slot, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Tent placement (2×3 footprint, ridge-tunnel shape) ─────
|
||||||
|
function tryPlaceTent(player) {
|
||||||
|
const dim = player.dimension;
|
||||||
|
const facing = cardinalFacing(player.getRotation().y);
|
||||||
|
const { fx, fz, rx, rz } = vecsForFacing(facing);
|
||||||
|
// Use precise player position; floor X/Z but scan Y downward to find the actual
|
||||||
|
// standing surface. player.location.y may be fractionally above the block you're
|
||||||
|
// on (e.g. 87.01), so floor() alone is reliable, but if the player is in the
|
||||||
|
// air (jumping / on a slab / flying) we want to project them down to solid ground
|
||||||
|
// so the tent doesn't try to sit on empty space.
|
||||||
|
const feetX = Math.floor(player.location.x);
|
||||||
|
const feetZ = Math.floor(player.location.z);
|
||||||
|
let feetY = Math.floor(player.location.y);
|
||||||
|
// If the block at feet level is solid (player inside a block, e.g. standing in
|
||||||
|
// tall grass that rounded up), step up one.
|
||||||
|
const feetBlock = dim.getBlock({ x: feetX, y: feetY, z: feetZ });
|
||||||
|
if (feetBlock && !feetBlock.isAir && !feetBlock.isLiquid) feetY += 1;
|
||||||
|
// If the block below is air (mid-jump / airborne), project down to ground.
|
||||||
|
for (let probe = 0; probe < 4; probe++) {
|
||||||
|
const below = dim.getBlock({ x: feetX, y: feetY - 1, z: feetZ });
|
||||||
|
if (below && !below.isAir && !below.isLiquid) break;
|
||||||
|
feetY -= 1;
|
||||||
|
}
|
||||||
|
const ox = feetX + fx;
|
||||||
|
const oy = feetY;
|
||||||
|
const oz = feetZ + fz;
|
||||||
|
|
||||||
|
const groundCells = [];
|
||||||
|
const clearCells = [];
|
||||||
|
for (let l = 0; l < 3; l++) {
|
||||||
|
for (let w = 0; w < 2; w++) {
|
||||||
|
const cx = ox + l * fx + w * rx;
|
||||||
|
const cz = oz + l * fz + w * rz;
|
||||||
|
groundCells.push({ x: cx, y: oy - 1, z: cz });
|
||||||
|
for (let h = 0; h <= 1; h++) clearCells.push({ x: cx, y: oy + h, z: cz });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const g of groundCells) {
|
||||||
|
const b = dim.getBlock(g);
|
||||||
|
if (!b || b.isAir || b.isLiquid) {
|
||||||
|
const seen = b ? b.typeId : "unloaded";
|
||||||
|
player.sendMessage(`§c[Camping] §7Ground at §f${g.x},${g.y},${g.z}§7 is §f${seen}§7 — need solid ground there.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const c of clearCells) {
|
||||||
|
const b = dim.getBlock(c);
|
||||||
|
if (!b) {
|
||||||
|
player.sendMessage(`§c[Camping] §7Can't reach §f${c.x},${c.y},${c.z}§7 (chunk unloaded).`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!b.isAir && !b.isLiquid) {
|
||||||
|
player.sendMessage(`§c[Camping] §7Space at §f${c.x},${c.y},${c.z}§7 is blocked by §f${b.typeId}§7.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A-frame layout: 3 long × 2 wide, single block tall. Each cross-section is a
|
||||||
|
// pair of slope panels meeting at the apex on the seam between the two columns.
|
||||||
|
// Bedrock renders the panel geometry mirrored across the block's local X axis
|
||||||
|
// relative to a literal read of the .geo.json, so panel_r goes in the player's
|
||||||
|
// column and panel_l goes one step right to land /\ instead of \/.
|
||||||
|
const blockFacing = blockFacingFor(facing);
|
||||||
|
const canvasCells = [];
|
||||||
|
for (let l = 0; l < 3; l++) {
|
||||||
|
canvasCells.push({
|
||||||
|
x: ox + l * fx,
|
||||||
|
y: oy,
|
||||||
|
z: oz + l * fz,
|
||||||
|
block: TENT_PANEL_R,
|
||||||
|
});
|
||||||
|
canvasCells.push({
|
||||||
|
x: ox + l * fx + rx,
|
||||||
|
y: oy,
|
||||||
|
z: oz + l * fz + rz,
|
||||||
|
block: TENT_PANEL_L,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const c of canvasCells) {
|
||||||
|
try {
|
||||||
|
dim.runCommand(
|
||||||
|
`setblock ${c.x} ${c.y} ${c.z} ${c.block} ["minecraft:cardinal_direction"="${blockFacing}"]`
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = keyOf(ox, oy, oz, dim.id);
|
||||||
|
state.tents[key] = {
|
||||||
|
ownerId: player.id,
|
||||||
|
ownerName: player.name,
|
||||||
|
facing,
|
||||||
|
cells: canvasCells.map((c) => [c.x, c.y, c.z]),
|
||||||
|
};
|
||||||
|
saveState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Hammock placement ──────────────────────────────────────
|
||||||
|
function isPostBlock(block) {
|
||||||
|
if (!block) return false;
|
||||||
|
const id = block.typeId;
|
||||||
|
return (
|
||||||
|
id.endsWith("_fence") ||
|
||||||
|
id.endsWith("_log") ||
|
||||||
|
id.endsWith("_wood") ||
|
||||||
|
id.includes("stripped_") ||
|
||||||
|
id.endsWith("_wall")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPartnerPost(dim, anchor) {
|
||||||
|
const candidates = [];
|
||||||
|
for (let dx = -6; dx <= 6; dx++) {
|
||||||
|
for (let dz = -6; dz <= 6; dz++) {
|
||||||
|
if (dx === 0 && dz === 0) continue;
|
||||||
|
const aligned = dx === 0 || dz === 0 || Math.abs(dx) === Math.abs(dz);
|
||||||
|
if (!aligned) continue;
|
||||||
|
const dist = Math.max(Math.abs(dx), Math.abs(dz));
|
||||||
|
if (dist < 3 || dist > 6) continue;
|
||||||
|
for (let dy = -1; dy <= 1; dy++) {
|
||||||
|
const pos = { x: anchor.x + dx, y: anchor.y + dy, z: anchor.z + dz };
|
||||||
|
let blk;
|
||||||
|
try { blk = dim.getBlock(pos); } catch (_) { continue; }
|
||||||
|
if (!blk || !isPostBlock(blk)) continue;
|
||||||
|
candidates.push({ pos, dist, dy });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (candidates.length === 0) return null;
|
||||||
|
candidates.sort((a, b) => (Math.abs(a.dy) - Math.abs(b.dy)) || (a.dist - b.dist));
|
||||||
|
return candidates[0].pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeHammockCells(a, b) {
|
||||||
|
const dx = b.x - a.x;
|
||||||
|
const dy = b.y - a.y;
|
||||||
|
const dz = b.z - a.z;
|
||||||
|
const steps = Math.max(Math.abs(dx), Math.abs(dz));
|
||||||
|
const cells = [];
|
||||||
|
for (let t = 1; t < steps; t++) {
|
||||||
|
const cx = a.x + Math.round((dx * t) / steps);
|
||||||
|
const cz = a.z + Math.round((dz * t) / steps);
|
||||||
|
let cy = a.y + Math.round((dy * t) / steps);
|
||||||
|
const rel = t / steps;
|
||||||
|
if (steps >= 4 && rel > 0.25 && rel < 0.75) cy -= 1;
|
||||||
|
cells.push({ x: cx, y: cy, z: cz });
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryPlaceHammock(player, anchorBlock) {
|
||||||
|
const dim = player.dimension;
|
||||||
|
const a = {
|
||||||
|
x: anchorBlock.location.x,
|
||||||
|
y: anchorBlock.location.y,
|
||||||
|
z: anchorBlock.location.z,
|
||||||
|
};
|
||||||
|
const b = findPartnerPost(dim, a);
|
||||||
|
if (!b) {
|
||||||
|
player.sendMessage("§c[Camping] §7Need a second post 3–6 blocks away (straight line or diagonal, ±1 block in height).");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const cells = computeHammockCells(a, b);
|
||||||
|
for (const c of cells) {
|
||||||
|
let blk;
|
||||||
|
try { blk = dim.getBlock(c); } catch (_) { return false; }
|
||||||
|
if (!blk || (!blk.isAir && !blk.isLiquid)) {
|
||||||
|
player.sendMessage("§c[Camping] §7The space between the posts isn't clear.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const c of cells) {
|
||||||
|
try { dim.runCommand(`setblock ${c.x} ${c.y} ${c.z} ${HAMMOCK_BLOCK}`); } catch (_) {}
|
||||||
|
}
|
||||||
|
const key = `${a.x},${a.y},${a.z}->${b.x},${b.y},${b.z},${dim.id}`;
|
||||||
|
state.hammocks[key] = {
|
||||||
|
ownerId: player.id,
|
||||||
|
ownerName: player.name,
|
||||||
|
anchorA: [a.x, a.y, a.z],
|
||||||
|
anchorB: [b.x, b.y, b.z],
|
||||||
|
cells: cells.map((c) => [c.x, c.y, c.z]),
|
||||||
|
};
|
||||||
|
saveState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Item use handler ───────────────────────────────────────
|
||||||
|
world.afterEvents.itemUse.subscribe((event) => {
|
||||||
|
const player = event.source;
|
||||||
|
const stack = event.itemStack;
|
||||||
|
if (!stack || !player) return;
|
||||||
|
if (stack.typeId === TENT_ITEM) {
|
||||||
|
system.run(() => {
|
||||||
|
if (tryPlaceTent(player)) {
|
||||||
|
consumeOneOfType(player, TENT_ITEM);
|
||||||
|
player.sendMessage("§a[Camping] §7Tent pitched. Right-click the canvas to rest until dawn.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (stack.typeId === HAMMOCK_ITEM) {
|
||||||
|
system.run(() => {
|
||||||
|
const looking = player.getBlockFromViewDirection({ maxDistance: 6 });
|
||||||
|
const block = looking?.block;
|
||||||
|
if (!block || !isPostBlock(block)) {
|
||||||
|
player.sendMessage("§c[Camping] §7Aim at a fence, log, or wooden post to anchor the hammock.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tryPlaceHammock(player, block)) {
|
||||||
|
consumeOneOfType(player, HAMMOCK_ITEM);
|
||||||
|
player.sendMessage("§a[Camping] §7Hammock strung. Right-click the cloth to climb in.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Interact: tent rest + hammock toggle ───────────────────
|
||||||
|
try {
|
||||||
|
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (!block) return;
|
||||||
|
if (TENT_BLOCK_IDS.includes(block.typeId)) {
|
||||||
|
event.cancel = true;
|
||||||
|
const player = event.player;
|
||||||
|
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||||
|
const dimId = block.dimension.id;
|
||||||
|
system.run(() => enterTentRest(player, loc, dimId));
|
||||||
|
} else if (block.typeId === HAMMOCK_BLOCK) {
|
||||||
|
event.cancel = true;
|
||||||
|
const player = event.player;
|
||||||
|
const loc = block.location;
|
||||||
|
system.run(() => toggleHammock(player, loc));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[Camping] playerInteractWithBlock unavailable: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Tent rest: vote-skip with mixed bed + tent sleepers ────────
|
||||||
|
// Tracks which players are currently "resting" in a tent. A player counts as a
|
||||||
|
// tent sleeper as long as they stay near the panel they interacted with and
|
||||||
|
// don't sneak/move/disconnect/take damage. Vanilla bed sleepers are detected
|
||||||
|
// via player.isSleeping (true while a player is in a real bed). We compare the
|
||||||
|
// combined count against the world's playersSleepingPercentage gamerule and
|
||||||
|
// skip the night when the threshold is crossed.
|
||||||
|
|
||||||
|
const tentRest = new Map(); // playerId → { x, y, z, dimId, startTick }
|
||||||
|
|
||||||
|
function isNight(tod) {
|
||||||
|
return tod >= NIGHT_START && tod <= NIGHT_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterTentRest(player, loc, dimId) {
|
||||||
|
const tod = world.getTimeOfDay();
|
||||||
|
if (!isNight(tod)) {
|
||||||
|
player.sendMessage("§e[Camping] §7It's still daylight — nothing to sleep off.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tentRest.has(player.id)) {
|
||||||
|
leaveTentRest(player, "§7[Camping] You stop resting.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tentRest.set(player.id, {
|
||||||
|
x: loc.x,
|
||||||
|
y: loc.y,
|
||||||
|
z: loc.z,
|
||||||
|
dimId,
|
||||||
|
startTick: system.currentTick,
|
||||||
|
});
|
||||||
|
// Cinematic fade so it reads like sleep instead of a status message.
|
||||||
|
try {
|
||||||
|
player.runCommand("camera @s fade time 0.4 1.5 0.6 color 0 0 0");
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
player.onScreenDisplay.setTitle("§7Resting…", {
|
||||||
|
fadeInDuration: 8,
|
||||||
|
stayDuration: 60,
|
||||||
|
fadeOutDuration: 12,
|
||||||
|
subtitle: "§8Move, sneak, or take damage to wake.",
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
reportSleepProgress(player, /*onEnter*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function leaveTentRest(player, msg) {
|
||||||
|
if (!tentRest.delete(player.id)) return;
|
||||||
|
if (msg && player) {
|
||||||
|
try { player.sendMessage(msg); } catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countSleepers() {
|
||||||
|
let bed = 0;
|
||||||
|
let tent = 0;
|
||||||
|
let online = 0;
|
||||||
|
for (const p of world.getAllPlayers()) {
|
||||||
|
online++;
|
||||||
|
// Vanilla bed sleep: Player.isSleeping is true while they're in a bed.
|
||||||
|
// Available since @minecraft/server 1.10+; guarded for safety.
|
||||||
|
let sleeping = false;
|
||||||
|
try { sleeping = !!p.isSleeping; } catch (_) {}
|
||||||
|
if (sleeping) bed++;
|
||||||
|
else if (tentRest.has(p.id)) tent++;
|
||||||
|
}
|
||||||
|
return { bed, tent, online, resting: bed + tent };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSleepThreshold() {
|
||||||
|
// playersSleepingPercentage is a percentage 0-100. 0 means any one player
|
||||||
|
// can skip night (vanilla quirk); 100 means everyone must sleep.
|
||||||
|
let pct = 100;
|
||||||
|
try {
|
||||||
|
const v = world.gameRules?.playersSleepingPercentage;
|
||||||
|
if (typeof v === "number") pct = v;
|
||||||
|
} catch (_) {}
|
||||||
|
// 0 in vanilla means "one is enough" — preserve that intent.
|
||||||
|
if (pct <= 0) return 1;
|
||||||
|
return pct;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredSleepers(online, pct) {
|
||||||
|
// Standard vanilla rounding: ceil(online * pct / 100), min 1.
|
||||||
|
return Math.max(1, Math.ceil((online * pct) / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportSleepProgress(targetPlayer, onEnter = false) {
|
||||||
|
const { bed, tent, online, resting } = countSleepers();
|
||||||
|
const pct = getSleepThreshold();
|
||||||
|
const need = requiredSleepers(online, pct);
|
||||||
|
const remaining = Math.max(0, need - resting);
|
||||||
|
const msg = onEnter
|
||||||
|
? `§a[Camping] §7You settle in. §f${resting}§7/§f${need}§7 resting (§f${tent}§7 tent + §f${bed}§7 bed)${remaining ? `. Need §f${remaining}§7 more.` : `.`}`
|
||||||
|
: `§7[Sleep] §f${resting}§7/§f${need}§7 resting (§f${tent}§7 tent + §f${bed}§7 bed)`;
|
||||||
|
if (onEnter && targetPlayer) {
|
||||||
|
try { targetPlayer.sendMessage(msg); } catch (_) {}
|
||||||
|
} else {
|
||||||
|
// Broadcast a subtle update to everyone currently resting.
|
||||||
|
for (const p of world.getAllPlayers()) {
|
||||||
|
if (tentRest.has(p.id) || (() => { try { return !!p.isSleeping; } catch (_) { return false; } })()) {
|
||||||
|
try { p.sendMessage(msg); } catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function awardRestEffects(player) {
|
||||||
|
try {
|
||||||
|
player.addEffect("regeneration", 200, { amplifier: 1, showParticles: false });
|
||||||
|
player.addEffect("saturation", 40, { amplifier: 0, showParticles: false });
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeNightSkip() {
|
||||||
|
// Snapshot tent sleepers before clearing, so we can give them rest perks.
|
||||||
|
const tentIds = [...tentRest.keys()];
|
||||||
|
tentRest.clear();
|
||||||
|
try { world.setTimeOfDay(0); } catch (_) {}
|
||||||
|
for (const p of world.getAllPlayers()) {
|
||||||
|
if (tentIds.includes(p.id) || p.isSleeping) {
|
||||||
|
awardRestEffects(p);
|
||||||
|
}
|
||||||
|
try { p.runCommand("camera @s clear"); } catch (_) {}
|
||||||
|
}
|
||||||
|
world.sendMessage("§6[Sleep] §7The camp rests. Dawn breaks.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Sleep loop: validate tent sleepers, check threshold ────────
|
||||||
|
system.runInterval(() => {
|
||||||
|
if (tentRest.size === 0) return;
|
||||||
|
|
||||||
|
// Validate each tent sleeper still meets the criteria.
|
||||||
|
for (const [pid, rest] of [...tentRest.entries()]) {
|
||||||
|
const player = world.getAllPlayers().find((p) => p.id === pid);
|
||||||
|
if (!player) {
|
||||||
|
tentRest.delete(pid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (player.dimension.id !== rest.dimId) {
|
||||||
|
leaveTentRest(player, "§7[Camping] You wandered out of camp.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const dx = player.location.x - (rest.x + 0.5);
|
||||||
|
const dy = player.location.y - rest.y;
|
||||||
|
const dz = player.location.z - (rest.z + 0.5);
|
||||||
|
if (dx * dx + dz * dz > 4 || Math.abs(dy) > 2) {
|
||||||
|
leaveTentRest(player, "§7[Camping] You wandered out of camp.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (player.isSneaking) {
|
||||||
|
leaveTentRest(player, "§7[Camping] You climb out of the tent.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Sleepers don't get scared off by mobs but a hit cancels rest:
|
||||||
|
// (handled implicitly — damage breaks the camera fade and the player is
|
||||||
|
// expected to sneak out; we don't have a public hurt event hook here)
|
||||||
|
awardRestEffects(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tentRest.size === 0) return;
|
||||||
|
const tod = world.getTimeOfDay();
|
||||||
|
if (!isNight(tod)) {
|
||||||
|
// Sun came up some other way — clear resters quietly.
|
||||||
|
tentRest.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { online, resting } = countSleepers();
|
||||||
|
const pct = getSleepThreshold();
|
||||||
|
const need = requiredSleepers(online, pct);
|
||||||
|
if (resting >= need) {
|
||||||
|
executeNightSkip();
|
||||||
|
}
|
||||||
|
}, SLEEP_TICK_INTERVAL);
|
||||||
|
|
||||||
|
const HAMMOCK_ANCHOR_PROP = "camping_hammock_anchor";
|
||||||
|
|
||||||
|
function toggleHammock(player, loc) {
|
||||||
|
if (player.hasTag(HAMMOCK_TAG)) {
|
||||||
|
exitHammock(player);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.addTag(HAMMOCK_TAG);
|
||||||
|
const anchor = { x: loc.x + 0.5, y: loc.y + 0.1, z: loc.z + 0.5 };
|
||||||
|
try {
|
||||||
|
player.setDynamicProperty(HAMMOCK_ANCHOR_PROP, JSON.stringify(anchor));
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
player.teleport(anchor, { dimension: player.dimension });
|
||||||
|
} catch (_) {}
|
||||||
|
// Slowness 255 + weakness + mining_fatigue make the player effectively immobile while
|
||||||
|
// still conscious; saturation + regen are the "rest" payoff.
|
||||||
|
try {
|
||||||
|
player.addEffect("slowness", 100000, { amplifier: 255, showParticles: false });
|
||||||
|
player.addEffect("weakness", 100000, { amplifier: 255, showParticles: false });
|
||||||
|
player.addEffect("mining_fatigue", 100000, { amplifier: 255, showParticles: false });
|
||||||
|
player.addEffect("saturation", 40, { amplifier: 0, showParticles: false });
|
||||||
|
player.addEffect("regeneration", 200, { amplifier: 1, showParticles: false });
|
||||||
|
} catch (_) {}
|
||||||
|
// Pull the camera back into a cinematic third-person view so the player can see
|
||||||
|
// themselves lying in the hammock. Ease in smoothly.
|
||||||
|
try {
|
||||||
|
player.runCommand("camera @s set minecraft:third_person ease 0.8 out_sine");
|
||||||
|
} catch (_) {}
|
||||||
|
player.sendMessage("§a[Camping] §7You settle into the hammock. Wild creatures don't notice you. §8(Sneak to climb out.)");
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitHammock(player) {
|
||||||
|
player.removeTag(HAMMOCK_TAG);
|
||||||
|
try {
|
||||||
|
player.removeEffect("slowness");
|
||||||
|
player.removeEffect("weakness");
|
||||||
|
player.removeEffect("mining_fatigue");
|
||||||
|
} catch (_) {}
|
||||||
|
// Nudge player one block off the hammock so the next tick doesn't re-teleport them
|
||||||
|
// back into the cradle.
|
||||||
|
try {
|
||||||
|
const raw = player.getDynamicProperty(HAMMOCK_ANCHOR_PROP);
|
||||||
|
if (raw && typeof raw === "string") {
|
||||||
|
const a = JSON.parse(raw);
|
||||||
|
const yaw = player.getRotation().y;
|
||||||
|
const rad = (yaw * Math.PI) / 180;
|
||||||
|
const dx = -Math.sin(rad);
|
||||||
|
const dz = Math.cos(rad);
|
||||||
|
player.teleport(
|
||||||
|
{ x: a.x + dx * 1.2, y: a.y + 0.3, z: a.z + dz * 1.2 },
|
||||||
|
{ dimension: player.dimension }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
try { player.setDynamicProperty(HAMMOCK_ANCHOR_PROP, undefined); } catch (_) {}
|
||||||
|
try { player.runCommand("camera @s clear"); } catch (_) {}
|
||||||
|
player.sendMessage("§7[Camping] You climb out of the hammock.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Hammock upkeep loop: position lock + mob repulsion + sneak-exit ────────
|
||||||
|
system.runInterval(() => {
|
||||||
|
for (const player of world.getAllPlayers()) {
|
||||||
|
if (!player.hasTag(HAMMOCK_TAG)) continue;
|
||||||
|
if (player.isSneaking) {
|
||||||
|
exitHammock(player);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Pin the player to the hammock anchor so they can't drift off even with slowness
|
||||||
|
try {
|
||||||
|
const raw = player.getDynamicProperty(HAMMOCK_ANCHOR_PROP);
|
||||||
|
if (raw && typeof raw === "string") {
|
||||||
|
const a = JSON.parse(raw);
|
||||||
|
const dx = player.location.x - a.x;
|
||||||
|
const dy = player.location.y - a.y;
|
||||||
|
const dz = player.location.z - a.z;
|
||||||
|
if (dx * dx + dy * dy + dz * dz > 0.25) {
|
||||||
|
player.teleport(a, { dimension: player.dimension });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
let hostiles = [];
|
||||||
|
try {
|
||||||
|
hostiles = player.dimension.getEntities({
|
||||||
|
families: ["monster"],
|
||||||
|
location: player.location,
|
||||||
|
maxDistance: 14,
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
for (const m of hostiles) {
|
||||||
|
const dx = m.location.x - player.location.x;
|
||||||
|
const dy = m.location.y - player.location.y;
|
||||||
|
const dz = m.location.z - player.location.z;
|
||||||
|
const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
if (d > 0.01 && d < 6) {
|
||||||
|
const scale = 9 / d;
|
||||||
|
const target = {
|
||||||
|
x: player.location.x + dx * scale,
|
||||||
|
y: m.location.y,
|
||||||
|
z: player.location.z + dz * scale,
|
||||||
|
};
|
||||||
|
try { m.tryTeleport(target, { checkForBlocks: true }); } catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// ─── Break cleanup: break one = pack up the whole structure ─
|
||||||
|
try {
|
||||||
|
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (!block) return;
|
||||||
|
const id = block.typeId;
|
||||||
|
const isTent = TENT_BLOCK_IDS.includes(id);
|
||||||
|
if (!isTent && id !== HAMMOCK_BLOCK) return;
|
||||||
|
event.cancel = true;
|
||||||
|
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||||
|
const dimId = block.dimension.id;
|
||||||
|
const player = event.player;
|
||||||
|
if (isTent) {
|
||||||
|
system.run(() => dismantleTentAt(loc, dimId, player));
|
||||||
|
} else {
|
||||||
|
system.run(() => dismantleHammockAt(loc, dimId, player));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[Camping] playerBreakBlock unavailable: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismantleTentAt(loc, dimId, player) {
|
||||||
|
const dim = world.getDimension(dimId);
|
||||||
|
let matchedKey = null;
|
||||||
|
for (const [k, tent] of Object.entries(state.tents)) {
|
||||||
|
const parts = k.split(",");
|
||||||
|
if (parts[parts.length - 1] !== dimId) continue;
|
||||||
|
if (tent.cells.some(([x, y, z]) => x === loc.x && y === loc.y && z === loc.z)) {
|
||||||
|
matchedKey = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matchedKey) {
|
||||||
|
try { dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`); } catch (_) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tent = state.tents[matchedKey];
|
||||||
|
for (const [x, y, z] of tent.cells) {
|
||||||
|
try { dim.runCommand(`setblock ${x} ${y} ${z} air`); } catch (_) {}
|
||||||
|
}
|
||||||
|
delete state.tents[matchedKey];
|
||||||
|
saveState();
|
||||||
|
try {
|
||||||
|
dim.spawnItem(new ItemStack(TENT_ITEM, 1), { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
|
||||||
|
} catch (_) {}
|
||||||
|
if (player) player.sendMessage("§7[Camping] Tent packed up.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismantleHammockAt(loc, dimId, player) {
|
||||||
|
const dim = world.getDimension(dimId);
|
||||||
|
let matchedKey = null;
|
||||||
|
for (const [k, h] of Object.entries(state.hammocks)) {
|
||||||
|
if (!k.endsWith("," + dimId)) continue;
|
||||||
|
if (h.cells.some(([x, y, z]) => x === loc.x && y === loc.y && z === loc.z)) {
|
||||||
|
matchedKey = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matchedKey) {
|
||||||
|
try { dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`); } catch (_) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const h = state.hammocks[matchedKey];
|
||||||
|
for (const [x, y, z] of h.cells) {
|
||||||
|
try { dim.runCommand(`setblock ${x} ${y} ${z} air`); } catch (_) {}
|
||||||
|
}
|
||||||
|
delete state.hammocks[matchedKey];
|
||||||
|
saveState();
|
||||||
|
try {
|
||||||
|
dim.spawnItem(new ItemStack(HAMMOCK_ITEM, 1), { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 });
|
||||||
|
} catch (_) {}
|
||||||
|
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();
|
||||||
|
world.sendMessage("§6[Camping] §7Camping Supplies loaded.");
|
||||||
|
});
|
||||||
5
camping-supplies-addon/camping_supplies_RP/blocks.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"format_version": [1, 1, 0],
|
||||||
|
"silverlabs:tent_canvas": { "sound": "cloth" },
|
||||||
|
"silverlabs:hammock_cloth": { "sound": "cloth" }
|
||||||
|
}
|
||||||
17
camping-supplies-addon/camping_supplies_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Camping Supplies Resources",
|
||||||
|
"description": "Textures and lang for camping supplies (tent, hammock, canvas, cloth)",
|
||||||
|
"uuid": "36f12107-10c6-484c-a0f2-b5dd88cd5baa",
|
||||||
|
"version": [1, 0, 1],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "c9ee429f-9374-4083-843b-4b195e8db130",
|
||||||
|
"version": [1, 0, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.12.0",
|
||||||
|
"minecraft:geometry": [
|
||||||
|
{
|
||||||
|
"description": {
|
||||||
|
"identifier": "geometry.silverlabs.hammock_slab",
|
||||||
|
"texture_width": 16,
|
||||||
|
"texture_height": 16
|
||||||
|
},
|
||||||
|
"bones": [
|
||||||
|
{
|
||||||
|
"name": "slab",
|
||||||
|
"pivot": [0, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{ "origin": [-8, 0, -8], "size": [16, 4, 16], "uv": [0, 0] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.12.0",
|
||||||
|
"minecraft:geometry": [
|
||||||
|
{
|
||||||
|
"description": {
|
||||||
|
"identifier": "geometry.silverlabs.tent_panel_l",
|
||||||
|
"texture_width": 16,
|
||||||
|
"texture_height": 16,
|
||||||
|
"visible_bounds_width": 2,
|
||||||
|
"visible_bounds_height": 2,
|
||||||
|
"visible_bounds_offset": [0, 0.5, 0]
|
||||||
|
},
|
||||||
|
"bones": [
|
||||||
|
{
|
||||||
|
"name": "panel",
|
||||||
|
"pivot": [0, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{ "origin": [-8, 0, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-6, 2, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-4, 4, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-2, 6, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 0, 8, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 2, 10, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 4, 12, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 6, 14, -8], "size": [2, 2, 16], "uv": [0, 0] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.12.0",
|
||||||
|
"minecraft:geometry": [
|
||||||
|
{
|
||||||
|
"description": {
|
||||||
|
"identifier": "geometry.silverlabs.tent_panel_r",
|
||||||
|
"texture_width": 16,
|
||||||
|
"texture_height": 16,
|
||||||
|
"visible_bounds_width": 2,
|
||||||
|
"visible_bounds_height": 2,
|
||||||
|
"visible_bounds_offset": [0, 0.5, 0]
|
||||||
|
},
|
||||||
|
"bones": [
|
||||||
|
{
|
||||||
|
"name": "panel",
|
||||||
|
"pivot": [0, 0, 0],
|
||||||
|
"cubes": [
|
||||||
|
{ "origin": [ 6, 0, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 4, 2, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 2, 4, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [ 0, 6, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-2, 8, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-4, 10, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-6, 12, -8], "size": [2, 2, 16], "uv": [0, 0] },
|
||||||
|
{ "origin": [-8, 14, -8], "size": [2, 2, 16], "uv": [0, 0] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
camping-supplies-addon/camping_supplies_RP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +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
|
||||||
|
After Width: | Height: | Size: 772 B |
|
After Width: | Height: | Size: 427 B |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "camping_supplies_RP",
|
||||||
|
"texture_name": "atlas.items",
|
||||||
|
"texture_data": {
|
||||||
|
"tent_item": {
|
||||||
|
"textures": "textures/items/tent"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 227 B |
|
After Width: | Height: | Size: 227 B |
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "camping_supplies_RP",
|
||||||
|
"texture_name": "atlas.terrain",
|
||||||
|
"padding": 8,
|
||||||
|
"num_mip_levels": 4,
|
||||||
|
"texture_data": {
|
||||||
|
"tent_canvas": {
|
||||||
|
"textures": "textures/blocks/tent_canvas"
|
||||||
|
},
|
||||||
|
"hammock_cloth": {
|
||||||
|
"textures": "textures/blocks/hammock_cloth"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,13 @@ services:
|
|||||||
container_name: mc-lobby
|
container_name: mc-lobby
|
||||||
environment:
|
environment:
|
||||||
EULA: "TRUE"
|
EULA: "TRUE"
|
||||||
SERVER_NAME: "SilverLABS Hub"
|
|
||||||
GAMEMODE: adventure
|
|
||||||
DIFFICULTY: peaceful
|
|
||||||
ALLOW_CHEATS: "true"
|
|
||||||
ONLINE_MODE: "false"
|
|
||||||
SERVER_PORT: "19132"
|
SERVER_PORT: "19132"
|
||||||
LEVEL_NAME: "Hub World"
|
|
||||||
MAX_PLAYERS: "10"
|
|
||||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
|
||||||
OP_PERMISSION_LEVEL: "4"
|
OP_PERMISSION_LEVEL: "4"
|
||||||
|
# Other server.properties-aliased env vars (SERVER_NAME, GAMEMODE, DIFFICULTY,
|
||||||
|
# ALLOW_CHEATS, ONLINE_MODE, MAX_PLAYERS, DEFAULT_PLAYER_PERMISSION_LEVEL) are
|
||||||
|
# intentionally not set here — they're owned by /data/server.properties inside
|
||||||
|
# the named volume and edited via mc-manager's UI. LEVEL_NAME lives in
|
||||||
|
# docker-compose.override.yml on the host (managed by mc-manager, gitignored).
|
||||||
ports:
|
ports:
|
||||||
- "${LOBBY_PORT:-19132}:19132/udp"
|
- "${LOBBY_PORT:-19132}:19132/udp"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -26,7 +23,27 @@ services:
|
|||||||
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
|
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
|
||||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||||
|
- ./monkey-addon/monkey_BP:/data/behavior_packs/monkey_BP
|
||||||
|
- ./monkey-addon/monkey_RP:/data/resource_packs/monkey_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
|
||||||
|
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
|
||||||
|
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
|
||||||
|
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||||
|
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||||
|
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||||
|
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
|
||||||
|
- ./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
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# Cap each Bedrock service so a runaway/hung server can't OOM-kill its
|
||||||
|
# neighbours. Host has 8 GB; 4 × 1500 MB leaves headroom for the OS and
|
||||||
|
# the supporting stack.
|
||||||
|
mem_limit: 1500m
|
||||||
|
memswap_limit: 2500m
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
|
|
||||||
@@ -35,30 +52,38 @@ services:
|
|||||||
container_name: mc-jamie
|
container_name: mc-jamie
|
||||||
environment:
|
environment:
|
||||||
EULA: "TRUE"
|
EULA: "TRUE"
|
||||||
SERVER_NAME: "Jamie's World"
|
|
||||||
GAMEMODE: survival
|
|
||||||
DIFFICULTY: normal
|
|
||||||
ALLOW_CHEATS: "true"
|
|
||||||
ONLINE_MODE: "false"
|
|
||||||
SERVER_PORT: "19132"
|
SERVER_PORT: "19132"
|
||||||
LEVEL_NAME: "Jamie World"
|
|
||||||
MAX_PLAYERS: "10"
|
|
||||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
|
||||||
OP_PERMISSION_LEVEL: "4"
|
OP_PERMISSION_LEVEL: "4"
|
||||||
|
# See lobby for rationale. LEVEL_SEED is dropped too — the world has long since
|
||||||
|
# been generated and the seed is ignored thereafter.
|
||||||
ports:
|
ports:
|
||||||
- "${JAMIE_PORT:-19133}:19132/udp"
|
- "${JAMIE_PORT:-19133}:19132/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- jamie-data:/data
|
- jamie-data:/data
|
||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
|
||||||
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
|
||||||
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
|
- ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP
|
||||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
- ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP
|
||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
|
||||||
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
|
||||||
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||||
|
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||||
|
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||||
|
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
|
||||||
|
- ./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
|
restart: unless-stopped
|
||||||
|
mem_limit: 1500m
|
||||||
|
memswap_limit: 2500m
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
|
|
||||||
@@ -67,21 +92,14 @@ services:
|
|||||||
container_name: mc-lyla
|
container_name: mc-lyla
|
||||||
environment:
|
environment:
|
||||||
EULA: "TRUE"
|
EULA: "TRUE"
|
||||||
SERVER_NAME: "Lyla's World"
|
|
||||||
GAMEMODE: survival
|
|
||||||
DIFFICULTY: normal
|
|
||||||
ALLOW_CHEATS: "true"
|
|
||||||
ONLINE_MODE: "false"
|
|
||||||
SERVER_PORT: "19132"
|
SERVER_PORT: "19132"
|
||||||
LEVEL_NAME: "Lyla World"
|
|
||||||
MAX_PLAYERS: "10"
|
|
||||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
|
||||||
OP_PERMISSION_LEVEL: "4"
|
OP_PERMISSION_LEVEL: "4"
|
||||||
ports:
|
ports:
|
||||||
- "${LYLA_PORT:-19134}:19132/udp"
|
- "${LYLA_PORT:-19134}:19132/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- lyla-data:/data
|
- lyla-data:/data
|
||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./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_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
||||||
@@ -89,8 +107,25 @@ services:
|
|||||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||||
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
||||||
|
- ./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
|
||||||
|
- ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP
|
||||||
|
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
|
||||||
|
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
|
||||||
|
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||||
|
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||||
|
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||||
|
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
|
||||||
|
- ./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
|
||||||
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
mem_limit: 1500m
|
||||||
|
memswap_limit: 2500m
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
|
|
||||||
@@ -99,21 +134,14 @@ services:
|
|||||||
container_name: mc-mya
|
container_name: mc-mya
|
||||||
environment:
|
environment:
|
||||||
EULA: "TRUE"
|
EULA: "TRUE"
|
||||||
SERVER_NAME: "Mya's World"
|
|
||||||
GAMEMODE: survival
|
|
||||||
DIFFICULTY: normal
|
|
||||||
ALLOW_CHEATS: "true"
|
|
||||||
ONLINE_MODE: "false"
|
|
||||||
SERVER_PORT: "19132"
|
SERVER_PORT: "19132"
|
||||||
LEVEL_NAME: "Mya World"
|
|
||||||
MAX_PLAYERS: "10"
|
|
||||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
|
||||||
OP_PERMISSION_LEVEL: "4"
|
OP_PERMISSION_LEVEL: "4"
|
||||||
ports:
|
ports:
|
||||||
- "${MYA_PORT:-19135}:19132/udp"
|
- "${MYA_PORT:-19135}:19132/udp"
|
||||||
volumes:
|
volumes:
|
||||||
- mya-data:/data
|
- mya-data:/data
|
||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./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_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
||||||
@@ -121,8 +149,31 @@ services:
|
|||||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||||
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
||||||
|
- ./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
|
||||||
|
- ./smart-crafting-addon/smart_crafting_RP:/data/resource_packs/smart_crafting_RP
|
||||||
|
- ./home-sign-addon/home_sign_BP:/data/behavior_packs/home_sign_BP
|
||||||
|
- ./home-sign-addon/home_sign_RP:/data/resource_packs/home_sign_RP
|
||||||
|
- ./keep-inventory-addon/keep_inventory_BP:/data/behavior_packs/keep_inventory_BP
|
||||||
|
- ./postal-service-addon/postal_service_BP:/data/behavior_packs/postal_service_BP
|
||||||
|
- ./postal-service-addon/postal_service_RP:/data/resource_packs/postal_service_RP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_BP:/data/behavior_packs/camping_supplies_BP
|
||||||
|
- ./camping-supplies-addon/camping_supplies_RP:/data/resource_packs/camping_supplies_RP
|
||||||
|
- ./dynamite-addon/dynamite_BP:/data/behavior_packs/dynamite_BP
|
||||||
|
- ./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
|
- ./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
|
restart: unless-stopped
|
||||||
|
mem_limit: 1500m
|
||||||
|
memswap_limit: 2500m
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
|
|
||||||
|
|||||||
41
dynamite-addon/dynamite_BP/entities/thrown_banger.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:entity": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:thrown_banger",
|
||||||
|
"is_spawnable": false,
|
||||||
|
"is_summonable": true,
|
||||||
|
"is_experimental": false
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:collision_box": {
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25
|
||||||
|
},
|
||||||
|
"minecraft:physics": {},
|
||||||
|
"minecraft:pushable": {
|
||||||
|
"is_pushable": false,
|
||||||
|
"is_pushable_by_piston": false
|
||||||
|
},
|
||||||
|
"minecraft:projectile": {
|
||||||
|
"on_hit": {
|
||||||
|
"impact_damage": {
|
||||||
|
"damage": 2,
|
||||||
|
"knockback": true,
|
||||||
|
"destroy_on_hit": true,
|
||||||
|
"semi_random_diff_damage": false
|
||||||
|
},
|
||||||
|
"remove_on_hit": {}
|
||||||
|
},
|
||||||
|
"power": 1.4,
|
||||||
|
"gravity": 0.05,
|
||||||
|
"inertia": 0.99,
|
||||||
|
"liquid_inertia": 0.6,
|
||||||
|
"anchor": 1,
|
||||||
|
"offset": [0, -0.1, 0],
|
||||||
|
"should_bounce": false,
|
||||||
|
"hit_sound": "random.fuse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
dynamite-addon/dynamite_BP/entities/thrown_bundle.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:entity": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:thrown_bundle",
|
||||||
|
"is_spawnable": false,
|
||||||
|
"is_summonable": true,
|
||||||
|
"is_experimental": false
|
||||||
|
},
|
||||||
|
"component_groups": {
|
||||||
|
"silverlabs:detonate": {
|
||||||
|
"minecraft:explode": {
|
||||||
|
"fuse_length": 0,
|
||||||
|
"fuse_lit": true,
|
||||||
|
"power": 3.0,
|
||||||
|
"causes_fire": false,
|
||||||
|
"breaks_blocks": true,
|
||||||
|
"max_resistance": 12,
|
||||||
|
"destroy_affected_by_griefing": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:collision_box": {
|
||||||
|
"width": 0.3,
|
||||||
|
"height": 0.3
|
||||||
|
},
|
||||||
|
"minecraft:physics": {},
|
||||||
|
"minecraft:pushable": {
|
||||||
|
"is_pushable": false,
|
||||||
|
"is_pushable_by_piston": false
|
||||||
|
},
|
||||||
|
"minecraft:projectile": {
|
||||||
|
"on_hit": {
|
||||||
|
"impact_damage": {
|
||||||
|
"damage": 6,
|
||||||
|
"knockback": true,
|
||||||
|
"destroy_on_hit": false,
|
||||||
|
"semi_random_diff_damage": false
|
||||||
|
},
|
||||||
|
"definition_event": {
|
||||||
|
"event_trigger": {
|
||||||
|
"event": "silverlabs:detonate",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power": 1.3,
|
||||||
|
"gravity": 0.06,
|
||||||
|
"inertia": 0.99,
|
||||||
|
"liquid_inertia": 0.6,
|
||||||
|
"anchor": 1,
|
||||||
|
"offset": [0, -0.1, 0],
|
||||||
|
"should_bounce": false,
|
||||||
|
"hit_sound": "random.fuse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"silverlabs:detonate": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["silverlabs:detonate"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
dynamite-addon/dynamite_BP/entities/thrown_dynamite.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:entity": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:thrown_dynamite",
|
||||||
|
"is_spawnable": false,
|
||||||
|
"is_summonable": true,
|
||||||
|
"is_experimental": false
|
||||||
|
},
|
||||||
|
"component_groups": {
|
||||||
|
"silverlabs:detonate": {
|
||||||
|
"minecraft:explode": {
|
||||||
|
"fuse_length": 0,
|
||||||
|
"fuse_lit": true,
|
||||||
|
"power": 1.5,
|
||||||
|
"causes_fire": false,
|
||||||
|
"breaks_blocks": true,
|
||||||
|
"max_resistance": 4,
|
||||||
|
"destroy_affected_by_griefing": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:collision_box": {
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25
|
||||||
|
},
|
||||||
|
"minecraft:physics": {},
|
||||||
|
"minecraft:pushable": {
|
||||||
|
"is_pushable": false,
|
||||||
|
"is_pushable_by_piston": false
|
||||||
|
},
|
||||||
|
"minecraft:projectile": {
|
||||||
|
"on_hit": {
|
||||||
|
"impact_damage": {
|
||||||
|
"damage": 4,
|
||||||
|
"knockback": true,
|
||||||
|
"destroy_on_hit": false,
|
||||||
|
"semi_random_diff_damage": false
|
||||||
|
},
|
||||||
|
"definition_event": {
|
||||||
|
"event_trigger": {
|
||||||
|
"event": "silverlabs:detonate",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"power": 1.4,
|
||||||
|
"gravity": 0.05,
|
||||||
|
"inertia": 0.99,
|
||||||
|
"liquid_inertia": 0.6,
|
||||||
|
"anchor": 1,
|
||||||
|
"offset": [0, -0.1, 0],
|
||||||
|
"should_bounce": false,
|
||||||
|
"hit_sound": "random.fuse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"silverlabs:detonate": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["silverlabs:detonate"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
dynamite-addon/dynamite_BP/items/banger.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:item": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:banger",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "equipment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:icon": "banger",
|
||||||
|
"minecraft:display_name": {
|
||||||
|
"value": "Banger"
|
||||||
|
},
|
||||||
|
"minecraft:max_stack_size": 16,
|
||||||
|
"minecraft:hand_equipped": true,
|
||||||
|
"minecraft:throwable": {
|
||||||
|
"do_swing_animation": true,
|
||||||
|
"max_draw_duration": 0,
|
||||||
|
"scale_power_by_draw_duration": false
|
||||||
|
},
|
||||||
|
"minecraft:projectile": {
|
||||||
|
"projectile_entity": "silverlabs:thrown_banger"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
dynamite-addon/dynamite_BP/items/dynamite_bundle.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:item": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:dynamite_bundle",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "equipment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:icon": "dynamite_bundle",
|
||||||
|
"minecraft:display_name": {
|
||||||
|
"value": "Bundle of Dynamite"
|
||||||
|
},
|
||||||
|
"minecraft:max_stack_size": 16,
|
||||||
|
"minecraft:hand_equipped": true,
|
||||||
|
"minecraft:throwable": {
|
||||||
|
"do_swing_animation": true,
|
||||||
|
"max_draw_duration": 0,
|
||||||
|
"scale_power_by_draw_duration": false
|
||||||
|
},
|
||||||
|
"minecraft:projectile": {
|
||||||
|
"projectile_entity": "silverlabs:thrown_bundle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
dynamite-addon/dynamite_BP/items/dynamite_stick.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:item": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:dynamite_stick",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "equipment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:icon": "dynamite_stick",
|
||||||
|
"minecraft:display_name": {
|
||||||
|
"value": "Stick of Dynamite"
|
||||||
|
},
|
||||||
|
"minecraft:max_stack_size": 16,
|
||||||
|
"minecraft:hand_equipped": true,
|
||||||
|
"minecraft:throwable": {
|
||||||
|
"do_swing_animation": true,
|
||||||
|
"max_draw_duration": 0,
|
||||||
|
"scale_power_by_draw_duration": false
|
||||||
|
},
|
||||||
|
"minecraft:projectile": {
|
||||||
|
"projectile_entity": "silverlabs:thrown_dynamite"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
dynamite-addon/dynamite_BP/manifest.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"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
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"uuid": "1354002c-fdd5-4f7e-b89b-f5dd2c38799c",
|
||||||
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"uuid": "a18bdde1-53f8-49aa-b06d-6f0ec6c45b46",
|
||||||
|
"version": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
dynamite-addon/dynamite_BP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 534 B |
25
dynamite-addon/dynamite_BP/recipes/banger.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:banger_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "minecraft:gunpowder" }
|
||||||
|
],
|
||||||
|
"pattern": [
|
||||||
|
"SP",
|
||||||
|
"G "
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"S": { "item": "minecraft:string" },
|
||||||
|
"P": { "item": "minecraft:paper" },
|
||||||
|
"G": { "item": "minecraft:gunpowder" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:banger",
|
||||||
|
"count": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
dynamite-addon/dynamite_BP/recipes/dynamite_bundle.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:dynamite_bundle_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "silverlabs:dynamite_stick" }
|
||||||
|
],
|
||||||
|
"pattern": [
|
||||||
|
"DDD",
|
||||||
|
" S "
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"D": { "item": "silverlabs:dynamite_stick" },
|
||||||
|
"S": { "item": "minecraft:string" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dynamite_bundle",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
dynamite-addon/dynamite_BP/recipes/dynamite_stick.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:dynamite_stick_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": [
|
||||||
|
{ "item": "silverlabs:banger" }
|
||||||
|
],
|
||||||
|
"pattern": [
|
||||||
|
"BB",
|
||||||
|
"RT"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"B": { "item": "silverlabs:banger" },
|
||||||
|
"R": { "item": "minecraft:redstone" },
|
||||||
|
"T": { "item": "minecraft:stick" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:dynamite_stick",
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
dynamite-addon/dynamite_RP/entity/thrown_banger.json
Normal 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" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
dynamite-addon/dynamite_RP/entity/thrown_bundle.json
Normal 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" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
dynamite-addon/dynamite_RP/entity/thrown_dynamite.json
Normal 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" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
dynamite-addon/dynamite_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Dynamite Resources",
|
||||||
|
"description": "Textures and language for the Dynamite addon.",
|
||||||
|
"uuid": "a18bdde1-53f8-49aa-b06d-6f0ec6c45b46",
|
||||||
|
"version": [1, 0, 2],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "587281f2-f159-4ad9-85a6-d20ff4899717",
|
||||||
|
"version": [1, 0, 2]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
dynamite-addon/dynamite_RP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 534 B |
6
dynamite-addon/dynamite_RP/texts/en_US.lang
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
item.silverlabs:banger=Banger
|
||||||
|
item.silverlabs:dynamite_stick=Stick of Dynamite
|
||||||
|
item.silverlabs:dynamite_bundle=Bundle of Dynamite
|
||||||
|
entity.silverlabs:thrown_banger.name=Banger
|
||||||
|
entity.silverlabs:thrown_dynamite.name=Dynamite
|
||||||
|
entity.silverlabs:thrown_bundle.name=Dynamite Bundle
|
||||||
3
dynamite-addon/dynamite_RP/texts/languages.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
"en_US"
|
||||||
|
]
|
||||||
BIN
dynamite-addon/dynamite_RP/textures/entity/thrown_banger.png
Normal file
|
After Width: | Height: | Size: 298 B |
BIN
dynamite-addon/dynamite_RP/textures/entity/thrown_bundle.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
dynamite-addon/dynamite_RP/textures/entity/thrown_dynamite.png
Normal file
|
After Width: | Height: | Size: 372 B |
15
dynamite-addon/dynamite_RP/textures/item_texture.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "dynamite_RP",
|
||||||
|
"texture_name": "atlas.items",
|
||||||
|
"texture_data": {
|
||||||
|
"banger": {
|
||||||
|
"textures": "textures/items/banger"
|
||||||
|
},
|
||||||
|
"dynamite_stick": {
|
||||||
|
"textures": "textures/items/dynamite_stick"
|
||||||
|
},
|
||||||
|
"dynamite_bundle": {
|
||||||
|
"textures": "textures/items/dynamite_bundle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
dynamite-addon/dynamite_RP/textures/items/banger.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
dynamite-addon/dynamite_RP/textures/items/dynamite_bundle.png
Normal file
|
After Width: | Height: | Size: 186 B |
BIN
dynamite-addon/dynamite_RP/textures/items/dynamite_stick.png
Normal file
|
After Width: | Height: | Size: 196 B |
@@ -240,8 +240,14 @@ function placeAllEggs(playerPos) {
|
|||||||
// Validate at least one stored egg block actually exists
|
// Validate at least one stored egg block actually exists
|
||||||
const stored = getEggPositions();
|
const stored = getEggPositions();
|
||||||
if (stored && stored.length > 0) {
|
if (stored && stored.length > 0) {
|
||||||
|
// Check far-ring eggs (last 5) — they're hardest to reach and least likely
|
||||||
|
// to have been collected yet. Avoids false "blocks missing" detection when
|
||||||
|
// only the close-ring eggs (1-5) have been collected.
|
||||||
let found = 0;
|
let found = 0;
|
||||||
for (let i = 0; i < Math.min(5, stored.length); i++) {
|
const checkAt = [45, 46, 47, 48, 49].filter(i => i < stored.length);
|
||||||
|
const fallback = checkAt.length === 0;
|
||||||
|
const indices = fallback ? [0, 1, 2, 3, 4].filter(i => i < stored.length) : checkAt;
|
||||||
|
for (const i of indices) {
|
||||||
try {
|
try {
|
||||||
const block = overworld.getBlock({ x: stored[i].x, y: stored[i].y, z: stored[i].z });
|
const block = overworld.getBlock({ x: stored[i].x, y: stored[i].y, z: stored[i].z });
|
||||||
if (block && block.typeId.includes("glazed_terracotta")) found++;
|
if (block && block.typeId.includes("glazed_terracotta")) found++;
|
||||||
|
|||||||
74
hemp-addon/hemp_BP/blocks/hemp_crop.json
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
50
hemp-addon/hemp_BP/blocks/sun_lamp.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
35
hemp-addon/hemp_BP/feature_rules/hemp_patch_rule.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:feature_rules": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:hemp_patch_rule",
|
||||||
|
"places_feature": "silverlabs:hemp_patch_feature"
|
||||||
|
},
|
||||||
|
"conditions": {
|
||||||
|
"placement_pass": "surface_pass",
|
||||||
|
"minecraft:biome_filter": [
|
||||||
|
{
|
||||||
|
"any_of": [
|
||||||
|
{ "test": "has_biome_tag", "value": "plains" },
|
||||||
|
{ "test": "has_biome_tag", "value": "forest" },
|
||||||
|
{ "test": "has_biome_tag", "value": "birch" },
|
||||||
|
{ "test": "has_biome_tag", "value": "flower_forest" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "test": "has_biome_tag", "operator": "!=", "value": "monster" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"distribution": {
|
||||||
|
"iterations": 1,
|
||||||
|
"scatter_chance": 14,
|
||||||
|
"x": { "distribution": "uniform", "extent": [0, 16] },
|
||||||
|
"y": {
|
||||||
|
"distribution": "fixed_grid",
|
||||||
|
"extent": [0, 64],
|
||||||
|
"grid_offset": 0,
|
||||||
|
"step_size": 1
|
||||||
|
},
|
||||||
|
"z": { "distribution": "uniform", "extent": [0, 16] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
hemp-addon/hemp_BP/features/hemp_patch_feature.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:scatter_feature": {
|
||||||
|
"description": { "identifier": "silverlabs:hemp_patch_feature" },
|
||||||
|
"iterations": 3,
|
||||||
|
"scatter_chance": 65,
|
||||||
|
"x": { "distribution": "uniform", "extent": [-3, 4] },
|
||||||
|
"y": 0,
|
||||||
|
"z": { "distribution": "uniform", "extent": [-3, 4] },
|
||||||
|
"places_feature": "silverlabs:hemp_single_feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
hemp-addon/hemp_BP/features/hemp_single_feature.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:single_block_feature": {
|
||||||
|
"description": { "identifier": "silverlabs:hemp_single_feature" },
|
||||||
|
"places_block": {
|
||||||
|
"name": "silverlabs:hemp_crop",
|
||||||
|
"states": {
|
||||||
|
"silverlabs:hemp_age": 4,
|
||||||
|
"silverlabs:hemp_top": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enforce_survivability_rules": true,
|
||||||
|
"enforce_placement_rules": true,
|
||||||
|
"may_attach_to": {
|
||||||
|
"min_sides_must_attach": 1,
|
||||||
|
"top": [
|
||||||
|
"minecraft:grass_block",
|
||||||
|
"minecraft:dirt",
|
||||||
|
"minecraft:podzol",
|
||||||
|
"minecraft:coarse_dirt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"may_replace": [
|
||||||
|
"minecraft:air"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
25
hemp-addon/hemp_BP/items/hemp_brownie.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
hemp-addon/hemp_BP/items/hemp_bud.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
hemp-addon/hemp_BP/items/hemp_seeds.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
hemp-addon/hemp_BP/items/hemp_tincture.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
hemp-addon/hemp_BP/loot_tables/blocks/hemp_crop.json
Normal 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 } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
54
hemp-addon/hemp_BP/manifest.json
Normal 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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
hemp-addon/hemp_BP/recipes/hemp_brownie.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
hemp-addon/hemp_BP/recipes/sun_lamp.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
812
hemp-addon/hemp_BP/scripts/main.js
Normal file
@@ -0,0 +1,812 @@
|
|||||||
|
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;
|
||||||
|
const held = stack.amount;
|
||||||
|
system.run(() => {
|
||||||
|
if (held < deal.perTrade) {
|
||||||
|
player.sendMessage(`§7[Trader] §fBring me at least §a${deal.perTrade}§f of those and I'll pay in emeralds.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inv = getInv(player);
|
||||||
|
if (!inv) return;
|
||||||
|
// Trade scope is the held stack only — never walk the rest of the inventory
|
||||||
|
const sel = player.selectedSlotIndex;
|
||||||
|
const cur = inv.getItem(sel);
|
||||||
|
if (!cur || cur.typeId !== stack.typeId) {
|
||||||
|
// Player swapped item out between the click and this run() — abort safely
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const trades = Math.floor(held / deal.perTrade);
|
||||||
|
const consumed = trades * deal.perTrade;
|
||||||
|
const remaining = held - consumed;
|
||||||
|
if (remaining > 0) {
|
||||||
|
cur.amount = remaining;
|
||||||
|
inv.setItem(sel, cur);
|
||||||
|
} else {
|
||||||
|
inv.setItem(sel, undefined);
|
||||||
|
}
|
||||||
|
giveItem(player, "minecraft:emerald", trades * 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${consumed}§f for §a${trades * deal.emeralds} emerald${trades * 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.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Chest loot injection: first-time-open chests sometimes contain seeds ---
|
||||||
|
// Bedrock has no merge-loot-table mechanism, so we hook chest opens and
|
||||||
|
// deposit hemp_seeds with low probability into a random empty slot.
|
||||||
|
// Tracking is per-chest via a world dynamic property holding a JSON list
|
||||||
|
// of "dim:x:y:z" keys; pruned to a rolling cap to bound storage.
|
||||||
|
const CHEST_TYPES = new Set(["minecraft:chest", "minecraft:trapped_chest"]);
|
||||||
|
const CHEST_SEED_CHANCE = 0.08;
|
||||||
|
const SEEDED_PROP = "hemp_seeded_chests_v1";
|
||||||
|
const SEEDED_CAP = 500;
|
||||||
|
|
||||||
|
function chestKey(block) {
|
||||||
|
const l = block.location;
|
||||||
|
return `${block.dimension.id}:${l.x}:${l.y}:${l.z}`;
|
||||||
|
}
|
||||||
|
function loadSeededChests() {
|
||||||
|
try {
|
||||||
|
const raw = world.getDynamicProperty(SEEDED_PROP);
|
||||||
|
return new Set(raw ? JSON.parse(raw) : []);
|
||||||
|
} catch (_) { return new Set(); }
|
||||||
|
}
|
||||||
|
function saveSeededChests(set) {
|
||||||
|
// Prune oldest if over cap (Set preserves insertion order in JS)
|
||||||
|
if (set.size > SEEDED_CAP) {
|
||||||
|
const arr = Array.from(set).slice(set.size - SEEDED_CAP);
|
||||||
|
set = new Set(arr);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
world.setDynamicProperty(SEEDED_PROP, JSON.stringify(Array.from(set)));
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
world.afterEvents.playerInteractWithBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (!block || !CHEST_TYPES.has(block.typeId)) return;
|
||||||
|
// Run on every chest interaction; the per-chest seeded set prevents repeats.
|
||||||
|
// Earlier `if (stack) return` was over-aggressive — players almost always
|
||||||
|
// hold an item when opening chests, so it suppressed nearly all opens.
|
||||||
|
const key = chestKey(block);
|
||||||
|
const seeded = loadSeededChests();
|
||||||
|
if (seeded.has(key)) return;
|
||||||
|
seeded.add(key);
|
||||||
|
saveSeededChests(seeded);
|
||||||
|
if (!chance(CHEST_SEED_CHANCE)) return;
|
||||||
|
// Try to insert into a random empty slot
|
||||||
|
let inv;
|
||||||
|
try { inv = block.getComponent("minecraft:inventory")?.container; } catch (_) { return; }
|
||||||
|
if (!inv) return;
|
||||||
|
const empties = [];
|
||||||
|
for (let i = 0; i < inv.size; i++) if (!inv.getItem(i)) empties.push(i);
|
||||||
|
if (empties.length === 0) return;
|
||||||
|
const slot = empties[rand(empties.length)];
|
||||||
|
const count = 1 + rand(3); // 1-3 seeds
|
||||||
|
try { inv.setItem(slot, new ItemStack(SEEDS, count)); } catch (_) {}
|
||||||
|
try { event.player.sendMessage("§a[Hemp] §7You spot some §dhemp seeds§7 tucked inside the chest."); } catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Wild hemp scatter: bridge until worldgen patches show up ---
|
||||||
|
// Bedrock features only fire on FRESH chunk generation. Pre-existing
|
||||||
|
// chunks (where the player has already been) won't have patches even
|
||||||
|
// after we ship the feature_rule. This periodic scatter seeds mature
|
||||||
|
// hemp_crop on suitable grass blocks in the player's vicinity so the
|
||||||
|
// mechanic is discoverable in legacy worlds. Cap one patch per minute
|
||||||
|
// per player to avoid flooding.
|
||||||
|
const SCATTER_INTERVAL_TICKS = 1200; // 60 s
|
||||||
|
const SCATTER_CHANCE_PER_TICK = 0.4; // 40% per minute → ~1 patch every 2.5 min
|
||||||
|
const SCATTER_RADIUS = 32;
|
||||||
|
const SCATTER_PATCH_SIZE = [2, 4]; // 2-4 plants per patch
|
||||||
|
function tryScatterHempNearPlayer(player) {
|
||||||
|
const dim = player.dimension;
|
||||||
|
if (dim.id !== "minecraft:overworld") return;
|
||||||
|
const px = Math.floor(player.location.x);
|
||||||
|
const py = Math.floor(player.location.y);
|
||||||
|
const pz = Math.floor(player.location.z);
|
||||||
|
// Sample up to 12 candidate locations; pick the first viable grass block
|
||||||
|
for (let attempt = 0; attempt < 12; attempt++) {
|
||||||
|
const dx = rand(SCATTER_RADIUS * 2 + 1) - SCATTER_RADIUS;
|
||||||
|
const dz = rand(SCATTER_RADIUS * 2 + 1) - SCATTER_RADIUS;
|
||||||
|
// Search vertically: top-down within ±6 of player y for the surface
|
||||||
|
let surface = null;
|
||||||
|
for (let dy = 6; dy >= -6; dy--) {
|
||||||
|
let b;
|
||||||
|
try { b = dim.getBlock({ x: px + dx, y: py + dy, z: pz + dz }); } catch (_) { continue; }
|
||||||
|
if (!b) continue;
|
||||||
|
if (b.typeId === "minecraft:grass_block") {
|
||||||
|
// Check above is air (and skylit-ish — not a cave with grass_path glitches)
|
||||||
|
let above;
|
||||||
|
try { above = dim.getBlock({ x: px + dx, y: py + dy + 1, z: pz + dz }); } catch (_) { continue; }
|
||||||
|
if (above && above.isAir) { surface = { x: px + dx, y: py + dy + 1, z: pz + dz }; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!surface) continue;
|
||||||
|
// Place 2-4 mature hemp_crop in a small cluster around surface
|
||||||
|
const count = SCATTER_PATCH_SIZE[0] + rand(SCATTER_PATCH_SIZE[1] - SCATTER_PATCH_SIZE[0] + 1);
|
||||||
|
let placed = 0;
|
||||||
|
for (let i = 0; i < count * 3 && placed < count; i++) {
|
||||||
|
const ox = rand(5) - 2;
|
||||||
|
const oz = rand(5) - 2;
|
||||||
|
const tx = surface.x + ox, ty = surface.y, tz = surface.z + oz;
|
||||||
|
let target, ground;
|
||||||
|
try {
|
||||||
|
target = dim.getBlock({ x: tx, y: ty, z: tz });
|
||||||
|
ground = dim.getBlock({ x: tx, y: ty - 1, z: tz });
|
||||||
|
} catch (_) { continue; }
|
||||||
|
if (!target || !ground) continue;
|
||||||
|
if (!target.isAir) continue;
|
||||||
|
if (ground.typeId !== "minecraft:grass_block" && ground.typeId !== "minecraft:dirt") continue;
|
||||||
|
try {
|
||||||
|
const perm = BlockPermutation.resolve(CROP, { [AGE]: 4, [TOP]: false });
|
||||||
|
target.setPermutation(perm);
|
||||||
|
placed++;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
return; // one patch per tick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
system.runInterval(() => {
|
||||||
|
const players = world.getAllPlayers();
|
||||||
|
if (players.length === 0) return;
|
||||||
|
const player = players[rand(players.length)];
|
||||||
|
if (!chance(SCATTER_CHANCE_PER_TICK)) return;
|
||||||
|
tryScatterHempNearPlayer(player);
|
||||||
|
}, SCATTER_INTERVAL_TICKS);
|
||||||
|
|
||||||
|
system.run(() => {
|
||||||
|
world.sendMessage("§a[Hemp] §7Hemp pack loaded.");
|
||||||
|
});
|
||||||