Compare commits
@@ -7,8 +7,15 @@ on:
|
||||
- 'addon/**'
|
||||
- 'lobby-addon/**'
|
||||
- 'hub-return-addon/**'
|
||||
- 'easter-egg-addon/**'
|
||||
- 'village-evolution-addon/**'
|
||||
- 'monkey-addon/**'
|
||||
- 'private-chest-addon/**'
|
||||
- 'home-sign-addon/**'
|
||||
- 'keep-inventory-addon/**'
|
||||
- 'postal-service-addon/**'
|
||||
- 'camping-supplies-addon/**'
|
||||
- 'family-goals-addon/**'
|
||||
- 'redstone-link-addon/**'
|
||||
- 'docker-compose.yml'
|
||||
- 'scripts/**'
|
||||
|
||||
@@ -26,6 +33,12 @@ jobs:
|
||||
script: |
|
||||
set -e
|
||||
APP_DIR="$HOME/minecraft-multiworld"
|
||||
# Anything mounted in docker-compose.yml but missing here is simply
|
||||
# never refreshed by CI. family-goals-addon and redstone-link-addon
|
||||
# are listed because docker-compose.yml mounts them; omitting either
|
||||
# while checking out docker-compose.yml would leave a mount pointing
|
||||
# at a directory this job never delivers.
|
||||
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/ family-goals-addon/ redstone-link-addon/ docker-compose.yml"
|
||||
|
||||
# First run: clone. Subsequent: pull.
|
||||
if [ ! -d "$APP_DIR/.git" ]; then
|
||||
@@ -33,11 +46,11 @@ jobs:
|
||||
git init
|
||||
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
||||
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
|
||||
cd "$APP_DIR"
|
||||
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
|
||||
|
||||
# Recreate containers so any new docker-compose volume mounts are applied,
|
||||
|
||||
@@ -16,8 +16,18 @@ addon/build/
|
||||
# Environment
|
||||
.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/
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
|
||||
# Art workspace (edit-in-place mirror; real textures live in each RP)
|
||||
/art/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"minecraft:entity": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:anthrax_cat",
|
||||
"is_spawnable": false,
|
||||
"is_spawnable": true,
|
||||
"is_summonable": true,
|
||||
"is_experimental": false
|
||||
},
|
||||
@@ -12,9 +12,13 @@
|
||||
"minecraft:interact": {
|
||||
"interactions": [
|
||||
{
|
||||
"on_interact": {
|
||||
"filters": { "test": "is_family", "subject": "other", "value": "player" },
|
||||
"event": "silverlabs:interacted",
|
||||
"target": "self"
|
||||
},
|
||||
"interact_text": "action.interact.fortune",
|
||||
"play_sounds": "note.bell",
|
||||
"event": "silverlabs:interacted"
|
||||
"play_sounds": "note.bell"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -39,18 +43,6 @@
|
||||
"minecraft:knockback_resistance": {
|
||||
"value": 1.0
|
||||
},
|
||||
"minecraft:damage_sensor": {
|
||||
"triggers": [
|
||||
{
|
||||
"cause": "all",
|
||||
"deals_damage": false
|
||||
},
|
||||
{
|
||||
"cause": "override",
|
||||
"deals_damage": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"minecraft:health": {
|
||||
"value": 20,
|
||||
"max": 20
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 16,
|
||||
"minecraft:icon": {
|
||||
"texture": "anthrax_cat"
|
||||
},
|
||||
"minecraft:icon": "anthrax_cat",
|
||||
"minecraft:entity_placer": {
|
||||
"entity": "silverlabs:anthrax_cat"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
"minecraft:item": {
|
||||
"description": {
|
||||
"identifier": "silverlabs:heyhe_egg",
|
||||
"category": "Nature"
|
||||
"menu_category": { "category": "nature" }
|
||||
},
|
||||
"components": {
|
||||
"minecraft:max_stack_size": 16,
|
||||
"minecraft:icon": {
|
||||
"texture": "heyhe_egg"
|
||||
},
|
||||
"minecraft:icon": "heyhe_egg",
|
||||
"minecraft:entity_placer": {
|
||||
"entity": "silverlabs:heyhe_egg",
|
||||
"dispense_on": [
|
||||
|
||||
@@ -2,22 +2,38 @@
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Hey Hey Chicken Pet",
|
||||
"description": "Hey Hey the loyal Moana chicken — tameable, following pet with waddly personality.",
|
||||
"description": "Hey Hey the loyal Moana chicken \u2014 tameable, following pet with waddly personality.",
|
||||
"uuid": "fc811a53-dbdb-4701-bc63-c3ca1d793c47",
|
||||
"version": [1, 0, 0],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
"version": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"min_engine_version": [
|
||||
1,
|
||||
21,
|
||||
0
|
||||
]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "data",
|
||||
"uuid": "f7aae24a-06d8-432b-85dc-359c4fda9dfd",
|
||||
"version": [1, 0, 0]
|
||||
"version": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"uuid": "45ec17df-0d84-456f-b2cc-2d990f96e6d5",
|
||||
"version": [1, 0, 0]
|
||||
"version": [
|
||||
1,
|
||||
0,
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -190,15 +190,14 @@
|
||||
"reselect_targets": true
|
||||
},
|
||||
"minecraft:entity_sensor": {
|
||||
"sensor_range": 500,
|
||||
"relative_range": false,
|
||||
"minimum_count": 1,
|
||||
"event_filters": {
|
||||
"test": "is_family",
|
||||
"subject": "other",
|
||||
"value": "whistle_signal"
|
||||
},
|
||||
"event": "silverlabs:whistle_called"
|
||||
"subsensors": [
|
||||
{
|
||||
"range": [500, 500],
|
||||
"minimum_count": 1,
|
||||
"event": "silverlabs:whistle_called",
|
||||
"event_filters": { "test": "is_family", "subject": "other", "value": "whistle_signal" }
|
||||
}
|
||||
]
|
||||
},
|
||||
"minecraft:behavior.melee_attack": {
|
||||
"priority": 2,
|
||||
|
||||
@@ -2,22 +2,38 @@
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Pets 4 Jamie's STARS",
|
||||
"description": "Tameable dragon pets with eggs, gravestones and personality — built for Jamie's STARS!",
|
||||
"description": "Tameable dragon pets with eggs, gravestones and personality \u2014 built for Jamie's STARS!",
|
||||
"uuid": "7cf924ce-e246-4c8c-998c-f420edb26451",
|
||||
"version": [1, 0, 0],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
"version": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"min_engine_version": [
|
||||
1,
|
||||
21,
|
||||
0
|
||||
]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "data",
|
||||
"uuid": "6806e843-c0b4-4a4d-9cb3-d5352e396fe5",
|
||||
"version": [1, 0, 0]
|
||||
"version": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
|
||||
"version": [1, 0, 0]
|
||||
"version": [
|
||||
1,
|
||||
0,
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,16 +4,29 @@
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_basket_recipe"
|
||||
},
|
||||
"tags": ["crafting_table"],
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"S S",
|
||||
"WLW",
|
||||
"SSS"
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:stick" },
|
||||
"W": { "item": "minecraft:wool" },
|
||||
"L": { "item": "minecraft:leather" }
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
},
|
||||
"W": {
|
||||
"item": "minecraft:wool"
|
||||
},
|
||||
"L": {
|
||||
"item": "minecraft:leather"
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"item": "silverlabs:dragon_basket"
|
||||
|
||||
@@ -4,20 +4,33 @@
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg"
|
||||
},
|
||||
"tags": ["crafting_table"],
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
" B ",
|
||||
"AGA",
|
||||
" A "
|
||||
],
|
||||
"key": {
|
||||
"B": { "item": "minecraft:blaze_powder" },
|
||||
"A": { "item": "minecraft:amethyst_shard" },
|
||||
"G": { "item": "minecraft:gold_ingot" }
|
||||
"B": {
|
||||
"item": "minecraft:blaze_powder"
|
||||
},
|
||||
"A": {
|
||||
"item": "minecraft:amethyst_shard"
|
||||
},
|
||||
"G": {
|
||||
"item": "minecraft:gold_ingot"
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"item": "silverlabs:dragon_egg",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
{
|
||||
"format_version": "1.21.0",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": { "identifier": "silverlabs:dragon_egg_crimson_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"ingredients": [
|
||||
{ "item": "silverlabs:dragon_egg" },
|
||||
{ "item": "minecraft:red_dye" }
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg_crimson_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"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",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": { "identifier": "silverlabs:dragon_egg_jungle_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"ingredients": [
|
||||
{ "item": "silverlabs:dragon_egg" },
|
||||
{ "item": "minecraft:lime_dye" }
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg_jungle_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"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",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": { "identifier": "silverlabs:dragon_egg_oak_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"ingredients": [
|
||||
{ "item": "silverlabs:dragon_egg" },
|
||||
{ "item": "minecraft:white_dye" }
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg_oak_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"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",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": { "identifier": "silverlabs:dragon_egg_obsidian_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"ingredients": [
|
||||
{ "item": "silverlabs:dragon_egg" },
|
||||
{ "item": "minecraft:black_dye" }
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg_obsidian_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"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",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": { "identifier": "silverlabs:dragon_egg_spruce_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"ingredients": [
|
||||
{ "item": "silverlabs:dragon_egg" },
|
||||
{ "item": "minecraft:brown_dye" }
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg_spruce_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"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",
|
||||
"minecraft:recipe_shapeless": {
|
||||
"description": { "identifier": "silverlabs:dragon_egg_warped_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"ingredients": [
|
||||
{ "item": "silverlabs:dragon_egg" },
|
||||
{ "item": "minecraft:cyan_dye" }
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_egg_warped_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"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",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": { "identifier": "silverlabs:dragon_nest_crimson_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_nest_crimson_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"S S",
|
||||
"WLW",
|
||||
"PPP"
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:stick" },
|
||||
"W": { "item": "minecraft:red_wool" },
|
||||
"L": { "item": "minecraft:leather" },
|
||||
"P": { "item": "minecraft:crimson_planks" }
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
},
|
||||
"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",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": { "identifier": "silverlabs:dragon_nest_jungle_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_nest_jungle_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"S S",
|
||||
"WLW",
|
||||
"PPP"
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:stick" },
|
||||
"W": { "item": "minecraft:lime_wool" },
|
||||
"L": { "item": "minecraft:leather" },
|
||||
"P": { "item": "minecraft:jungle_planks" }
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
},
|
||||
"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",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": { "identifier": "silverlabs:dragon_nest_oak_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_nest_oak_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"S S",
|
||||
"WLW",
|
||||
"PPP"
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:stick" },
|
||||
"W": { "item": "minecraft:white_wool" },
|
||||
"L": { "item": "minecraft:leather" },
|
||||
"P": { "item": "minecraft:oak_planks" }
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
},
|
||||
"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",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": { "identifier": "silverlabs:dragon_nest_obsidian_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_nest_obsidian_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"O O",
|
||||
"WLW",
|
||||
"OOO"
|
||||
],
|
||||
"key": {
|
||||
"O": { "item": "minecraft:obsidian" },
|
||||
"W": { "item": "minecraft:black_wool" },
|
||||
"L": { "item": "minecraft:leather" }
|
||||
"O": {
|
||||
"item": "minecraft:obsidian"
|
||||
},
|
||||
"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",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": { "identifier": "silverlabs:dragon_nest_spruce_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_nest_spruce_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"S S",
|
||||
"WLW",
|
||||
"PPP"
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:stick" },
|
||||
"W": { "item": "minecraft:brown_wool" },
|
||||
"L": { "item": "minecraft:leather" },
|
||||
"P": { "item": "minecraft:spruce_planks" }
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
},
|
||||
"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",
|
||||
"minecraft:recipe_shaped": {
|
||||
"description": { "identifier": "silverlabs:dragon_nest_warped_recipe" },
|
||||
"tags": ["crafting_table"],
|
||||
"description": {
|
||||
"identifier": "silverlabs:dragon_nest_warped_recipe"
|
||||
},
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
"S S",
|
||||
"WLW",
|
||||
"PPP"
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:stick" },
|
||||
"W": { "item": "minecraft:cyan_wool" },
|
||||
"L": { "item": "minecraft:leather" },
|
||||
"P": { "item": "minecraft:warped_planks" }
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
},
|
||||
"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": {
|
||||
"identifier": "silverlabs:dragon_toy"
|
||||
},
|
||||
"tags": ["crafting_table"],
|
||||
"unlock": [
|
||||
{
|
||||
"item": "minecraft:crafting_table"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"crafting_table"
|
||||
],
|
||||
"pattern": [
|
||||
" S ",
|
||||
"SLS",
|
||||
" S "
|
||||
],
|
||||
"key": {
|
||||
"S": { "item": "minecraft:string" },
|
||||
"L": { "item": "minecraft:leather" }
|
||||
"S": {
|
||||
"item": "minecraft:string"
|
||||
},
|
||||
"L": {
|
||||
"item": "minecraft:leather"
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"item": "silverlabs:dragon_toy",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
{ "fire_breathing": "query.property('silverlabs:firing')" },
|
||||
{ "flying": "!query.is_on_ground && query.property('silverlabs:growth_stage') == 2" },
|
||||
{ "walking": "query.modified_move_speed > 0.1" },
|
||||
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" },
|
||||
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" },
|
||||
{ "sitting": "query.is_sitting" },
|
||||
{ "grooming": "query.is_on_ground && query.modified_move_speed <= 0.05 && math.mod(math.floor((query.life_time + math.mod(math.abs(math.floor(query.position(0) + query.position(2))), 5) * 36.0) / 36.0), 5) == 1" },
|
||||
{ "sniffing": "query.is_on_ground && query.modified_move_speed <= 0.15 && math.mod(math.floor((query.life_time + math.mod(math.abs(math.floor(query.position(0) + query.position(2))), 5) * 36.0) / 36.0), 5) == 2" },
|
||||
@@ -52,14 +52,14 @@
|
||||
"animations": ["sit"],
|
||||
"transitions": [
|
||||
{ "idle": "!query.is_sitting" },
|
||||
{ "sleeping": "query.is_sitting && (query.day_light_level < 4)" }
|
||||
{ "sleeping": "query.is_sitting && (query.time_of_day > 0.5)" }
|
||||
]
|
||||
},
|
||||
"sleeping": {
|
||||
"animations": ["sleep"],
|
||||
"transitions": [
|
||||
{ "idle": "!query.is_sitting" },
|
||||
{ "sitting": "query.is_sitting && (query.day_light_level >= 4)" }
|
||||
{ "sitting": "query.is_sitting && (query.time_of_day <= 0.5)" }
|
||||
]
|
||||
},
|
||||
"flying": {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"particle_effects": {
|
||||
"beacon_beam": "minecraft:endrod",
|
||||
"mood_happy": "minecraft:villager_happy",
|
||||
"mood_needy": "minecraft:large_smoke"
|
||||
"mood_needy": "minecraft:basic_smoke_particle"
|
||||
},
|
||||
"spawn_egg": {
|
||||
"base_color": "#7B2FBE",
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
"name": "Pets 4 Jamie's STARS Resources",
|
||||
"description": "Textures, models and animations for Pets 4 Jamie's STARS",
|
||||
"uuid": "5f25d547-00bb-49ce-8be3-d86cd3941c9b",
|
||||
"version": [1, 0, 0],
|
||||
"version": [1, 0, 1],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "resources",
|
||||
"uuid": "6adc20cd-25a5-4ca5-aa12-f5e43ee9ea22",
|
||||
"version": [1, 0, 0]
|
||||
"version": [1, 0, 1]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
|
||||
@@ -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,25 @@
|
||||
{
|
||||
"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:geometry": "minecraft:geometry.full_block",
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"format_version": [1, 1, 0],
|
||||
"silverlabs:tent_canvas": { "sound": "cloth" },
|
||||
"silverlabs:hammock_cloth": { "sound": "cloth" }
|
||||
}
|
||||
@@ -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] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
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
|
||||
environment:
|
||||
EULA: "TRUE"
|
||||
SERVER_NAME: "SilverLABS Hub"
|
||||
GAMEMODE: adventure
|
||||
DIFFICULTY: peaceful
|
||||
ALLOW_CHEATS: "true"
|
||||
ONLINE_MODE: "false"
|
||||
SERVER_PORT: "19132"
|
||||
LEVEL_NAME: "Hub World"
|
||||
MAX_PLAYERS: "10"
|
||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
||||
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:
|
||||
- "${LOBBY_PORT:-19132}:19132/udp"
|
||||
volumes:
|
||||
@@ -26,7 +23,29 @@ services:
|
||||
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
|
||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||
- ./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
|
||||
- ./redstone-link-addon/redstone_link_BP:/data/behavior_packs/redstone_link_BP
|
||||
- ./redstone-link-addon/redstone_link_RP:/data/resource_packs/redstone_link_RP
|
||||
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:
|
||||
- mc-network
|
||||
|
||||
@@ -35,30 +54,39 @@ services:
|
||||
container_name: mc-jamie
|
||||
environment:
|
||||
EULA: "TRUE"
|
||||
SERVER_NAME: "Jamie's World"
|
||||
GAMEMODE: survival
|
||||
DIFFICULTY: normal
|
||||
ALLOW_CHEATS: "true"
|
||||
ONLINE_MODE: "false"
|
||||
SERVER_PORT: "19132"
|
||||
LEVEL_NAME: "Jamie World"
|
||||
MAX_PLAYERS: "10"
|
||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
||||
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:
|
||||
- "${JAMIE_PORT:-19133}:19132/udp"
|
||||
volumes:
|
||||
- jamie-data:/data
|
||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
||||
- ./addon/heyhe_pet_RP:/data/resource_packs/heyhe_pet_RP
|
||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||
- ./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/enabled_packs.json:/data/config/default/enabled_packs.json
|
||||
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
|
||||
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
|
||||
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
|
||||
- ./smart-crafting-addon/smart_crafting_BP:/data/behavior_packs/smart_crafting_BP
|
||||
- ./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
|
||||
- ./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
|
||||
- ./family-goals-addon/family_goals_BP:/data/behavior_packs/family_goals_BP
|
||||
restart: unless-stopped
|
||||
mem_limit: 1500m
|
||||
memswap_limit: 2500m
|
||||
networks:
|
||||
- mc-network
|
||||
|
||||
@@ -67,21 +95,14 @@ services:
|
||||
container_name: mc-lyla
|
||||
environment:
|
||||
EULA: "TRUE"
|
||||
SERVER_NAME: "Lyla's World"
|
||||
GAMEMODE: survival
|
||||
DIFFICULTY: normal
|
||||
ALLOW_CHEATS: "true"
|
||||
ONLINE_MODE: "false"
|
||||
SERVER_PORT: "19132"
|
||||
LEVEL_NAME: "Lyla World"
|
||||
MAX_PLAYERS: "10"
|
||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
||||
OP_PERMISSION_LEVEL: "4"
|
||||
ports:
|
||||
- "${LYLA_PORT:-19134}:19132/udp"
|
||||
volumes:
|
||||
- lyla-data:/data
|
||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
|
||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
||||
@@ -89,8 +110,25 @@ services:
|
||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||
- ./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
|
||||
restart: unless-stopped
|
||||
mem_limit: 1500m
|
||||
memswap_limit: 2500m
|
||||
networks:
|
||||
- mc-network
|
||||
|
||||
@@ -99,21 +137,14 @@ services:
|
||||
container_name: mc-mya
|
||||
environment:
|
||||
EULA: "TRUE"
|
||||
SERVER_NAME: "Mya's World"
|
||||
GAMEMODE: survival
|
||||
DIFFICULTY: normal
|
||||
ALLOW_CHEATS: "true"
|
||||
ONLINE_MODE: "false"
|
||||
SERVER_PORT: "19132"
|
||||
LEVEL_NAME: "Mya World"
|
||||
MAX_PLAYERS: "10"
|
||||
DEFAULT_PLAYER_PERMISSION_LEVEL: operator
|
||||
OP_PERMISSION_LEVEL: "4"
|
||||
ports:
|
||||
- "${MYA_PORT:-19135}:19132/udp"
|
||||
volumes:
|
||||
- mya-data:/data
|
||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||
- ./hub-return-addon/hub_return_transfer_RP:/data/resource_packs/hub_return_transfer_RP
|
||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||
- ./addon/heyhe_pet_BP:/data/behavior_packs/heyhe_pet_BP
|
||||
@@ -121,8 +152,31 @@ services:
|
||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||
- ./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
|
||||
- ./trees-features-addon/trees_features_BP:/data/behavior_packs/trees_features_BP
|
||||
- ./trees-features-addon/trees_features_RP:/data/resource_packs/trees_features_RP
|
||||
- ./hemp-addon/hemp_BP:/data/behavior_packs/hemp_BP
|
||||
- ./hemp-addon/hemp_RP:/data/resource_packs/hemp_RP
|
||||
restart: unless-stopped
|
||||
mem_limit: 1500m
|
||||
memswap_limit: 2500m
|
||||
networks:
|
||||
- mc-network
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
"en_US"
|
||||
]
|
||||
|
After Width: | Height: | Size: 298 B |
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 372 B |
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 196 B |
@@ -240,8 +240,14 @@ function placeAllEggs(playerPos) {
|
||||
// Validate at least one stored egg block actually exists
|
||||
const stored = getEggPositions();
|
||||
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;
|
||||
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 {
|
||||
const block = overworld.getBlock({ x: stored[i].x, y: stored[i].y, z: stored[i].z });
|
||||
if (block && block.typeId.includes("glazed_terracotta")) found++;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"format_version": 2,
|
||||
"header": {
|
||||
"name": "Family Goals",
|
||||
"description": "Shared goals, family bank, loot share, pity timer, and PvP-off for the family survival world",
|
||||
"uuid": "f1a2b3c4-d5e6-4789-90ab-cdef12345001",
|
||||
"version": [1, 0, 6],
|
||||
"min_engine_version": [1, 21, 0]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"type": "script",
|
||||
"language": "javascript",
|
||||
"uuid": "f1a2b3c4-d5e6-4789-90ab-cdef12345002",
|
||||
"version": [1, 0, 6],
|
||||
"entry": "scripts/main.js"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"module_name": "@minecraft/server",
|
||||
"version": "1.17.0"
|
||||
},
|
||||
{
|
||||
"module_name": "@minecraft/server-ui",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
{
|
||||
"module_name": "@minecraft/server-admin",
|
||||
"version": "1.0.0-beta"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
// Family Goals Addon
|
||||
//
|
||||
// Lives in jamie's world (the shared family survival world). Provides:
|
||||
// • PvP-off (siblings can't hurt each other)
|
||||
// • Family roster ( !family setup adds online players )
|
||||
// • Family Bank ( !family bank registers a vanilla chest as shared )
|
||||
// • Five shared goals with per-player contribution tracking
|
||||
// • Loot share within 32 blocks (sibling helpers get a kill bonus)
|
||||
// • Pity timer (idle siblings get an encouragement bundle every 60 min)
|
||||
//
|
||||
// All persistent state lives in world dynamic properties so it survives
|
||||
// container restarts. No resource pack — vanilla items + titleraw + actionforms.
|
||||
|
||||
import { world, system, ItemStack } from "@minecraft/server";
|
||||
import { ActionFormData } from "@minecraft/server-ui";
|
||||
// We don't actually use @minecraft/server-admin, but importing from it flips
|
||||
// the beta-API gate for this pack so world.beforeEvents.chatSend resolves.
|
||||
// If we ever drop this import, fall back to afterEvents.chatSend (no cancel).
|
||||
import { variables } from "@minecraft/server-admin";
|
||||
void variables; // silence unused warning
|
||||
|
||||
// ─── Tunables ──────────────────────────────────────────────────
|
||||
const FAMILY_PROP = "family_roster_v1"; // { members: [<playerId>, ...] }
|
||||
const GOALS_PROP = "family_goals_v1"; // see initGoals() for shape
|
||||
const BANK_PROP = "family_bank_v1"; // { x, y, z, dim } | null
|
||||
const PROGRESS_PROP = "family_last_progress_at"; // per-player dyn prop, Date.now()
|
||||
|
||||
const VANILLA_CHEST = "minecraft:chest";
|
||||
const LOOT_SHARE_RADIUS = 32;
|
||||
const PITY_THRESHOLD_MS = 60 * 60 * 1000; // 60 min of no progress
|
||||
const PITY_TICK_INTERVAL = 6000; // poll every 5 min (20 tps)
|
||||
const NIGHTS_TICK_INTERVAL = 200; // every 10s
|
||||
|
||||
const GOAL_DEFS = [
|
||||
{ id: "logs", label: "Logs Chopped", target: 5000 },
|
||||
{ id: "diamonds", label: "Diamonds Mined", target: 50 },
|
||||
{ id: "mobs", label: "Monsters Defeated", target: 500 },
|
||||
{ id: "crops", label: "Crops Harvested", target: 1000 },
|
||||
{ id: "nights", label: "Nights Survived", target: 30 },
|
||||
];
|
||||
|
||||
const LOG_IDS = new Set([
|
||||
"minecraft:oak_log", "minecraft:spruce_log", "minecraft:birch_log",
|
||||
"minecraft:jungle_log", "minecraft:acacia_log", "minecraft:dark_oak_log",
|
||||
"minecraft:cherry_log", "minecraft:mangrove_log",
|
||||
"minecraft:stripped_oak_log", "minecraft:stripped_spruce_log",
|
||||
"minecraft:stripped_birch_log", "minecraft:stripped_jungle_log",
|
||||
"minecraft:stripped_acacia_log", "minecraft:stripped_dark_oak_log",
|
||||
"minecraft:stripped_cherry_log", "minecraft:stripped_mangrove_log",
|
||||
]);
|
||||
const DIAMOND_IDS = new Set([
|
||||
"minecraft:diamond_ore", "minecraft:deepslate_diamond_ore",
|
||||
]);
|
||||
// Block id → "growth"-like state value at full maturity
|
||||
const CROP_AGES = {
|
||||
"minecraft:wheat": 7,
|
||||
"minecraft:carrots": 7,
|
||||
"minecraft:potatoes": 7,
|
||||
"minecraft:beetroot": 3,
|
||||
};
|
||||
|
||||
// ─── In-memory mirror of dynamic properties ────────────────────
|
||||
let family = { members: [] };
|
||||
let goals = null;
|
||||
let bank = null;
|
||||
let lastDay = -1; // tracks day-rollover for the "nights survived" goal
|
||||
|
||||
// ─── Persistence ───────────────────────────────────────────────
|
||||
function loadState() {
|
||||
try {
|
||||
const f = world.getDynamicProperty(FAMILY_PROP);
|
||||
if (typeof f === "string") family = JSON.parse(f);
|
||||
} catch (_) { family = { members: [] }; }
|
||||
|
||||
try {
|
||||
const g = world.getDynamicProperty(GOALS_PROP);
|
||||
if (typeof g === "string") goals = JSON.parse(g);
|
||||
} catch (_) { goals = null; }
|
||||
if (!goals || !goals.goals) goals = initGoals();
|
||||
// Backfill any newly added goal definitions on upgrade
|
||||
for (const def of GOAL_DEFS) {
|
||||
if (!goals.goals[def.id]) {
|
||||
goals.goals[def.id] = { current: 0, target: def.target, completed: false, contributions: {} };
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const b = world.getDynamicProperty(BANK_PROP);
|
||||
if (typeof b === "string") bank = JSON.parse(b);
|
||||
} catch (_) { bank = null; }
|
||||
}
|
||||
|
||||
function initGoals() {
|
||||
const g = { goals: {} };
|
||||
for (const def of GOAL_DEFS) {
|
||||
g.goals[def.id] = { current: 0, target: def.target, completed: false, contributions: {} };
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
function saveFamily() {
|
||||
try { world.setDynamicProperty(FAMILY_PROP, JSON.stringify(family)); } catch (_) {}
|
||||
}
|
||||
function saveGoals() {
|
||||
try {
|
||||
const json = JSON.stringify(goals);
|
||||
world.setDynamicProperty(GOALS_PROP, json);
|
||||
const verify = world.getDynamicProperty(GOALS_PROP);
|
||||
console.warn(`[Family] saveGoals: wrote ${json.length} chars, readback typeof=${typeof verify} len=${typeof verify === "string" ? verify.length : "n/a"}`);
|
||||
} catch (e) { console.warn(`[Family] saveGoals FAILED: ${e}`); }
|
||||
}
|
||||
function saveBank() {
|
||||
try { world.setDynamicProperty(BANK_PROP, bank ? JSON.stringify(bank) : undefined); } catch (_) {}
|
||||
}
|
||||
|
||||
function isFamily(player) {
|
||||
if (!player || !family.members) return false;
|
||||
return family.members.includes(player.id);
|
||||
}
|
||||
|
||||
// ─── Goal mutation ─────────────────────────────────────────────
|
||||
function incrGoal(goalId, player, n = 1) {
|
||||
if (!isFamily(player)) return;
|
||||
const g = goals.goals[goalId];
|
||||
if (!g || g.completed) return;
|
||||
g.current += n;
|
||||
g.contributions[player.id] = (g.contributions[player.id] || 0) + n;
|
||||
try { player.setDynamicProperty(PROGRESS_PROP, Date.now()); } catch (_) {}
|
||||
|
||||
if (g.current >= g.target) {
|
||||
g.completed = true;
|
||||
g.current = g.target;
|
||||
fireGoalReward(goalId);
|
||||
}
|
||||
saveGoals();
|
||||
}
|
||||
|
||||
function fireGoalReward(goalId) {
|
||||
const def = GOAL_DEFS.find(d => d.id === goalId);
|
||||
const label = def ? def.label : goalId;
|
||||
for (const p of world.getAllPlayers()) {
|
||||
try {
|
||||
p.onScreenDisplay.setTitle(`§6✦ Family Goal Complete!`, {
|
||||
subtitle: `§e${label}`,
|
||||
fadeInDuration: 10,
|
||||
stayDuration: 80,
|
||||
fadeOutDuration: 20,
|
||||
});
|
||||
p.dimension.runCommand(`summon firework_rocket ${p.location.x} ${p.location.y + 2} ${p.location.z}`);
|
||||
// v1 trophy: a named gold ingot. Easy to tell apart in inventory.
|
||||
p.runCommand(`give @s gold_ingot 1`);
|
||||
} catch (_) {}
|
||||
}
|
||||
world.sendMessage(`§6[Family] §eGoal complete: §f${label}§e! §7Trophy gold awarded.`);
|
||||
}
|
||||
|
||||
// ─── Family roster command ─────────────────────────────────────
|
||||
function familySetup(player) {
|
||||
const online = world.getAllPlayers();
|
||||
const before = new Set(family.members);
|
||||
let added = 0;
|
||||
for (const p of online) {
|
||||
if (!before.has(p.id)) {
|
||||
family.members.push(p.id);
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
saveFamily();
|
||||
|
||||
const names = online.map(p => p.name).join(", ");
|
||||
if (added === 0) {
|
||||
player.sendMessage(`§7[Family] §fAll currently-online players are already in the roster (${family.members.length} members).`);
|
||||
} else {
|
||||
player.sendMessage(`§a[Family] §fAdded ${added} new member(s). Roster (${family.members.length}): §e${names}§f.`);
|
||||
world.sendMessage(`§a[Family] §fRoster updated by §e${player.name}§f.`);
|
||||
}
|
||||
}
|
||||
|
||||
function familyReset(player) {
|
||||
family = { members: [] };
|
||||
saveFamily();
|
||||
player.sendMessage("§c[Family] §fRoster cleared. Run §e!family setup§f again to repopulate.");
|
||||
}
|
||||
|
||||
// ─── Family Bank command ───────────────────────────────────────
|
||||
function familyBank(player) {
|
||||
if (!isFamily(player)) {
|
||||
player.sendMessage("§c[Family] §7Only family members can register the bank — run §e!family setup§7 first while everyone's online.");
|
||||
return;
|
||||
}
|
||||
let view;
|
||||
try {
|
||||
view = player.getBlockFromViewDirection({ maxDistance: 6 });
|
||||
} catch (e) {
|
||||
player.sendMessage(`§c[Family] §7Couldn't look at a block: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
const target = view?.block;
|
||||
if (!target || target.typeId !== VANILLA_CHEST) {
|
||||
player.sendMessage("§c[Family] §7Look at a vanilla chest within 6 blocks, then re-run §e!family bank§7.");
|
||||
return;
|
||||
}
|
||||
bank = {
|
||||
x: target.location.x,
|
||||
y: target.location.y,
|
||||
z: target.location.z,
|
||||
dim: target.dimension.id,
|
||||
};
|
||||
saveBank();
|
||||
player.sendMessage(`§a[Family] §fBank registered at §e${bank.x}, ${bank.y}, ${bank.z}§f. All family members can now open it.`);
|
||||
}
|
||||
|
||||
// ─── Bank gating (interact + break) ────────────────────────────
|
||||
function isBankAt(loc, dimId) {
|
||||
if (!bank) return false;
|
||||
return Math.floor(loc.x) === bank.x
|
||||
&& Math.floor(loc.y) === bank.y
|
||||
&& Math.floor(loc.z) === bank.z
|
||||
&& bank.dim === dimId;
|
||||
}
|
||||
|
||||
try {
|
||||
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (block.typeId !== VANILLA_CHEST) return;
|
||||
if (!isBankAt(block.location, block.dimension.id)) return;
|
||||
if (isFamily(event.player)) return;
|
||||
event.cancel = true;
|
||||
const playerRef = event.player;
|
||||
system.run(() => playerRef.sendMessage("§c[Family Bank] §7Only family members can open this chest."));
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Family] beforeEvents.playerInteractWithBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
try {
|
||||
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||
const block = event.block;
|
||||
if (block.typeId !== VANILLA_CHEST) return;
|
||||
if (!isBankAt(block.location, block.dimension.id)) return;
|
||||
|
||||
if (isFamily(event.player)) {
|
||||
// Family member breaking the bank → un-register so it stops being protected
|
||||
bank = null;
|
||||
saveBank();
|
||||
const p = event.player;
|
||||
system.run(() => p.sendMessage("§7[Family Bank] §fBank chest broken — un-registered. Place a new chest and run §e!family bank§f to re-register."));
|
||||
return;
|
||||
}
|
||||
event.cancel = true;
|
||||
const playerRef = event.player;
|
||||
system.run(() => playerRef.sendMessage("§c[Family Bank] §7Only family members can break this chest."));
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Family] beforeEvents.playerBreakBlock unavailable: ${e}`);
|
||||
}
|
||||
|
||||
// ─── PvP off (heal damage between siblings) ────────────────────
|
||||
// Bedrock has no server.properties pvp toggle — we hook the after-event
|
||||
// and immediately re-heal. Net visible effect: zero damage between players.
|
||||
world.afterEvents.entityHurt.subscribe((event) => {
|
||||
const source = event.damageSource?.damagingEntity;
|
||||
const victim = event.hurtEntity;
|
||||
if (!source || !victim) return;
|
||||
if (source.typeId !== "minecraft:player" || victim.typeId !== "minecraft:player") return;
|
||||
try {
|
||||
const health = victim.getComponent("minecraft:health");
|
||||
if (!health) return;
|
||||
const restored = Math.min(health.effectiveMax, health.currentValue + event.damage);
|
||||
health.setCurrentValue(restored);
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
// ─── Goal triggers ─────────────────────────────────────────────
|
||||
world.afterEvents.playerBreakBlock.subscribe((event) => {
|
||||
const player = event.player;
|
||||
if (!isFamily(player)) return;
|
||||
const id = event.brokenBlockPermutation?.type?.id;
|
||||
if (!id) return;
|
||||
|
||||
if (LOG_IDS.has(id)) {
|
||||
incrGoal("logs", player);
|
||||
return;
|
||||
}
|
||||
if (DIAMOND_IDS.has(id)) {
|
||||
incrGoal("diamonds", player);
|
||||
return;
|
||||
}
|
||||
if (id in CROP_AGES) {
|
||||
// Only fully grown crops count
|
||||
let states = null;
|
||||
try { states = event.brokenBlockPermutation.getAllStates(); } catch (_) {}
|
||||
const age = states && (states["growth"] ?? states["age"]);
|
||||
if (typeof age === "number" && age >= CROP_AGES[id]) {
|
||||
incrGoal("crops", player);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
world.afterEvents.entityDie.subscribe((event) => {
|
||||
const dead = event.deadEntity;
|
||||
if (!dead || dead.typeId === "minecraft:player") return;
|
||||
const source = event.damageSource?.damagingEntity;
|
||||
if (!source || source.typeId !== "minecraft:player") return;
|
||||
if (!isFamily(source)) return;
|
||||
|
||||
// Goal: monsters only
|
||||
let isMonster = false;
|
||||
try {
|
||||
const fam = dead.getComponent("minecraft:type_family");
|
||||
if (fam && typeof fam.hasTypeFamily === "function" && fam.hasTypeFamily("monster")) {
|
||||
isMonster = true;
|
||||
}
|
||||
} catch (_) {}
|
||||
if (isMonster) incrGoal("mobs", source);
|
||||
|
||||
// Loot share: every family member in 32 blocks (other than killer) gets cooked beef.
|
||||
// Sharing helpers, not real drop mirroring — keeps the drop pipeline untouched.
|
||||
try {
|
||||
const k = source.location;
|
||||
const r2 = LOOT_SHARE_RADIUS * LOOT_SHARE_RADIUS;
|
||||
for (const other of world.getAllPlayers()) {
|
||||
if (other.id === source.id) continue;
|
||||
if (!isFamily(other)) continue;
|
||||
if (other.dimension.id !== source.dimension.id) continue;
|
||||
const dx = other.location.x - k.x;
|
||||
const dy = other.location.y - k.y;
|
||||
const dz = other.location.z - k.z;
|
||||
if (dx*dx + dy*dy + dz*dz <= r2) {
|
||||
other.runCommand("give @s cooked_beef 2");
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
// ─── Nights survived ───────────────────────────────────────────
|
||||
system.runInterval(() => {
|
||||
let day;
|
||||
try { day = world.getDay(); } catch (_) { return; }
|
||||
if (typeof day !== "number") return;
|
||||
if (lastDay === -1) { lastDay = day; return; }
|
||||
if (day > lastDay) {
|
||||
const diff = day - lastDay;
|
||||
lastDay = day;
|
||||
for (const p of world.getAllPlayers()) {
|
||||
if (isFamily(p)) incrGoal("nights", p, diff);
|
||||
}
|
||||
}
|
||||
}, NIGHTS_TICK_INTERVAL);
|
||||
|
||||
// ─── Pity timer ────────────────────────────────────────────────
|
||||
system.runInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const p of world.getAllPlayers()) {
|
||||
if (!isFamily(p)) continue;
|
||||
|
||||
let last;
|
||||
try { last = p.getDynamicProperty(PROGRESS_PROP); } catch (_) { last = undefined; }
|
||||
if (typeof last !== "number") {
|
||||
// First time we've seen this family member contribute (or first session) — initialize, don't pity yet
|
||||
try { p.setDynamicProperty(PROGRESS_PROP, now); } catch (_) {}
|
||||
continue;
|
||||
}
|
||||
if (now - last < PITY_THRESHOLD_MS) continue;
|
||||
|
||||
// Drop encouragement bundle directly into inventory
|
||||
try {
|
||||
p.runCommand("give @s diamond 1");
|
||||
p.runCommand("give @s cooked_beef 16");
|
||||
p.runCommand("give @s iron_pickaxe 1");
|
||||
p.sendMessage("§6[Family] §fA little boost to keep you going! §7(family pity bundle)");
|
||||
p.setDynamicProperty(PROGRESS_PROP, now); // reset so we don't spam every 5 min
|
||||
} catch (_) {}
|
||||
}
|
||||
}, PITY_TICK_INTERVAL);
|
||||
|
||||
// ─── Goal display ──────────────────────────────────────────────
|
||||
function makeBar(pct) {
|
||||
const filled = Math.max(0, Math.min(10, Math.floor(pct / 10)));
|
||||
return "§a" + "▰".repeat(filled) + "§8" + "▰".repeat(10 - filled);
|
||||
}
|
||||
|
||||
async function showGoals(player, perPlayerMode) {
|
||||
const lines = [];
|
||||
for (const def of GOAL_DEFS) {
|
||||
const g = goals.goals[def.id];
|
||||
if (!g) continue;
|
||||
const cur = Math.min(g.current, g.target);
|
||||
const pct = Math.floor((cur / g.target) * 100);
|
||||
const bar = makeBar(pct);
|
||||
const status = g.completed ? " §a✔" : "";
|
||||
|
||||
if (perPlayerMode) {
|
||||
const my = g.contributions[player.id] || 0;
|
||||
const myPct = cur > 0 ? Math.floor((my / cur) * 100) : 0;
|
||||
lines.push(`§e${def.label}${status}\n§7 ${bar}§7 §f${cur}§7/§f${g.target}\n§7 You: §f${my}§7 (${myPct}% of progress)`);
|
||||
} else {
|
||||
lines.push(`§e${def.label}${status}\n§7 ${bar}§7 §f${cur}§7/§f${g.target}`);
|
||||
}
|
||||
}
|
||||
if (!lines.length) lines.push("§7No goals configured.");
|
||||
|
||||
const form = new ActionFormData()
|
||||
.title(perPlayerMode ? "My Goal Contributions" : "Family Goals")
|
||||
.body(lines.join("\n\n"))
|
||||
.button("§7OK");
|
||||
try { await form.show(player); } catch (_) {}
|
||||
}
|
||||
|
||||
// ─── Family menu (entered via scriptevent from hub-return compass) ─────
|
||||
function showRoster(player) {
|
||||
if (family.members.length === 0) {
|
||||
player.sendMessage("§7[Family] §fNo roster set yet — open §e🏆 Family§f from your compass and pick §eAdd me to the family§f.");
|
||||
return;
|
||||
}
|
||||
const names = world.getAllPlayers()
|
||||
.filter(p => family.members.includes(p.id))
|
||||
.map(p => p.name);
|
||||
player.sendMessage(`§a[Family] §fRoster: §e${family.members.length}§f members. Online: §e${names.join(", ") || "(none)"}§f.`);
|
||||
}
|
||||
|
||||
async function openFamilyMenu(player) {
|
||||
const inFamily = isFamily(player);
|
||||
const form = new ActionFormData()
|
||||
.title("🏆 Family")
|
||||
.body(`§7Roster: §f${family.members.length}§7 members${inFamily ? " §a(you're in)" : " §c(you're not)"}\n§7Bank: ${bank ? `§f${bank.x}, ${bank.y}, ${bank.z}` : "§cnot set"}`);
|
||||
|
||||
const actions = [];
|
||||
form.button("🎯 Family Goals"); actions.push("goals");
|
||||
form.button("📝 My Contributions"); actions.push("mygoals");
|
||||
form.button("👥 Show Roster"); actions.push("roster");
|
||||
form.button("➕ Add me / Add online players"); actions.push("setup");
|
||||
form.button("🏦 Register Family Bank (look at chest)"); actions.push("bank");
|
||||
form.button("♻ Reset Roster (danger)"); actions.push("reset");
|
||||
form.button("§7Cancel"); actions.push("cancel");
|
||||
|
||||
let res;
|
||||
try { res = await form.show(player); } catch (_) { return; }
|
||||
if (res.canceled || res.selection === undefined) return;
|
||||
const a = actions[res.selection];
|
||||
|
||||
if (a === "goals") return showGoals(player, false);
|
||||
if (a === "mygoals") return showGoals(player, true);
|
||||
if (a === "roster") return showRoster(player);
|
||||
if (a === "setup") return familySetup(player);
|
||||
if (a === "bank") return familyBank(player);
|
||||
if (a === "reset") return familyReset(player);
|
||||
}
|
||||
|
||||
// scriptEventReceive is the cross-pack dispatch surface. Hub-return's compass
|
||||
// menu fires `scriptevent family:menu` to enter our UI. Direct ids are also
|
||||
// handled so an opped player can run e.g. `/scriptevent family:setup`.
|
||||
try {
|
||||
system.afterEvents.scriptEventReceive.subscribe((event) => {
|
||||
const id = event.id;
|
||||
if (!id || !id.startsWith("family:")) return;
|
||||
const player = event.sourceEntity;
|
||||
if (!player || player.typeId !== "minecraft:player") return;
|
||||
switch (id) {
|
||||
case "family:menu": system.run(() => openFamilyMenu(player)); break;
|
||||
case "family:goals": system.run(() => showGoals(player, false)); break;
|
||||
case "family:mygoals": system.run(() => showGoals(player, true)); break;
|
||||
case "family:roster": showRoster(player); break;
|
||||
case "family:setup": familySetup(player); break;
|
||||
case "family:bank": familyBank(player); break;
|
||||
case "family:reset": familyReset(player); break;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`[Family] scriptEventReceive unavailable: ${e}`);
|
||||
}
|
||||
|
||||
// ─── Boot ──────────────────────────────────────────────────────
|
||||
system.run(() => {
|
||||
loadState();
|
||||
saveGoals(); // persist any backfilled goal defs from upgrade
|
||||
|
||||
world.sendMessage("§6[Family] §fLoaded. Right-click your §dHub Compass§f to find the §6🏆 Family§f menu.");
|
||||
});
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||