feat(postal): add postal service addon and bundle pending addon work
All checks were successful
Deploy Addons / deploy (push) Successful in 16s
- New postal-service-addon: per-player mailboxes + post-office send block (ActionForm recipient picker, offline notification queue, chunk-load retry via tickingarea) - Commit previously untracked private-chest, home-sign, keep-inventory addons and their docker-compose mounts - Deploy workflow: add postal + previously unwired addons to path filter and checkout list; drop easter-egg from deployment - enabled_packs.json: register postal UUIDs for Lyla + Mya Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@@ -7,8 +7,12 @@ on:
|
|||||||
- 'addon/**'
|
- 'addon/**'
|
||||||
- 'lobby-addon/**'
|
- 'lobby-addon/**'
|
||||||
- 'hub-return-addon/**'
|
- 'hub-return-addon/**'
|
||||||
- 'easter-egg-addon/**'
|
|
||||||
- 'village-evolution-addon/**'
|
- 'village-evolution-addon/**'
|
||||||
|
- 'monkey-addon/**'
|
||||||
|
- 'private-chest-addon/**'
|
||||||
|
- 'home-sign-addon/**'
|
||||||
|
- 'keep-inventory-addon/**'
|
||||||
|
- 'postal-service-addon/**'
|
||||||
- 'docker-compose.yml'
|
- 'docker-compose.yml'
|
||||||
- 'scripts/**'
|
- 'scripts/**'
|
||||||
|
|
||||||
@@ -26,6 +30,7 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
set -e
|
set -e
|
||||||
APP_DIR="$HOME/minecraft-multiworld"
|
APP_DIR="$HOME/minecraft-multiworld"
|
||||||
|
PATHS="addon/ lobby-addon/ hub-return-addon/ village-evolution-addon/ monkey-addon/ private-chest-addon/ home-sign-addon/ keep-inventory-addon/ postal-service-addon/ docker-compose.yml"
|
||||||
|
|
||||||
# First run: clone. Subsequent: pull.
|
# First run: clone. Subsequent: pull.
|
||||||
if [ ! -d "$APP_DIR/.git" ]; then
|
if [ ! -d "$APP_DIR/.git" ]; then
|
||||||
@@ -33,11 +38,11 @@ jobs:
|
|||||||
git init
|
git init
|
||||||
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ village-evolution-addon/ docker-compose.yml
|
git checkout -f origin/main -- $PATHS
|
||||||
else
|
else
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ village-evolution-addon/ docker-compose.yml
|
git checkout -f origin/main -- $PATHS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Recreate containers so any new docker-compose volume mounts are applied,
|
# Recreate containers so any new docker-compose volume mounts are applied,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"minecraft:entity": {
|
"minecraft:entity": {
|
||||||
"description": {
|
"description": {
|
||||||
"identifier": "silverlabs:anthrax_cat",
|
"identifier": "silverlabs:anthrax_cat",
|
||||||
"is_spawnable": false,
|
"is_spawnable": true,
|
||||||
"is_summonable": true,
|
"is_summonable": true,
|
||||||
"is_experimental": false
|
"is_experimental": false
|
||||||
},
|
},
|
||||||
@@ -39,18 +39,6 @@
|
|||||||
"minecraft:knockback_resistance": {
|
"minecraft:knockback_resistance": {
|
||||||
"value": 1.0
|
"value": 1.0
|
||||||
},
|
},
|
||||||
"minecraft:damage_sensor": {
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"cause": "all",
|
|
||||||
"deals_damage": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cause": "override",
|
|
||||||
"deals_damage": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"minecraft:health": {
|
"minecraft:health": {
|
||||||
"value": 20,
|
"value": 20,
|
||||||
"max": 20
|
"max": 20
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ services:
|
|||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||||
- ./monkey-addon/monkey_BP:/data/behavior_packs/monkey_BP
|
- ./monkey-addon/monkey_BP:/data/behavior_packs/monkey_BP
|
||||||
- ./monkey-addon/monkey_RP:/data/resource_packs/monkey_RP
|
- ./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
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
@@ -53,6 +60,13 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- jamie-data:/data
|
- jamie-data:/data
|
||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||||
|
- ./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
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
@@ -84,6 +98,13 @@ services:
|
|||||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||||
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
||||||
|
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
|
||||||
|
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
|
||||||
|
- ./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
|
||||||
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
@@ -116,6 +137,13 @@ services:
|
|||||||
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
- ./addon/anthrax_cat_BP:/data/behavior_packs/anthrax_cat_BP
|
||||||
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
- ./addon/anthrax_cat_RP:/data/resource_packs/anthrax_cat_RP
|
||||||
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
- ./village-evolution-addon/village_evolution_BP:/data/behavior_packs/village_evolution_BP
|
||||||
|
- ./private-chest-addon/private_chest_BP:/data/behavior_packs/private_chest_BP
|
||||||
|
- ./private-chest-addon/private_chest_RP:/data/resource_packs/private_chest_RP
|
||||||
|
- ./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
|
||||||
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
- ./village-evolution-addon/enabled_packs.json:/data/config/default/enabled_packs.json
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
27
home-sign-addon/home_sign_BP/blocks/home_sign.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:home_sign",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.decorations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 1.5
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 200.0
|
||||||
|
},
|
||||||
|
"minecraft:map_color": "#C0703A",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "home_sign",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
701
home-sign-addon/home_sign_BP/entities/cat.json
Normal file
@@ -0,0 +1,701 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.26.10",
|
||||||
|
"minecraft:entity": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "minecraft:cat",
|
||||||
|
"spawn_category": "creature",
|
||||||
|
"is_spawnable": true,
|
||||||
|
"is_summonable": true,
|
||||||
|
"properties": {
|
||||||
|
"minecraft:sound_variant": {
|
||||||
|
"type": "enum",
|
||||||
|
"values": ["default", "royal"],
|
||||||
|
"default": "default",
|
||||||
|
"client_sync": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"component_groups": {
|
||||||
|
"minecraft:cat_baby": {
|
||||||
|
"minecraft:is_baby": {},
|
||||||
|
"minecraft:scale": {
|
||||||
|
"value": 0.4
|
||||||
|
},
|
||||||
|
"minecraft:ageable": {
|
||||||
|
"duration": 1200,
|
||||||
|
"feed_items": ["fish", "salmon"],
|
||||||
|
"pause_growth_items": [ "golden_dandelion" ],
|
||||||
|
"reset_growth_items": [ "golden_dandelion" ],
|
||||||
|
"grow_up": {
|
||||||
|
"event": "minecraft:ageable_grow_up",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_adult": {
|
||||||
|
"minecraft:experience_reward": {
|
||||||
|
"on_bred": "Math.Random(1,7)",
|
||||||
|
"on_death": "query.last_hit_by_player ? Math.Random(1,3) : 0"
|
||||||
|
},
|
||||||
|
"minecraft:loot": {
|
||||||
|
"table": "loot_tables/entities/cat.json"
|
||||||
|
},
|
||||||
|
"minecraft:scale": {
|
||||||
|
"value": 0.8
|
||||||
|
},
|
||||||
|
"minecraft:leashable_to": {},
|
||||||
|
"minecraft:breedable": {
|
||||||
|
"require_tame": true,
|
||||||
|
"require_full_health": true,
|
||||||
|
"allow_sitting": true,
|
||||||
|
"breeds_with": {
|
||||||
|
"minecraft:cat": {}
|
||||||
|
},
|
||||||
|
"breed_items": ["fish", "salmon"]
|
||||||
|
},
|
||||||
|
"minecraft:behavior.breed": {
|
||||||
|
"priority": 3,
|
||||||
|
"speed_multiplier": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_wild": {
|
||||||
|
"minecraft:health": {
|
||||||
|
"value": 10,
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
"minecraft:tameable": {
|
||||||
|
"probability": 0.33,
|
||||||
|
"tame_items": ["fish", "salmon"],
|
||||||
|
"tame_event": {
|
||||||
|
"event": "minecraft:on_tame",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:rideable": {
|
||||||
|
"seat_count": 1,
|
||||||
|
"family_types": ["baby_undead"],
|
||||||
|
"seats": {
|
||||||
|
// This value results in zombies floating when riding baby cats,
|
||||||
|
// but switching to a different setup would break pre-existing mobs.
|
||||||
|
"position": [0.0, 0.35, 0.0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:behavior.nearest_attackable_target": {
|
||||||
|
"priority": 1,
|
||||||
|
"reselect_targets": true,
|
||||||
|
"within_radius": 16.0,
|
||||||
|
"entity_types": [
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"test": "is_family",
|
||||||
|
"subject": "other",
|
||||||
|
"value": "rabbit"
|
||||||
|
},
|
||||||
|
"max_dist": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"all_of": [
|
||||||
|
{
|
||||||
|
"test": "is_family",
|
||||||
|
"subject": "other",
|
||||||
|
"value": "baby_turtle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test": "in_water",
|
||||||
|
"subject": "other",
|
||||||
|
"operator": "!=",
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"max_dist": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:behavior.tempt": {
|
||||||
|
"priority": 5,
|
||||||
|
"speed_multiplier": 0.5,
|
||||||
|
"within_radius": 16,
|
||||||
|
"can_get_scared": true,
|
||||||
|
"tempt_sound": "tempt",
|
||||||
|
"sound_interval": [0, 100],
|
||||||
|
"items": ["fish", "salmon"]
|
||||||
|
},
|
||||||
|
"minecraft:behavior.avoid_mob_type": {
|
||||||
|
"priority": 6,
|
||||||
|
"entity_types": [
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"test": "is_family",
|
||||||
|
"subject": "other",
|
||||||
|
"value": "player"
|
||||||
|
},
|
||||||
|
"max_dist": 10,
|
||||||
|
"walk_speed_multiplier": 0.8,
|
||||||
|
"sprint_speed_multiplier": 1.33
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:behavior.move_towards_dwelling_restriction": {
|
||||||
|
"priority": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_tame": {
|
||||||
|
"minecraft:is_tamed": {},
|
||||||
|
"minecraft:health": {
|
||||||
|
"value": 20,
|
||||||
|
"max": 20
|
||||||
|
},
|
||||||
|
"minecraft:color": {
|
||||||
|
"value": 14
|
||||||
|
},
|
||||||
|
"minecraft:sittable": {},
|
||||||
|
"minecraft:is_dyeable": {
|
||||||
|
"interact_text": "action.interact.dye"
|
||||||
|
},
|
||||||
|
"minecraft:on_wake_with_owner": {
|
||||||
|
"event": "minecraft:pet_slept_with_owner",
|
||||||
|
"target": "self"
|
||||||
|
},
|
||||||
|
"minecraft:behavior.teleport_to_owner": {
|
||||||
|
"priority": 0,
|
||||||
|
"filters": {
|
||||||
|
"all_of": [
|
||||||
|
{ "test": "owner_distance", "operator": ">", "value": 12 },
|
||||||
|
{ "test": "is_panicking" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:behavior.pet_sleep_with_owner": {
|
||||||
|
"priority": 2,
|
||||||
|
"speed_multiplier": 1.2,
|
||||||
|
"search_radius": 10,
|
||||||
|
"search_height": 10,
|
||||||
|
"goal_radius": 1.0
|
||||||
|
},
|
||||||
|
"minecraft:behavior.stay_while_sitting": {
|
||||||
|
"priority": 3
|
||||||
|
},
|
||||||
|
"minecraft:behavior.tempt": {
|
||||||
|
"priority": 5,
|
||||||
|
"speed_multiplier": 0.5,
|
||||||
|
"within_radius": 16,
|
||||||
|
"items": ["fish", "salmon"]
|
||||||
|
},
|
||||||
|
"minecraft:behavior.ocelot_sit_on_block": {
|
||||||
|
"priority": 7,
|
||||||
|
"speed_multiplier": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_gift_for_owner": {
|
||||||
|
"minecraft:behavior.drop_item_for": {
|
||||||
|
"priority": 1,
|
||||||
|
"seconds_before_pickup": 0.0,
|
||||||
|
"cooldown": 0.25,
|
||||||
|
"drop_item_chance": 0.7,
|
||||||
|
"offering_distance": 5.0,
|
||||||
|
"minimum_teleport_distance": 2.0,
|
||||||
|
"max_head_look_at_height": 10.0,
|
||||||
|
"target_range": [5.0, 5.0, 5.0],
|
||||||
|
"teleport_offset": [0.0, 1.0, 0.0],
|
||||||
|
"time_of_day_range": {
|
||||||
|
"min": 0.74999,
|
||||||
|
"max": 0.8
|
||||||
|
},
|
||||||
|
"speed_multiplier": 1.0,
|
||||||
|
"search_range": 5,
|
||||||
|
"search_height": 2,
|
||||||
|
"search_count": 0,
|
||||||
|
"goal_radius": 1.0,
|
||||||
|
"entity_types": [
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"test": "is_family",
|
||||||
|
"subject": "other",
|
||||||
|
"value": "player"
|
||||||
|
},
|
||||||
|
"max_dist": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"loot_table": "loot_tables/entities/cat_gift.json",
|
||||||
|
"on_drop_attempt": {
|
||||||
|
"event": "minecraft:cat_gifted_owner",
|
||||||
|
"target": "self"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_white": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_tuxedo": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_red": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_siamese": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_british": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_calico": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_persian": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_ragdoll": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_tabby": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_black": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_jellie": {
|
||||||
|
"minecraft:variant": {
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"components": {
|
||||||
|
"minecraft:ambient_sound_interval": {
|
||||||
|
"value": 120,
|
||||||
|
"range": 60,
|
||||||
|
"event_name": "ambient"
|
||||||
|
},
|
||||||
|
"minecraft:offspring": {
|
||||||
|
"offspring_pairs": {
|
||||||
|
"minecraft:cat": "minecraft:cat"
|
||||||
|
},
|
||||||
|
"combine_parent_colors": true
|
||||||
|
},
|
||||||
|
"minecraft:spawn_egg_interaction": {},
|
||||||
|
"minecraft:leashable": {},
|
||||||
|
"minecraft:balloonable": {
|
||||||
|
"mass": 0.6
|
||||||
|
},
|
||||||
|
"minecraft:is_hidden_when_invisible": {},
|
||||||
|
"minecraft:attack_damage": {
|
||||||
|
"value": 4
|
||||||
|
},
|
||||||
|
"minecraft:nameable": {},
|
||||||
|
"minecraft:type_family": {
|
||||||
|
"family": ["cat", "mob"]
|
||||||
|
},
|
||||||
|
"minecraft:breathable": {
|
||||||
|
"total_supply": 15,
|
||||||
|
"suffocate_time": 0
|
||||||
|
},
|
||||||
|
"minecraft:collision_box": {
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 0.7
|
||||||
|
},
|
||||||
|
"minecraft:healable": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item": "fish",
|
||||||
|
"heal_amount": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item": "salmon",
|
||||||
|
"heal_amount": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:hurt_on_condition": {
|
||||||
|
"damage_conditions": [
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"test": "in_lava",
|
||||||
|
"subject": "self",
|
||||||
|
"operator": "==",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
"cause": "lava",
|
||||||
|
"damage_per_tick": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:movement": {
|
||||||
|
"value": 0.3
|
||||||
|
},
|
||||||
|
"minecraft:navigation.walk": {
|
||||||
|
"can_float": true,
|
||||||
|
"avoid_water": true,
|
||||||
|
"avoid_damage_blocks": true
|
||||||
|
},
|
||||||
|
"minecraft:movement.basic": {},
|
||||||
|
"minecraft:jump.static": {},
|
||||||
|
"minecraft:can_climb": {},
|
||||||
|
"minecraft:damage_sensor": {
|
||||||
|
"triggers": {
|
||||||
|
"cause": "fall",
|
||||||
|
"deals_damage": "no"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:dweller": {
|
||||||
|
"dwelling_type": "village",
|
||||||
|
"dweller_role": "passive",
|
||||||
|
"update_interval_base": 60,
|
||||||
|
"update_interval_variant": 40,
|
||||||
|
"can_find_poi": false,
|
||||||
|
"can_migrate": true,
|
||||||
|
"first_founding_reward": 0
|
||||||
|
},
|
||||||
|
"minecraft:despawn": {
|
||||||
|
"despawn_from_distance": {}
|
||||||
|
},
|
||||||
|
"minecraft:physics": {},
|
||||||
|
"minecraft:pushable_by_entity": {
|
||||||
|
},
|
||||||
|
"minecraft:pushable_by_block": {
|
||||||
|
},
|
||||||
|
"minecraft:conditional_bandwidth_optimization": {},
|
||||||
|
"minecraft:behavior.float": {
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
"minecraft:behavior.panic": {
|
||||||
|
"priority": 1,
|
||||||
|
"speed_multiplier": 1.25
|
||||||
|
},
|
||||||
|
"minecraft:behavior.mount_pathing": {
|
||||||
|
"priority": 1,
|
||||||
|
"speed_multiplier": 1.25,
|
||||||
|
"target_dist": 0,
|
||||||
|
"track_target": true
|
||||||
|
},
|
||||||
|
"minecraft:behavior.leap_at_target": {
|
||||||
|
"priority": 3,
|
||||||
|
"target_dist": 0.3
|
||||||
|
},
|
||||||
|
"minecraft:behavior.ocelotattack": {
|
||||||
|
"priority": 4,
|
||||||
|
"cooldown_time": 1.0,
|
||||||
|
"x_max_rotation": 30.0,
|
||||||
|
"y_max_head_rotation": 30.0,
|
||||||
|
"max_distance": 15.0,
|
||||||
|
"max_sneak_range": 15.0,
|
||||||
|
"max_sprint_range": 4.0,
|
||||||
|
"reach_multiplier": 2.0,
|
||||||
|
"sneak_speed_multiplier": 0.6,
|
||||||
|
"sprint_speed_multiplier": 1.33,
|
||||||
|
"walk_speed_multiplier": 0.8
|
||||||
|
},
|
||||||
|
"minecraft:behavior.random_stroll": {
|
||||||
|
"priority": 8,
|
||||||
|
"speed_multiplier": 0.8
|
||||||
|
},
|
||||||
|
"minecraft:behavior.look_at_player": {
|
||||||
|
"priority": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"events": {
|
||||||
|
"minecraft:entity_spawned": {
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"randomize": [
|
||||||
|
{
|
||||||
|
"weight": 3,
|
||||||
|
"remove": {},
|
||||||
|
"add": {
|
||||||
|
"component_groups": [
|
||||||
|
"minecraft:cat_adult",
|
||||||
|
"minecraft:cat_wild"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 1,
|
||||||
|
"remove": {},
|
||||||
|
"add": {
|
||||||
|
"component_groups": [
|
||||||
|
"minecraft:cat_baby",
|
||||||
|
"minecraft:cat_wild"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"randomize": [
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_white"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_tuxedo"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_red"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_siamese"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_british"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_calico"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_persian"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_ragdoll"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_tabby"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_black"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_jellie"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trigger": "minecraft:randomize_sound_variant"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:spawn_from_village": {
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"randomize": [
|
||||||
|
{
|
||||||
|
"weight": 3,
|
||||||
|
"trigger": "minecraft:spawn_wild_adult"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 1,
|
||||||
|
"trigger": "minecraft:spawn_wild_baby"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"randomize": [
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_tuxedo"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_red"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_siamese"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_white"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_british"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_calico"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_persian"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_ragdoll"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_tabby"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 15,
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_jellie"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:spawn_midnight_cat": {
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"trigger": "minecraft:spawn_wild_adult",
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_black"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:entity_born": {
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"test": "is_tamed"
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:spawn_tame_baby"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filters": {
|
||||||
|
"test": "is_tamed",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:spawn_wild_baby"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:spawn_wild_baby": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_baby", "minecraft:cat_wild"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:spawn_wild_adult": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_adult", "minecraft:cat_wild"]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:randomize_sound_variant"
|
||||||
|
},
|
||||||
|
"minecraft:spawn_tame_baby": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_baby", "minecraft:cat_tame"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:spawn_tame_adult": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_adult", "minecraft:cat_tame"]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:randomize_sound_variant"
|
||||||
|
},
|
||||||
|
"minecraft:ageable_grow_up": {
|
||||||
|
"remove": {
|
||||||
|
"component_groups": ["minecraft:cat_baby"]
|
||||||
|
},
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_adult"]
|
||||||
|
},
|
||||||
|
"trigger": "minecraft:randomize_sound_variant"
|
||||||
|
},
|
||||||
|
"minecraft:on_tame": {
|
||||||
|
"sequence": [
|
||||||
|
{
|
||||||
|
"remove": {
|
||||||
|
"component_groups": ["minecraft:cat_wild"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_tame"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"minecraft:pet_slept_with_owner": {
|
||||||
|
"add": {
|
||||||
|
"component_groups": ["minecraft:cat_gift_for_owner"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:cat_gifted_owner": {
|
||||||
|
"remove": {
|
||||||
|
"component_groups": ["minecraft:cat_gift_for_owner"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:randomize_sound_variant": {
|
||||||
|
"randomize": [
|
||||||
|
{
|
||||||
|
"weight": 1,
|
||||||
|
"set_property": {
|
||||||
|
"minecraft:sound_variant": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": 1,
|
||||||
|
"set_property": {
|
||||||
|
"minecraft:sound_variant": "royal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
home-sign-addon/home_sign_BP/manifest.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Home Sweet Home",
|
||||||
|
"description": "Defines a 32-block home zone for tamed cats; quieter cat meows",
|
||||||
|
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b80",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b81",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "script",
|
||||||
|
"language": "javascript",
|
||||||
|
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b82",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"entry": "scripts/main.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server",
|
||||||
|
"version": "1.17.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b83",
|
||||||
|
"version": [1, 0, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
home-sign-addon/home_sign_BP/recipes/home_sign.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shapeless": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:home_sign_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": { "context": "AlwaysUnlocked" },
|
||||||
|
"ingredients": [
|
||||||
|
{ "item": "minecraft:oak_sign" },
|
||||||
|
{ "item": "minecraft:red_dye" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:home_sign",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
191
home-sign-addon/home_sign_BP/scripts/main.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { world, system, ItemStack } from "@minecraft/server";
|
||||||
|
|
||||||
|
// ─── Constants ──────────────────────────────────────────────
|
||||||
|
const HOME_BLOCK = "silverlabs:home_sign";
|
||||||
|
const PROP_KEY = "home_signs_v1";
|
||||||
|
|
||||||
|
const HOME_RADIUS = 32; // user-facing "home area" size
|
||||||
|
const STRAY_RADIUS = 16; // cat is nudged back beyond this — keeps cats actually at home, not just "within shouting distance"
|
||||||
|
const RETURN_RADIUS_MIN = 2;
|
||||||
|
const RETURN_RADIUS_MAX = 10;
|
||||||
|
const TICK_INTERVAL = 40; // 2 s
|
||||||
|
|
||||||
|
// ─── State ──────────────────────────────────────────────────
|
||||||
|
// In-memory mirror: { "x,y,z,dim": { ownerId, ownerName } }
|
||||||
|
let homes = {};
|
||||||
|
|
||||||
|
function loadState() {
|
||||||
|
try {
|
||||||
|
const raw = world.getDynamicProperty(PROP_KEY);
|
||||||
|
if (raw && typeof raw === "string") {
|
||||||
|
homes = JSON.parse(raw);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Home Sweet Home] Failed to load state: ${e.message}`);
|
||||||
|
homes = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveState() {
|
||||||
|
try {
|
||||||
|
world.setDynamicProperty(PROP_KEY, JSON.stringify(homes));
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Home Sweet Home] Failed to save state: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyOf(loc, dimensionId) {
|
||||||
|
return `${Math.floor(loc.x)},${Math.floor(loc.y)},${Math.floor(loc.z)},${dimensionId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function claim(loc, dimensionId, player) {
|
||||||
|
homes[keyOf(loc, dimensionId)] = { ownerId: player.id, ownerName: player.name };
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function release(loc, dimensionId) {
|
||||||
|
delete homes[keyOf(loc, dimensionId)];
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOwner(loc, dimensionId) {
|
||||||
|
return homes[keyOf(loc, dimensionId)] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Placement: claim home zone ────────────────────────────
|
||||||
|
world.afterEvents.playerPlaceBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== HOME_BLOCK) return;
|
||||||
|
const player = event.player;
|
||||||
|
claim(block.location, block.dimension.id, player);
|
||||||
|
player.sendMessage(
|
||||||
|
`§a[Home Sweet Home] §7Home zone set — tamed cats will stay within ${HOME_RADIUS} blocks.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Break: owner-only ─────────────────────────────────────
|
||||||
|
try {
|
||||||
|
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== HOME_BLOCK) return;
|
||||||
|
const owner = getOwner(block.location, block.dimension.id);
|
||||||
|
if (!owner) return; // unclaimed — let it break
|
||||||
|
|
||||||
|
const player = event.player;
|
||||||
|
if (owner.ownerId !== player.id) {
|
||||||
|
event.cancel = true;
|
||||||
|
system.run(() =>
|
||||||
|
player.sendMessage(
|
||||||
|
`§c[Home Sweet Home] §7This sign belongs to §f${owner.ownerName}§7. You can't break it.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner break — drop the item back, clear the registry, remove the block.
|
||||||
|
event.cancel = true;
|
||||||
|
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||||
|
const dim = block.dimension;
|
||||||
|
system.run(() => {
|
||||||
|
try {
|
||||||
|
const dropPos = { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 };
|
||||||
|
dim.spawnItem(new ItemStack(HOME_BLOCK, 1), dropPos);
|
||||||
|
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`);
|
||||||
|
} catch (e) {
|
||||||
|
player.sendMessage(`§c[Home Sweet Home] Error during break: ${e.message}`);
|
||||||
|
}
|
||||||
|
release(loc, dim.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[Home Sweet Home] beforeEvents.playerBreakBlock unavailable: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Cat homing: nudge stray tamed cats back to the nearest home ──
|
||||||
|
function homesByDimension() {
|
||||||
|
const out = {};
|
||||||
|
for (const key in homes) {
|
||||||
|
const [xs, ys, zs, dim] = key.split(",");
|
||||||
|
if (!out[dim]) out[dim] = [];
|
||||||
|
out[dim].push({
|
||||||
|
x: parseInt(xs, 10) + 0.5,
|
||||||
|
y: parseInt(ys, 10) + 0.5,
|
||||||
|
z: parseInt(zs, 10) + 0.5,
|
||||||
|
ownerName: homes[key].ownerName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nearestHome(catLoc, homeList) {
|
||||||
|
let best = null;
|
||||||
|
let bestD2 = Infinity;
|
||||||
|
for (const h of homeList) {
|
||||||
|
const dx = catLoc.x - h.x;
|
||||||
|
const dy = catLoc.y - h.y;
|
||||||
|
const dz = catLoc.z - h.z;
|
||||||
|
const d2 = dx * dx + dy * dy + dz * dz;
|
||||||
|
if (d2 < bestD2) {
|
||||||
|
bestD2 = d2;
|
||||||
|
best = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { home: best, distance: Math.sqrt(bestD2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickReturnSpot(home) {
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
const r = RETURN_RADIUS_MIN + Math.random() * (RETURN_RADIUS_MAX - RETURN_RADIUS_MIN);
|
||||||
|
return {
|
||||||
|
x: home.x + Math.cos(angle) * r,
|
||||||
|
y: home.y,
|
||||||
|
z: home.z + Math.sin(angle) * r,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
system.runInterval(() => {
|
||||||
|
const byDim = homesByDimension();
|
||||||
|
const dimIds = Object.keys(byDim);
|
||||||
|
if (dimIds.length === 0) return;
|
||||||
|
|
||||||
|
for (const dimId of dimIds) {
|
||||||
|
let dim;
|
||||||
|
try {
|
||||||
|
dim = world.getDimension(dimId);
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cats;
|
||||||
|
try {
|
||||||
|
// Tamed cats only — vanilla cats gain the "tamed" family on tame.
|
||||||
|
cats = dim.getEntities({ type: "minecraft:cat", families: ["tamed"] });
|
||||||
|
} catch (e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const cat of cats) {
|
||||||
|
try {
|
||||||
|
const { home, distance } = nearestHome(cat.location, byDim[dimId]);
|
||||||
|
if (!home || distance <= STRAY_RADIUS) continue;
|
||||||
|
const target = pickReturnSpot(home);
|
||||||
|
const ok = cat.tryTeleport(target, { checkForBlocks: true });
|
||||||
|
if (!ok) {
|
||||||
|
// Retry tighter
|
||||||
|
cat.tryTeleport(
|
||||||
|
{ x: home.x, y: home.y, z: home.z },
|
||||||
|
{ checkForBlocks: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Skip bad cat, continue with next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, TICK_INTERVAL);
|
||||||
|
|
||||||
|
// ─── Boot ──────────────────────────────────────────────────
|
||||||
|
system.run(() => {
|
||||||
|
loadState();
|
||||||
|
world.sendMessage("§a[Home Sweet Home] §7Loaded — place a sign to claim a 32-block home zone.");
|
||||||
|
});
|
||||||
4
home-sign-addon/home_sign_RP/blocks.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"format_version": [1, 1, 0],
|
||||||
|
"silverlabs:home_sign": { "sound": "wood" }
|
||||||
|
}
|
||||||
17
home-sign-addon/home_sign_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Home Sweet Home Resources",
|
||||||
|
"description": "Texture and lang for the silverlabs:home_sign block",
|
||||||
|
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b83",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b84",
|
||||||
|
"version": [1, 0, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
home-sign-addon/home_sign_RP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 971 B |
1
home-sign-addon/home_sign_RP/texts/en_US.lang
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tile.silverlabs:home_sign.name=Home Sweet Home
|
||||||
BIN
home-sign-addon/home_sign_RP/textures/blocks/home_sign.png
Normal file
|
After Width: | Height: | Size: 268 B |
11
home-sign-addon/home_sign_RP/textures/terrain_texture.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "home_sign_RP",
|
||||||
|
"texture_name": "atlas.terrain",
|
||||||
|
"padding": 8,
|
||||||
|
"num_mip_levels": 4,
|
||||||
|
"texture_data": {
|
||||||
|
"home_sign": {
|
||||||
|
"textures": "textures/blocks/home_sign"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
keep-inventory-addon/keep_inventory_BP/manifest.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Keep Inventory",
|
||||||
|
"description": "Asserts gamerule keepInventory true on world load (Lyla's world)",
|
||||||
|
"uuid": "b4d72e91-8c43-4f1a-95d2-7e3b6c8a0d10",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"uuid": "b4d72e91-8c43-4f1a-95d2-7e3b6c8a0d11",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "script",
|
||||||
|
"language": "javascript",
|
||||||
|
"uuid": "b4d72e91-8c43-4f1a-95d2-7e3b6c8a0d12",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"entry": "scripts/main.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server",
|
||||||
|
"version": "1.17.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
keep-inventory-addon/keep_inventory_BP/scripts/main.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { world, system } from "@minecraft/server";
|
||||||
|
|
||||||
|
function assertKeepInventory() {
|
||||||
|
try {
|
||||||
|
world.getDimension("overworld").runCommand("gamerule keepInventory true");
|
||||||
|
} catch (e) {
|
||||||
|
// Non-fatal — chunk-load races, etc. The next interval will retry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
system.run(() => {
|
||||||
|
assertKeepInventory();
|
||||||
|
world.sendMessage("§a[Keep Inventory] §7keepInventory enforced — items stay on death.");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-assert every 10 minutes so anything that toggles it off (manual op, world reset, etc.) gets corrected.
|
||||||
|
system.runInterval(assertKeepInventory, 12000);
|
||||||
27
postal-service-addon/postal_service_BP/blocks/mailbox.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:mailbox",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.chest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 2.0
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:map_color": "#C83232",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "mailbox",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:post_office",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.chest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 2.5
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:map_color": "#8B5A2B",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "post_office",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
postal-service-addon/postal_service_BP/manifest.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Postal Service",
|
||||||
|
"description": "Player-to-player mail: personal mailboxes and a post office send block",
|
||||||
|
"uuid": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b60",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"uuid": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b61",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "script",
|
||||||
|
"language": "javascript",
|
||||||
|
"uuid": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b62",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"entry": "scripts/main.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server",
|
||||||
|
"version": "1.17.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server-ui",
|
||||||
|
"version": "1.3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b63",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
postal-service-addon/postal_service_BP/recipes/mailbox.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shapeless": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:mailbox_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": { "context": "AlwaysUnlocked" },
|
||||||
|
"ingredients": [
|
||||||
|
{ "item": "minecraft:chest" },
|
||||||
|
{ "item": "minecraft:gold_ingot" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:mailbox",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shaped": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:post_office_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": { "context": "AlwaysUnlocked" },
|
||||||
|
"pattern": [
|
||||||
|
"III",
|
||||||
|
"PCP",
|
||||||
|
"PPP"
|
||||||
|
],
|
||||||
|
"key": {
|
||||||
|
"I": { "item": "minecraft:iron_ingot" },
|
||||||
|
"P": { "item": "minecraft:oak_planks" },
|
||||||
|
"C": { "item": "minecraft:chest" }
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:post_office",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
390
postal-service-addon/postal_service_BP/scripts/main.js
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
import { world, system, ItemStack } from "@minecraft/server";
|
||||||
|
import { ActionFormData, MessageFormData } from "@minecraft/server-ui";
|
||||||
|
|
||||||
|
// ─── Constants ──────────────────────────────────────────────
|
||||||
|
const MAILBOX_BLOCK = "silverlabs:mailbox";
|
||||||
|
const POST_OFFICE_BLOCK = "silverlabs:post_office";
|
||||||
|
const VANILLA_CHEST = "minecraft:chest";
|
||||||
|
const PROP_KEY = "postal_state_v1";
|
||||||
|
|
||||||
|
// ─── State ──────────────────────────────────────────────────
|
||||||
|
// mailboxes: { "x,y,z,dim": { ownerId, ownerName } }
|
||||||
|
// registry: { [ownerId]: { name, x, y, z, dim } } — reverse lookup for recipient picker
|
||||||
|
// pending: { [ownerId]: [{ from, itemSummary, ts }] } — queued offline notifications
|
||||||
|
let state = { mailboxes: {}, registry: {}, pending: {} };
|
||||||
|
|
||||||
|
function loadState() {
|
||||||
|
try {
|
||||||
|
const raw = world.getDynamicProperty(PROP_KEY);
|
||||||
|
if (raw && typeof raw === "string") {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
state = {
|
||||||
|
mailboxes: parsed.mailboxes || {},
|
||||||
|
registry: parsed.registry || {},
|
||||||
|
pending: parsed.pending || {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Postal] Failed to load state: ${e.message}`);
|
||||||
|
state = { mailboxes: {}, registry: {}, pending: {} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveState() {
|
||||||
|
try {
|
||||||
|
world.setDynamicProperty(PROP_KEY, JSON.stringify(state));
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Postal] Failed to save state: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyOf(loc, dimensionId) {
|
||||||
|
return `${Math.floor(loc.x)},${Math.floor(loc.y)},${Math.floor(loc.z)},${dimensionId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMailboxOwner(loc, dimensionId) {
|
||||||
|
return state.mailboxes[keyOf(loc, dimensionId)] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function claimMailbox(loc, dimensionId, player) {
|
||||||
|
const k = keyOf(loc, dimensionId);
|
||||||
|
state.mailboxes[k] = { ownerId: player.id, ownerName: player.name };
|
||||||
|
state.registry[player.id] = {
|
||||||
|
name: player.name,
|
||||||
|
x: Math.floor(loc.x),
|
||||||
|
y: Math.floor(loc.y),
|
||||||
|
z: Math.floor(loc.z),
|
||||||
|
dim: dimensionId,
|
||||||
|
};
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseMailbox(loc, dimensionId) {
|
||||||
|
const k = keyOf(loc, dimensionId);
|
||||||
|
const entry = state.mailboxes[k];
|
||||||
|
delete state.mailboxes[k];
|
||||||
|
if (entry && state.registry[entry.ownerId]) {
|
||||||
|
const reg = state.registry[entry.ownerId];
|
||||||
|
if (reg.x === Math.floor(loc.x) && reg.y === Math.floor(loc.y) && reg.z === Math.floor(loc.z) && reg.dim === dimensionId) {
|
||||||
|
delete state.registry[entry.ownerId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasClaim(playerId) {
|
||||||
|
return !!state.registry[playerId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Chest facing from player rotation ──────────────────────
|
||||||
|
function chestFacing(yaw) {
|
||||||
|
let y = yaw;
|
||||||
|
while (y > 180) y -= 360;
|
||||||
|
while (y < -180) y += 360;
|
||||||
|
if (y >= -45 && y < 45) return "north";
|
||||||
|
if (y >= 45 && y < 135) return "east";
|
||||||
|
if (y >= -135 && y < -45) return "west";
|
||||||
|
return "south";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Mailbox placement ──────────────────────────────────────
|
||||||
|
world.afterEvents.playerPlaceBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== MAILBOX_BLOCK) return;
|
||||||
|
|
||||||
|
const player = event.player;
|
||||||
|
const loc = block.location;
|
||||||
|
const dim = block.dimension;
|
||||||
|
|
||||||
|
if (hasClaim(player.id)) {
|
||||||
|
// Revert placement and refund the item
|
||||||
|
try {
|
||||||
|
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`);
|
||||||
|
dim.spawnItem(new ItemStack(MAILBOX_BLOCK, 1), {
|
||||||
|
x: loc.x + 0.5,
|
||||||
|
y: loc.y + 0.5,
|
||||||
|
z: loc.z + 0.5,
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
player.sendMessage(`§c[Postal] §7You already have a mailbox in this world.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const facing = chestFacing(player.getRotation().y);
|
||||||
|
try {
|
||||||
|
dim.runCommand(
|
||||||
|
`setblock ${loc.x} ${loc.y} ${loc.z} chest ["minecraft:cardinal_direction":"${facing}"]`
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} chest`);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
claimMailbox(loc, dim.id, player);
|
||||||
|
player.sendMessage(`§6[Postal] §7Mailbox locked to you. Only you can open or break it.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Interact: gate mailbox opening for non-owners ──────────
|
||||||
|
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (!block) return;
|
||||||
|
|
||||||
|
// Post-office block: open send UI (cancel default interact)
|
||||||
|
if (block.typeId === POST_OFFICE_BLOCK) {
|
||||||
|
event.cancel = true;
|
||||||
|
const player = event.player;
|
||||||
|
system.run(() => openSendForm(player));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vanilla chest: gate if it's a claimed mailbox
|
||||||
|
if (block.typeId !== VANILLA_CHEST) return;
|
||||||
|
const owner = getMailboxOwner(block.location, block.dimension.id);
|
||||||
|
if (!owner) return;
|
||||||
|
if (owner.ownerId === event.player.id) return;
|
||||||
|
event.cancel = true;
|
||||||
|
const playerRef = event.player;
|
||||||
|
system.run(() =>
|
||||||
|
playerRef.sendMessage(`§c[Postal] §7This mailbox belongs to §f${owner.ownerName}§7.`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Break: protect mailboxes; owner break returns custom item ──
|
||||||
|
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== VANILLA_CHEST) return;
|
||||||
|
const owner = getMailboxOwner(block.location, block.dimension.id);
|
||||||
|
if (!owner) return;
|
||||||
|
|
||||||
|
const player = event.player;
|
||||||
|
|
||||||
|
if (owner.ownerId !== player.id) {
|
||||||
|
event.cancel = true;
|
||||||
|
system.run(() =>
|
||||||
|
player.sendMessage(`§c[Postal] §7This mailbox belongs to §f${owner.ownerName}§7. You can't break it.`)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.cancel = true;
|
||||||
|
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||||
|
const dim = block.dimension;
|
||||||
|
|
||||||
|
system.run(() => {
|
||||||
|
try {
|
||||||
|
const inv = dim.getBlock(loc)?.getComponent("inventory");
|
||||||
|
const container = inv?.container;
|
||||||
|
const dropPos = { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 };
|
||||||
|
if (container) {
|
||||||
|
for (let i = 0; i < container.size; i++) {
|
||||||
|
const item = container.getItem(i);
|
||||||
|
if (item) {
|
||||||
|
dim.spawnItem(item, dropPos);
|
||||||
|
container.setItem(i, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dim.spawnItem(new ItemStack(MAILBOX_BLOCK, 1), dropPos);
|
||||||
|
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`);
|
||||||
|
} catch (e) {
|
||||||
|
player.sendMessage(`§c[Postal] Error during break: ${e.message}`);
|
||||||
|
}
|
||||||
|
releaseMailbox(loc, dim.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Post Office: send form ─────────────────────────────────
|
||||||
|
function getHeldItem(player) {
|
||||||
|
try {
|
||||||
|
const inv = player.getComponent("inventory")?.container;
|
||||||
|
if (!inv) return { item: undefined, slot: -1 };
|
||||||
|
const slot = player.selectedSlotIndex;
|
||||||
|
const item = inv.getItem(slot);
|
||||||
|
return { item, slot, inv };
|
||||||
|
} catch (_) {
|
||||||
|
return { item: undefined, slot: -1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function itemSummary(item) {
|
||||||
|
const niceName = item.typeId.replace(/^minecraft:/, "").replace(/_/g, " ");
|
||||||
|
return `${item.amount} × ${niceName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openSendForm(player) {
|
||||||
|
const { item, slot, inv } = getHeldItem(player);
|
||||||
|
if (!item) {
|
||||||
|
player.sendMessage(`§c[Postal] §7Hold the item you want to send in your hotbar first.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = [];
|
||||||
|
for (const [ownerId, reg] of Object.entries(state.registry)) {
|
||||||
|
if (ownerId === player.id) continue;
|
||||||
|
candidates.push({ ownerId, reg });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
player.sendMessage(`§c[Postal] §7No other players have claimed a mailbox yet.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = new ActionFormData()
|
||||||
|
.title("Post Office")
|
||||||
|
.body(`Sending §f${itemSummary(item)}§r\n\nChoose a recipient:`);
|
||||||
|
for (const c of candidates) form.button(c.reg.name);
|
||||||
|
form.button("§cCancel");
|
||||||
|
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await form.show(player);
|
||||||
|
} catch (_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.canceled || response.selection === undefined) return;
|
||||||
|
if (response.selection >= candidates.length) return; // cancel button
|
||||||
|
|
||||||
|
const chosen = candidates[response.selection];
|
||||||
|
|
||||||
|
const confirm = new MessageFormData()
|
||||||
|
.title("Confirm Send")
|
||||||
|
.body(`Send §f${itemSummary(item)}§r\nto §f${chosen.reg.name}§r?`)
|
||||||
|
.button1("Send")
|
||||||
|
.button2("Cancel");
|
||||||
|
|
||||||
|
let conf;
|
||||||
|
try {
|
||||||
|
conf = await confirm.show(player);
|
||||||
|
} catch (_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (conf.canceled || conf.selection !== 0) return;
|
||||||
|
|
||||||
|
// Re-fetch held item in case it changed while form was open
|
||||||
|
const fresh = getHeldItem(player);
|
||||||
|
if (!fresh.item || fresh.item.typeId !== item.typeId || fresh.item.amount !== item.amount) {
|
||||||
|
player.sendMessage(`§c[Postal] §7Held item changed — send cancelled.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deliver(player, chosen.ownerId, chosen.reg, fresh.item, fresh.slot, fresh.inv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Delivery ───────────────────────────────────────────────
|
||||||
|
function deliver(senderPlayer, recipientId, reg, itemStack, slot, inv, retry = 0) {
|
||||||
|
const dim = world.getDimension(reg.dim);
|
||||||
|
const loc = { x: reg.x, y: reg.y, z: reg.z };
|
||||||
|
let block;
|
||||||
|
try {
|
||||||
|
block = dim.getBlock(loc);
|
||||||
|
} catch (_) {
|
||||||
|
block = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!block) {
|
||||||
|
if (retry >= 1) {
|
||||||
|
senderPlayer.sendMessage(
|
||||||
|
`§c[Postal] §7Couldn't reach §f${reg.name}§7's mailbox (chunk not loaded). Try again later.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Force chunk load briefly, then retry
|
||||||
|
const taName = `postal_tmp_${recipientId}`.replace(/[^a-zA-Z0-9_]/g, "_").slice(0, 20);
|
||||||
|
try {
|
||||||
|
dim.runCommand(
|
||||||
|
`tickingarea add ${reg.x} ${reg.y} ${reg.z} ${reg.x} ${reg.y} ${reg.z} ${taName}`
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
system.runTimeout(() => {
|
||||||
|
deliver(senderPlayer, recipientId, reg, itemStack, slot, inv, retry + 1);
|
||||||
|
try {
|
||||||
|
dim.runCommand(`tickingarea remove ${taName}`);
|
||||||
|
} catch (_) {}
|
||||||
|
}, 20);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.typeId !== VANILLA_CHEST) {
|
||||||
|
// Mailbox was destroyed externally — clean up registry
|
||||||
|
releaseMailbox(loc, reg.dim);
|
||||||
|
senderPlayer.sendMessage(
|
||||||
|
`§c[Postal] §f${reg.name}§7's mailbox is missing. Send cancelled.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = getMailboxOwner(loc, reg.dim);
|
||||||
|
if (!owner || owner.ownerId !== recipientId) {
|
||||||
|
releaseMailbox(loc, reg.dim);
|
||||||
|
senderPlayer.sendMessage(
|
||||||
|
`§c[Postal] §f${reg.name}§7's mailbox is no longer claimed. Send cancelled.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = block.getComponent("inventory")?.container;
|
||||||
|
if (!container) {
|
||||||
|
senderPlayer.sendMessage(`§c[Postal] §7Couldn't access the mailbox contents.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftover = container.addItem(itemStack);
|
||||||
|
|
||||||
|
// Remove the full stack from sender's inventory first
|
||||||
|
try {
|
||||||
|
inv.setItem(slot, undefined);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// Refund leftover (mailbox full) back to sender
|
||||||
|
if (leftover) {
|
||||||
|
const p = senderPlayer.location;
|
||||||
|
try {
|
||||||
|
senderPlayer.dimension.spawnItem(leftover, p);
|
||||||
|
} catch (_) {}
|
||||||
|
senderPlayer.sendMessage(
|
||||||
|
`§c[Postal] §f${reg.name}§7's mailbox is full — partial delivery. Leftover dropped at your feet.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const summary = itemSummary(itemStack);
|
||||||
|
senderPlayer.sendMessage(`§6[Postal] §7Sent §f${summary}§7 to §f${reg.name}§7.`);
|
||||||
|
|
||||||
|
// Notify recipient
|
||||||
|
const onlineRecipient = world.getAllPlayers().find((p) => p.id === recipientId);
|
||||||
|
if (onlineRecipient) {
|
||||||
|
onlineRecipient.sendMessage(`§6[Postal] §7You've got mail from §f${senderPlayer.name}§7! (${summary})`);
|
||||||
|
} else {
|
||||||
|
if (!state.pending[recipientId]) state.pending[recipientId] = [];
|
||||||
|
state.pending[recipientId].push({
|
||||||
|
from: senderPlayer.name,
|
||||||
|
itemSummary: summary,
|
||||||
|
ts: Date.now(),
|
||||||
|
});
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Flush pending notifications on login ───────────────────
|
||||||
|
world.afterEvents.playerSpawn.subscribe((event) => {
|
||||||
|
if (!event.initialSpawn) return;
|
||||||
|
const player = event.player;
|
||||||
|
const queue = state.pending[player.id];
|
||||||
|
if (!queue || queue.length === 0) return;
|
||||||
|
|
||||||
|
system.runTimeout(() => {
|
||||||
|
player.sendMessage(`§6[Postal] §7You have §f${queue.length}§7 new mail notification(s):`);
|
||||||
|
for (const n of queue) {
|
||||||
|
const when = new Date(n.ts).toISOString().replace("T", " ").slice(0, 16);
|
||||||
|
player.sendMessage(`§7[${when}] §7from §f${n.from}§7 — ${n.itemSummary}`);
|
||||||
|
}
|
||||||
|
delete state.pending[player.id];
|
||||||
|
saveState();
|
||||||
|
}, 40);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Boot ──────────────────────────────────────────────────
|
||||||
|
system.run(() => {
|
||||||
|
loadState();
|
||||||
|
world.sendMessage("§6[Postal] §7Postal service loaded.");
|
||||||
|
});
|
||||||
5
postal-service-addon/postal_service_RP/blocks.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"format_version": [1, 1, 0],
|
||||||
|
"silverlabs:mailbox": { "sound": "stone" },
|
||||||
|
"silverlabs:post_office": { "sound": "wood" }
|
||||||
|
}
|
||||||
17
postal-service-addon/postal_service_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Postal Service Resources",
|
||||||
|
"description": "Textures and lang for silverlabs:mailbox and silverlabs:post_office",
|
||||||
|
"uuid": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b63",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b64",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
postal-service-addon/postal_service_RP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 971 B |
2
postal-service-addon/postal_service_RP/texts/en_US.lang
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
tile.silverlabs:mailbox.name=Mailbox
|
||||||
|
tile.silverlabs:post_office.name=Post Office
|
||||||
|
After Width: | Height: | Size: 317 B |
|
After Width: | Height: | Size: 317 B |
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "postal_service_RP",
|
||||||
|
"texture_name": "atlas.terrain",
|
||||||
|
"padding": 8,
|
||||||
|
"num_mip_levels": 4,
|
||||||
|
"texture_data": {
|
||||||
|
"mailbox": {
|
||||||
|
"textures": "textures/blocks/mailbox"
|
||||||
|
},
|
||||||
|
"post_office": {
|
||||||
|
"textures": "textures/blocks/post_office"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:block": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:private_chest",
|
||||||
|
"menu_category": {
|
||||||
|
"category": "items",
|
||||||
|
"group": "itemGroup.name.chest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:destructible_by_mining": {
|
||||||
|
"seconds_to_destroy": 2.5
|
||||||
|
},
|
||||||
|
"minecraft:destructible_by_explosion": {
|
||||||
|
"explosion_resistance": 1200.0
|
||||||
|
},
|
||||||
|
"minecraft:map_color": "#B0B0B0",
|
||||||
|
"minecraft:material_instances": {
|
||||||
|
"*": {
|
||||||
|
"texture": "private_chest",
|
||||||
|
"render_method": "opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
private-chest-addon/private_chest_BP/manifest.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Private Chests",
|
||||||
|
"description": "Owner-locked chests — only the placer can open or break them",
|
||||||
|
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b50",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b51",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "script",
|
||||||
|
"language": "javascript",
|
||||||
|
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b52",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"entry": "scripts/main.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server",
|
||||||
|
"version": "1.17.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b53",
|
||||||
|
"version": [1, 0, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.21.0",
|
||||||
|
"minecraft:recipe_shapeless": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "silverlabs:private_chest_recipe"
|
||||||
|
},
|
||||||
|
"tags": ["crafting_table"],
|
||||||
|
"unlock": { "context": "AlwaysUnlocked" },
|
||||||
|
"ingredients": [
|
||||||
|
{ "item": "minecraft:chest" },
|
||||||
|
{ "item": "minecraft:iron_ingot" }
|
||||||
|
],
|
||||||
|
"result": {
|
||||||
|
"item": "silverlabs:private_chest",
|
||||||
|
"count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
158
private-chest-addon/private_chest_BP/scripts/main.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { world, system, ItemStack } from "@minecraft/server";
|
||||||
|
|
||||||
|
// ─── Constants ──────────────────────────────────────────────
|
||||||
|
const PRIVATE_BLOCK = "silverlabs:private_chest";
|
||||||
|
const VANILLA_CHEST = "minecraft:chest";
|
||||||
|
const PROP_KEY = "private_chests_v1";
|
||||||
|
|
||||||
|
// ─── State ──────────────────────────────────────────────────
|
||||||
|
// In-memory mirror of dynamic property: { "x,y,z,dim": { ownerId, ownerName } }
|
||||||
|
let chests = {};
|
||||||
|
|
||||||
|
function loadState() {
|
||||||
|
try {
|
||||||
|
const raw = world.getDynamicProperty(PROP_KEY);
|
||||||
|
if (raw && typeof raw === "string") {
|
||||||
|
chests = JSON.parse(raw);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Private Chest] Failed to load state: ${e.message}`);
|
||||||
|
chests = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveState() {
|
||||||
|
try {
|
||||||
|
world.setDynamicProperty(PROP_KEY, JSON.stringify(chests));
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Private Chest] Failed to save state: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyOf(loc, dimensionId) {
|
||||||
|
return `${Math.floor(loc.x)},${Math.floor(loc.y)},${Math.floor(loc.z)},${dimensionId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function claim(loc, dimensionId, player) {
|
||||||
|
chests[keyOf(loc, dimensionId)] = { ownerId: player.id, ownerName: player.name };
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function release(loc, dimensionId) {
|
||||||
|
delete chests[keyOf(loc, dimensionId)];
|
||||||
|
saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOwner(loc, dimensionId) {
|
||||||
|
return chests[keyOf(loc, dimensionId)] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Chest facing from player rotation ──────────────────────
|
||||||
|
// Bedrock yaw: 0=south, 90=west, 180=north, -90=east. Chest front faces the player.
|
||||||
|
function chestFacing(yaw) {
|
||||||
|
let y = yaw;
|
||||||
|
while (y > 180) y -= 360;
|
||||||
|
while (y < -180) y += 360;
|
||||||
|
if (y >= -45 && y < 45) return "north"; // player facing south
|
||||||
|
if (y >= 45 && y < 135) return "east"; // player facing west
|
||||||
|
if (y >= -135 && y < -45) return "west"; // player facing east
|
||||||
|
return "south"; // player facing north
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Placement: swap custom block → vanilla chest + claim ──
|
||||||
|
world.afterEvents.playerPlaceBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== PRIVATE_BLOCK) return;
|
||||||
|
|
||||||
|
const player = event.player;
|
||||||
|
const loc = block.location;
|
||||||
|
const dim = block.dimension;
|
||||||
|
const facing = chestFacing(player.getRotation().y);
|
||||||
|
|
||||||
|
try {
|
||||||
|
dim.runCommand(
|
||||||
|
`setblock ${loc.x} ${loc.y} ${loc.z} chest ["minecraft:cardinal_direction":"${facing}"]`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Older block-state syntax fallback (rare)
|
||||||
|
try {
|
||||||
|
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} chest`);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
claim(loc, dim.id, player);
|
||||||
|
player.sendMessage(`§6[Private Chest] §7Locked to you. Only you can open or break it.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Interact: block opening for non-owners ─────────────────
|
||||||
|
try {
|
||||||
|
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== VANILLA_CHEST) return;
|
||||||
|
const owner = getOwner(block.location, block.dimension.id);
|
||||||
|
if (!owner) return;
|
||||||
|
if (owner.ownerId === event.player.id) return;
|
||||||
|
event.cancel = true;
|
||||||
|
const playerRef = event.player;
|
||||||
|
system.run(() =>
|
||||||
|
playerRef.sendMessage(`§c[Private Chest] §7This chest belongs to §f${owner.ownerName}§7.`)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[Private Chest] beforeEvents.playerInteractWithBlock unavailable: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Break: protect for non-owners; owner break drops the custom item back ──
|
||||||
|
try {
|
||||||
|
world.beforeEvents.playerBreakBlock.subscribe((event) => {
|
||||||
|
const block = event.block;
|
||||||
|
if (block.typeId !== VANILLA_CHEST) return;
|
||||||
|
const owner = getOwner(block.location, block.dimension.id);
|
||||||
|
if (!owner) return;
|
||||||
|
|
||||||
|
const player = event.player;
|
||||||
|
|
||||||
|
if (owner.ownerId !== player.id) {
|
||||||
|
event.cancel = true;
|
||||||
|
system.run(() =>
|
||||||
|
player.sendMessage(`§c[Private Chest] §7This chest belongs to §f${owner.ownerName}§7. You can't break it.`)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner breaking — cancel default drop, manually eject contents + return the custom item.
|
||||||
|
event.cancel = true;
|
||||||
|
const loc = { x: block.location.x, y: block.location.y, z: block.location.z };
|
||||||
|
const dim = block.dimension;
|
||||||
|
|
||||||
|
system.run(() => {
|
||||||
|
try {
|
||||||
|
const inv = dim.getBlock(loc)?.getComponent("inventory");
|
||||||
|
const container = inv?.container;
|
||||||
|
const dropPos = { x: loc.x + 0.5, y: loc.y + 0.5, z: loc.z + 0.5 };
|
||||||
|
if (container) {
|
||||||
|
for (let i = 0; i < container.size; i++) {
|
||||||
|
const item = container.getItem(i);
|
||||||
|
if (item) {
|
||||||
|
dim.spawnItem(item, dropPos);
|
||||||
|
container.setItem(i, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dim.spawnItem(new ItemStack(PRIVATE_BLOCK, 1), dropPos);
|
||||||
|
dim.runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} air`);
|
||||||
|
} catch (e) {
|
||||||
|
player.sendMessage(`§c[Private Chest] Error during break: ${e.message}`);
|
||||||
|
}
|
||||||
|
release(loc, dim.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[Private Chest] beforeEvents.playerBreakBlock unavailable: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Boot ──────────────────────────────────────────────────
|
||||||
|
system.run(() => {
|
||||||
|
loadState();
|
||||||
|
world.sendMessage("§6[Private Chest] §7Owner-locked chest system loaded.");
|
||||||
|
});
|
||||||
4
private-chest-addon/private_chest_RP/blocks.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"format_version": [1, 1, 0],
|
||||||
|
"silverlabs:private_chest": { "sound": "stone" }
|
||||||
|
}
|
||||||
17
private-chest-addon/private_chest_RP/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Private Chests Resources",
|
||||||
|
"description": "Textures and lang for the silverlabs:private_chest block",
|
||||||
|
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b53",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b54",
|
||||||
|
"version": [1, 0, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
private-chest-addon/private_chest_RP/pack_icon.png
Normal file
|
After Width: | Height: | Size: 971 B |
1
private-chest-addon/private_chest_RP/texts/en_US.lang
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tile.silverlabs:private_chest.name=Private Chest
|
||||||
|
After Width: | Height: | Size: 317 B |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"resource_pack_name": "private_chest_RP",
|
||||||
|
"texture_name": "atlas.terrain",
|
||||||
|
"padding": 8,
|
||||||
|
"num_mip_levels": 4,
|
||||||
|
"texture_data": {
|
||||||
|
"private_chest": {
|
||||||
|
"textures": "textures/blocks/private_chest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
{
|
{
|
||||||
"behavior_packs": [
|
"behavior_packs": [
|
||||||
{"pack_id": "c7e91f32-4a8b-4d6e-b21c-9f3a05d8e047", "version": [1, 0, 0]}
|
{"pack_id": "c7e91f32-4a8b-4d6e-b21c-9f3a05d8e047", "version": [1, 0, 0]},
|
||||||
|
{"pack_id": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b50", "version": [1, 0, 0]},
|
||||||
|
{"pack_id": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b80", "version": [1, 0, 0]},
|
||||||
|
{"pack_id": "b4d72e91-8c43-4f1a-95d2-7e3b6c8a0d10", "version": [1, 0, 0]},
|
||||||
|
{"pack_id": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b60", "version": [1, 0, 0]}
|
||||||
],
|
],
|
||||||
"resource_packs": []
|
"resource_packs": [
|
||||||
|
{"pack_id": "9a3f8d2e-7c5b-4e1a-b9d2-1f6e3c4a8b53", "version": [1, 0, 1]},
|
||||||
|
{"pack_id": "c8e51d72-9a4f-4b3e-b8c1-2f7d3e6a4b83", "version": [1, 0, 1]},
|
||||||
|
{"pack_id": "d7a4b9c1-2e3f-4a5b-8c6d-1f2e3d4c5b63", "version": [1, 0, 0]}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||