From f7aa71e9ebe3396810e2e726295bf3e9f3b3745a Mon Sep 17 00:00:00 2001 From: SysAdmin Date: Mon, 20 Apr 2026 20:07:39 +0100 Subject: [PATCH] feat(postal): add postal service addon and bundle pending addon work - 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) --- .gitea/workflows/deploy.yml | 11 +- .../anthrax_cat_BP/entities/anthrax_cat.json | 14 +- docker-compose.yml | 28 + .../home_sign_BP/blocks/home_sign.json | 27 + .../home_sign_BP/entities/cat.json | 701 ++++++++++++++++++ home-sign-addon/home_sign_BP/manifest.json | 34 + .../home_sign_BP/recipes/home_sign.json | 18 + home-sign-addon/home_sign_BP/scripts/main.js | 191 +++++ home-sign-addon/home_sign_RP/blocks.json | 4 + home-sign-addon/home_sign_RP/manifest.json | 17 + home-sign-addon/home_sign_RP/pack_icon.png | Bin 0 -> 971 bytes home-sign-addon/home_sign_RP/texts/en_US.lang | 1 + .../textures/blocks/home_sign.png | Bin 0 -> 268 bytes .../textures/terrain_texture.json | 11 + .../keep_inventory_BP/manifest.json | 30 + .../keep_inventory_BP/scripts/main.js | 17 + .../postal_service_BP/blocks/mailbox.json | 27 + .../postal_service_BP/blocks/post_office.json | 27 + .../postal_service_BP/manifest.json | 38 + .../postal_service_BP/recipes/mailbox.json | 18 + .../recipes/post_office.json | 24 + .../postal_service_BP/scripts/main.js | 390 ++++++++++ .../postal_service_RP/blocks.json | 5 + .../postal_service_RP/manifest.json | 17 + .../postal_service_RP/pack_icon.png | Bin 0 -> 971 bytes .../postal_service_RP/texts/en_US.lang | 2 + .../textures/blocks/mailbox.png | Bin 0 -> 317 bytes .../textures/blocks/post_office.png | Bin 0 -> 317 bytes .../textures/terrain_texture.json | 14 + .../blocks/private_chest.json | 27 + .../private_chest_BP/manifest.json | 34 + .../recipes/private_chest.json | 18 + .../private_chest_BP/scripts/main.js | 158 ++++ .../private_chest_RP/blocks.json | 4 + .../private_chest_RP/manifest.json | 17 + .../private_chest_RP/pack_icon.png | Bin 0 -> 971 bytes .../private_chest_RP/texts/en_US.lang | 1 + .../textures/blocks/private_chest.png | Bin 0 -> 317 bytes .../textures/terrain_texture.json | 11 + village-evolution-addon/enabled_packs.json | 12 +- 40 files changed, 1930 insertions(+), 18 deletions(-) create mode 100644 home-sign-addon/home_sign_BP/blocks/home_sign.json create mode 100644 home-sign-addon/home_sign_BP/entities/cat.json create mode 100644 home-sign-addon/home_sign_BP/manifest.json create mode 100644 home-sign-addon/home_sign_BP/recipes/home_sign.json create mode 100644 home-sign-addon/home_sign_BP/scripts/main.js create mode 100644 home-sign-addon/home_sign_RP/blocks.json create mode 100644 home-sign-addon/home_sign_RP/manifest.json create mode 100644 home-sign-addon/home_sign_RP/pack_icon.png create mode 100644 home-sign-addon/home_sign_RP/texts/en_US.lang create mode 100644 home-sign-addon/home_sign_RP/textures/blocks/home_sign.png create mode 100644 home-sign-addon/home_sign_RP/textures/terrain_texture.json create mode 100644 keep-inventory-addon/keep_inventory_BP/manifest.json create mode 100644 keep-inventory-addon/keep_inventory_BP/scripts/main.js create mode 100644 postal-service-addon/postal_service_BP/blocks/mailbox.json create mode 100644 postal-service-addon/postal_service_BP/blocks/post_office.json create mode 100644 postal-service-addon/postal_service_BP/manifest.json create mode 100644 postal-service-addon/postal_service_BP/recipes/mailbox.json create mode 100644 postal-service-addon/postal_service_BP/recipes/post_office.json create mode 100644 postal-service-addon/postal_service_BP/scripts/main.js create mode 100644 postal-service-addon/postal_service_RP/blocks.json create mode 100644 postal-service-addon/postal_service_RP/manifest.json create mode 100644 postal-service-addon/postal_service_RP/pack_icon.png create mode 100644 postal-service-addon/postal_service_RP/texts/en_US.lang create mode 100644 postal-service-addon/postal_service_RP/textures/blocks/mailbox.png create mode 100644 postal-service-addon/postal_service_RP/textures/blocks/post_office.png create mode 100644 postal-service-addon/postal_service_RP/textures/terrain_texture.json create mode 100644 private-chest-addon/private_chest_BP/blocks/private_chest.json create mode 100644 private-chest-addon/private_chest_BP/manifest.json create mode 100644 private-chest-addon/private_chest_BP/recipes/private_chest.json create mode 100644 private-chest-addon/private_chest_BP/scripts/main.js create mode 100644 private-chest-addon/private_chest_RP/blocks.json create mode 100644 private-chest-addon/private_chest_RP/manifest.json create mode 100644 private-chest-addon/private_chest_RP/pack_icon.png create mode 100644 private-chest-addon/private_chest_RP/texts/en_US.lang create mode 100644 private-chest-addon/private_chest_RP/textures/blocks/private_chest.png create mode 100644 private-chest-addon/private_chest_RP/textures/terrain_texture.json diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index de3ef71..df8554e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -7,8 +7,12 @@ 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/**' - 'docker-compose.yml' - 'scripts/**' @@ -26,6 +30,7 @@ jobs: script: | set -e 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. if [ ! -d "$APP_DIR/.git" ]; then @@ -33,11 +38,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, diff --git a/addon/anthrax_cat_BP/entities/anthrax_cat.json b/addon/anthrax_cat_BP/entities/anthrax_cat.json index 19063be..dbb96ae 100644 --- a/addon/anthrax_cat_BP/entities/anthrax_cat.json +++ b/addon/anthrax_cat_BP/entities/anthrax_cat.json @@ -3,7 +3,7 @@ "minecraft:entity": { "description": { "identifier": "silverlabs:anthrax_cat", - "is_spawnable": false, + "is_spawnable": true, "is_summonable": true, "is_experimental": false }, @@ -39,18 +39,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 diff --git a/docker-compose.yml b/docker-compose.yml index 4690b28..22b1f52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,13 @@ services: - ./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 restart: unless-stopped networks: - mc-network @@ -53,6 +60,13 @@ services: volumes: - jamie-data:/data - ./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 networks: - mc-network @@ -84,6 +98,13 @@ 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 + - ./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 restart: unless-stopped networks: @@ -116,6 +137,13 @@ 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 + - ./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 restart: unless-stopped networks: diff --git a/home-sign-addon/home_sign_BP/blocks/home_sign.json b/home-sign-addon/home_sign_BP/blocks/home_sign.json new file mode 100644 index 0000000..3c59f85 --- /dev/null +++ b/home-sign-addon/home_sign_BP/blocks/home_sign.json @@ -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" + } + } + } + } +} diff --git a/home-sign-addon/home_sign_BP/entities/cat.json b/home-sign-addon/home_sign_BP/entities/cat.json new file mode 100644 index 0000000..0274e0f --- /dev/null +++ b/home-sign-addon/home_sign_BP/entities/cat.json @@ -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" + } + } + ] + } + } + } +} diff --git a/home-sign-addon/home_sign_BP/manifest.json b/home-sign-addon/home_sign_BP/manifest.json new file mode 100644 index 0000000..49b6414 --- /dev/null +++ b/home-sign-addon/home_sign_BP/manifest.json @@ -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] + } + ] +} diff --git a/home-sign-addon/home_sign_BP/recipes/home_sign.json b/home-sign-addon/home_sign_BP/recipes/home_sign.json new file mode 100644 index 0000000..0fe6031 --- /dev/null +++ b/home-sign-addon/home_sign_BP/recipes/home_sign.json @@ -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 + } + } +} diff --git a/home-sign-addon/home_sign_BP/scripts/main.js b/home-sign-addon/home_sign_BP/scripts/main.js new file mode 100644 index 0000000..e7c80ff --- /dev/null +++ b/home-sign-addon/home_sign_BP/scripts/main.js @@ -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."); +}); diff --git a/home-sign-addon/home_sign_RP/blocks.json b/home-sign-addon/home_sign_RP/blocks.json new file mode 100644 index 0000000..020317d --- /dev/null +++ b/home-sign-addon/home_sign_RP/blocks.json @@ -0,0 +1,4 @@ +{ + "format_version": [1, 1, 0], + "silverlabs:home_sign": { "sound": "wood" } +} diff --git a/home-sign-addon/home_sign_RP/manifest.json b/home-sign-addon/home_sign_RP/manifest.json new file mode 100644 index 0000000..232c429 --- /dev/null +++ b/home-sign-addon/home_sign_RP/manifest.json @@ -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] + } + ] +} diff --git a/home-sign-addon/home_sign_RP/pack_icon.png b/home-sign-addon/home_sign_RP/pack_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a507642d37648f9bf0fb7e5ed2a3229eb2392bb9 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV4mdZ;uumf=j~m?JmElr_K%#W z1cNTSgzR&$QsJ9mCBC5F;boALN{hqGm&&hX6oh2OABbfJw=>P#9Cx$g?7mv#&;RbP z_fmNrG4K55b7{hTa{2!Ac{asW*fac4VkkU**#F$Ru(*C^-oJnTWdAjkc93E0P-ZA( zQRrhhBG_QTB;d~Qhy%Oeosa)!e@U${^;uY>IwgkTwuZYSgY^m*M+Wv6hFnZDG^<5e zHZ=GI3OFbSE>>W0f2?8C^uQ%RAntYp?}IbH4GypsJh81YHf!kSXx6{M(CaF-Pf~ip z%nT5R*F|=pB%_Yrrw?Z>Saj5WJ>WJwz_f|yeh+Uz=(2<2ISpP$eD`&3uvyseyi+}U zcl+$4^2Zf7@$KUfJTT>8f4;P0%bUaI+yxvKDL+jA#p>Gf+&@#vd`9-s&y%U2eJ`AT zz`dO};I-(TBH=G)HJcUQGcI~v_@VVQ+ls%dVvD%H*wk!h$Y*M|tEqqdSKt~jeD3n! z*9l-q{UN9nAW45J^l@#@t)iuahWT*&%y zd*8b6nJ+D!avQ|5cU*wpn{?uE083X^;Ucd%Y~EvB)_<-sn-jsx?5 zD=zrXv8b5;z#D&-y=?-u+%3P%KEw;U)GKY~+Q|6rq}`tr{r0X55qy|2>NJ=0&eL}f z)xMmZvN3%1bm{r^KR=#(Iq}VOrG_{6_W#@U&mxK^lj+1Z*0N49=UEI@COrqwsCR@g ze%ZuY_VEVaN+yHX_DzdIdEEQAFZX{s*=N7FxMkDFQr^d*_Sf$ywEYkEyv5Mvq3E-7 z)ygoQVkHT-3GW09#GXzt_&RCZ!v|UUZT~0QF>HRWeqXu8^14x9{82f_52c)I$ztaD0e F0stcJlKlVx literal 0 HcmV?d00001 diff --git a/home-sign-addon/home_sign_RP/texts/en_US.lang b/home-sign-addon/home_sign_RP/texts/en_US.lang new file mode 100644 index 0000000..e2f9c2e --- /dev/null +++ b/home-sign-addon/home_sign_RP/texts/en_US.lang @@ -0,0 +1 @@ +tile.silverlabs:home_sign.name=Home Sweet Home diff --git a/home-sign-addon/home_sign_RP/textures/blocks/home_sign.png b/home-sign-addon/home_sign_RP/textures/blocks/home_sign.png new file mode 100644 index 0000000000000000000000000000000000000000..8e917a079c0758bbf4e0c6cfc80945b25ae245e7 GIT binary patch literal 268 zcmV+n0rUQeP)nI<9C4H7%VvN6(j;YaYf z_TF=HdT*NK^Kr!w3;{O9@+#k&gkZK>u~T~hG9dua z%6cY)n7A~COo&$87&U2v1`VY#q3a+o2C|T7=VHBR=lY+}XA%+vB>8+?EhZzckRI;O z;W@{Boq9c|%dOtRR0^+1QBS=|6{0nW5fgR^Zs8Mp4fOenK41Ts;2n}*zw-m(KSo!& S69lXP0000 { + 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); diff --git a/postal-service-addon/postal_service_BP/blocks/mailbox.json b/postal-service-addon/postal_service_BP/blocks/mailbox.json new file mode 100644 index 0000000..4a7a3ca --- /dev/null +++ b/postal-service-addon/postal_service_BP/blocks/mailbox.json @@ -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" + } + } + } + } +} diff --git a/postal-service-addon/postal_service_BP/blocks/post_office.json b/postal-service-addon/postal_service_BP/blocks/post_office.json new file mode 100644 index 0000000..e6129aa --- /dev/null +++ b/postal-service-addon/postal_service_BP/blocks/post_office.json @@ -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" + } + } + } + } +} diff --git a/postal-service-addon/postal_service_BP/manifest.json b/postal-service-addon/postal_service_BP/manifest.json new file mode 100644 index 0000000..5d75923 --- /dev/null +++ b/postal-service-addon/postal_service_BP/manifest.json @@ -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] + } + ] +} diff --git a/postal-service-addon/postal_service_BP/recipes/mailbox.json b/postal-service-addon/postal_service_BP/recipes/mailbox.json new file mode 100644 index 0000000..67d97ea --- /dev/null +++ b/postal-service-addon/postal_service_BP/recipes/mailbox.json @@ -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 + } + } +} diff --git a/postal-service-addon/postal_service_BP/recipes/post_office.json b/postal-service-addon/postal_service_BP/recipes/post_office.json new file mode 100644 index 0000000..7613849 --- /dev/null +++ b/postal-service-addon/postal_service_BP/recipes/post_office.json @@ -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 + } + } +} diff --git a/postal-service-addon/postal_service_BP/scripts/main.js b/postal-service-addon/postal_service_BP/scripts/main.js new file mode 100644 index 0000000..98f9a69 --- /dev/null +++ b/postal-service-addon/postal_service_BP/scripts/main.js @@ -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."); +}); diff --git a/postal-service-addon/postal_service_RP/blocks.json b/postal-service-addon/postal_service_RP/blocks.json new file mode 100644 index 0000000..52dc4b8 --- /dev/null +++ b/postal-service-addon/postal_service_RP/blocks.json @@ -0,0 +1,5 @@ +{ + "format_version": [1, 1, 0], + "silverlabs:mailbox": { "sound": "stone" }, + "silverlabs:post_office": { "sound": "wood" } +} diff --git a/postal-service-addon/postal_service_RP/manifest.json b/postal-service-addon/postal_service_RP/manifest.json new file mode 100644 index 0000000..8eb0aee --- /dev/null +++ b/postal-service-addon/postal_service_RP/manifest.json @@ -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] + } + ] +} diff --git a/postal-service-addon/postal_service_RP/pack_icon.png b/postal-service-addon/postal_service_RP/pack_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a507642d37648f9bf0fb7e5ed2a3229eb2392bb9 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV4mdZ;uumf=j~m?JmElr_K%#W z1cNTSgzR&$QsJ9mCBC5F;boALN{hqGm&&hX6oh2OABbfJw=>P#9Cx$g?7mv#&;RbP z_fmNrG4K55b7{hTa{2!Ac{asW*fac4VkkU**#F$Ru(*C^-oJnTWdAjkc93E0P-ZA( zQRrhhBG_QTB;d~Qhy%Oeosa)!e@U${^;uY>IwgkTwuZYSgY^m*M+Wv6hFnZDG^<5e zHZ=GI3OFbSE>>W0f2?8C^uQ%RAntYp?}IbH4GypsJh81YHf!kSXx6{M(CaF-Pf~ip z%nT5R*F|=pB%_Yrrw?Z>Saj5WJ>WJwz_f|yeh+Uz=(2<2ISpP$eD`&3uvyseyi+}U zcl+$4^2Zf7@$KUfJTT>8f4;P0%bUaI+yxvKDL+jA#p>Gf+&@#vd`9-s&y%U2eJ`AT zz`dO};I-(TBH=G)HJcUQGcI~v_@VVQ+ls%dVvD%H*wk!h$Y*M|tEqqdSKt~jeD3n! z*9l-q{UN9nAW45J^l@#@t)iuahWT*&%y zd*8b6nJ+D!avQ|5cU*wpn{?uE083X^;Ucd%Y~EvB)_<-sn-jsx?5 zD=zrXv8b5;z#D&-y=?-u+%3P%KEw;U)GKY~+Q|6rq}`tr{r0X55qy|2>NJ=0&eL}f z)xMmZvN3%1bm{r^KR=#(Iq}VOrG_{6_W#@U&mxK^lj+1Z*0N49=UEI@COrqwsCR@g ze%ZuY_VEVaN+yHX_DzdIdEEQAFZX{s*=N7FxMkDFQr^d*_Sf$ywEYkEyv5Mvq3E-7 z)ygoQVkHT-3GW09#GXzt_&RCZ!v|UUZT~0QF>HRWeqXu8^14x9{82f_52c)I$ztaD0e F0stcJlKlVx literal 0 HcmV?d00001 diff --git a/postal-service-addon/postal_service_RP/texts/en_US.lang b/postal-service-addon/postal_service_RP/texts/en_US.lang new file mode 100644 index 0000000..9d90a4f --- /dev/null +++ b/postal-service-addon/postal_service_RP/texts/en_US.lang @@ -0,0 +1,2 @@ +tile.silverlabs:mailbox.name=Mailbox +tile.silverlabs:post_office.name=Post Office diff --git a/postal-service-addon/postal_service_RP/textures/blocks/mailbox.png b/postal-service-addon/postal_service_RP/textures/blocks/mailbox.png new file mode 100644 index 0000000000000000000000000000000000000000..6608d72bca8bfcffd80319a64ffdbf4ec24f0e77 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU}W)haSW-Lle{JH;qUVfj0uSe z2?-y}C#!mYJ{H_BIU`}taix1aRUeHz7Rh&49Og{~sq^pTj*tL?JiDJAiZjihGH}%g z^)ZJzLj-Mpw}ciQZsX+v0Y*kfi>n(9fZ)l`*NIcF{Mf;GitE6tv`fdlXC!`@`;{~7 za6-f9?L2dM`I$=sq6Hg5i;Iu5@ffaQi?J)8)CdFzZNKX=1Hs{QCr??#wg_?;by-|B zTCloRxDOFefFi{XQZpTG0;yY7+Pv$s}KV%krG zoEu+dmUH+0*_H(KV8Vpj%~OG9b9v63c@&A Ben(9fZ)l`*NIcF{Mf;GitE6tv`fdlXC!`@`;{~7 za6-f9?L2dM`I$=sq6Hg5i;Iu5@ffaQi?J)8)CdFzZNKX=1Hs{QCr??#wg_?;by-|B zTCloRxDOFefFi{XQZpTG0;yY7+Pv$s}KV%krG zoEu+dmUH+0*_H(KV8Vpj%~OG9b9v63c@&A Be 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."); +}); diff --git a/private-chest-addon/private_chest_RP/blocks.json b/private-chest-addon/private_chest_RP/blocks.json new file mode 100644 index 0000000..0f82e57 --- /dev/null +++ b/private-chest-addon/private_chest_RP/blocks.json @@ -0,0 +1,4 @@ +{ + "format_version": [1, 1, 0], + "silverlabs:private_chest": { "sound": "stone" } +} diff --git a/private-chest-addon/private_chest_RP/manifest.json b/private-chest-addon/private_chest_RP/manifest.json new file mode 100644 index 0000000..b47c98d --- /dev/null +++ b/private-chest-addon/private_chest_RP/manifest.json @@ -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] + } + ] +} diff --git a/private-chest-addon/private_chest_RP/pack_icon.png b/private-chest-addon/private_chest_RP/pack_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a507642d37648f9bf0fb7e5ed2a3229eb2392bb9 GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV4mdZ;uumf=j~m?JmElr_K%#W z1cNTSgzR&$QsJ9mCBC5F;boALN{hqGm&&hX6oh2OABbfJw=>P#9Cx$g?7mv#&;RbP z_fmNrG4K55b7{hTa{2!Ac{asW*fac4VkkU**#F$Ru(*C^-oJnTWdAjkc93E0P-ZA( zQRrhhBG_QTB;d~Qhy%Oeosa)!e@U${^;uY>IwgkTwuZYSgY^m*M+Wv6hFnZDG^<5e zHZ=GI3OFbSE>>W0f2?8C^uQ%RAntYp?}IbH4GypsJh81YHf!kSXx6{M(CaF-Pf~ip z%nT5R*F|=pB%_Yrrw?Z>Saj5WJ>WJwz_f|yeh+Uz=(2<2ISpP$eD`&3uvyseyi+}U zcl+$4^2Zf7@$KUfJTT>8f4;P0%bUaI+yxvKDL+jA#p>Gf+&@#vd`9-s&y%U2eJ`AT zz`dO};I-(TBH=G)HJcUQGcI~v_@VVQ+ls%dVvD%H*wk!h$Y*M|tEqqdSKt~jeD3n! z*9l-q{UN9nAW45J^l@#@t)iuahWT*&%y zd*8b6nJ+D!avQ|5cU*wpn{?uE083X^;Ucd%Y~EvB)_<-sn-jsx?5 zD=zrXv8b5;z#D&-y=?-u+%3P%KEw;U)GKY~+Q|6rq}`tr{r0X55qy|2>NJ=0&eL}f z)xMmZvN3%1bm{r^KR=#(Iq}VOrG_{6_W#@U&mxK^lj+1Z*0N49=UEI@COrqwsCR@g ze%ZuY_VEVaN+yHX_DzdIdEEQAFZX{s*=N7FxMkDFQr^d*_Sf$ywEYkEyv5Mvq3E-7 z)ygoQVkHT-3GW09#GXzt_&RCZ!v|UUZT~0QF>HRWeqXu8^14x9{82f_52c)I$ztaD0e F0stcJlKlVx literal 0 HcmV?d00001 diff --git a/private-chest-addon/private_chest_RP/texts/en_US.lang b/private-chest-addon/private_chest_RP/texts/en_US.lang new file mode 100644 index 0000000..408a365 --- /dev/null +++ b/private-chest-addon/private_chest_RP/texts/en_US.lang @@ -0,0 +1 @@ +tile.silverlabs:private_chest.name=Private Chest diff --git a/private-chest-addon/private_chest_RP/textures/blocks/private_chest.png b/private-chest-addon/private_chest_RP/textures/blocks/private_chest.png new file mode 100644 index 0000000000000000000000000000000000000000..6608d72bca8bfcffd80319a64ffdbf4ec24f0e77 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4(FKU}W)haSW-Lle{JH;qUVfj0uSe z2?-y}C#!mYJ{H_BIU`}taix1aRUeHz7Rh&49Og{~sq^pTj*tL?JiDJAiZjihGH}%g z^)ZJzLj-Mpw}ciQZsX+v0Y*kfi>n(9fZ)l`*NIcF{Mf;GitE6tv`fdlXC!`@`;{~7 za6-f9?L2dM`I$=sq6Hg5i;Iu5@ffaQi?J)8)CdFzZNKX=1Hs{QCr??#wg_?;by-|B zTCloRxDOFefFi{XQZpTG0;yY7+Pv$s}KV%krG zoEu+dmUH+0*_H(KV8Vpj%~OG9b9v63c@&A Be