feat(postal): add postal service addon and bundle pending addon work
All checks were successful
Deploy Addons / deploy (push) Successful in 16s
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:
191
home-sign-addon/home_sign_BP/scripts/main.js
Normal file
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.");
|
||||
});
|
||||
Reference in New Issue
Block a user