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>
192 lines
6.0 KiB
JavaScript
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.");
|
|
});
|