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