Files
minecraft-aiworld/home-sign-addon/home_sign_BP/scripts/main.js
SysAdmin f7aa71e9eb
All checks were successful
Deploy Addons / deploy (push) Successful in 16s
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) <noreply@anthropic.com>
2026-04-20 20:07:39 +01:00

192 lines
6.0 KiB
JavaScript

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