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>
This commit is contained in:
2026-04-20 20:07:39 +01:00
parent cc18066c17
commit f7aa71e9eb
40 changed files with 1930 additions and 18 deletions

View 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"
}
}
}
}
}

View 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"
}
}
]
}
}
}
}

View 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]
}
]
}

View 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
}
}
}

View 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.");
});