import { world, system, ItemStack } from "@minecraft/server"; import { variables } from "@minecraft/server-admin"; // ─── World Detection ───────────────────────────────────────────── let PREFIX = ""; let EGG_BLOCK = ""; try { const p = world.getDynamicProperty("egg_prefix"); const b = world.getDynamicProperty("egg_block"); if (p && b) { PREFIX = p; EGG_BLOCK = b; } } catch (e) {} if (!PREFIX) { let worldName = ""; try { worldName = (variables.get("world_name") || "").toLowerCase(); } catch (e) {} if (worldName.includes("jamie")) { PREFIX = "j"; EGG_BLOCK = "lime_glazed_terracotta"; } else if (worldName.includes("lyla")) { PREFIX = "l"; EGG_BLOCK = "purple_glazed_terracotta"; } else if (worldName.includes("mya")) { PREFIX = "m"; EGG_BLOCK = "cyan_glazed_terracotta"; } else { PREFIX = "?"; EGG_BLOCK = "yellow_glazed_terracotta"; } try { world.setDynamicProperty("egg_prefix", PREFIX); world.setDynamicProperty("egg_block", EGG_BLOCK); } catch (e) {} } // ─── Egg Positions ─────────────────────────────────────────────── // 50 (dx, dz) offsets from the first player's spawn position. // Three rings: close (5-20), medium (20-50), far (50-100). const EGG_OFFSETS = [ // Close ring — 12 eggs [ 8, 5], [-6, 9], [12, -3], [-10, 7], [ 5, -14], [-14, -5], [ 7, 11], [-8, -12], [15, 8], [-15, 3], [ 4, 18], [ -3, -16], // Medium ring — 22 eggs [ 22, 15], [-25, 8], [18, -28], [-20, -22], [35, 5], [-38, 12], [ 30,-18], [-32, 25], [45, 10], [-42, -8], [15, 40], [-18, -35], [ 28, 35], [-30,-28], [48, -12], [-45, 20], [38,-32], [-22, 45], [ 12,-48], [-48,-15], [50, 22], [-38, -40], // Far ring — 16 eggs [ 65, 15], [-68, 22], [55, -48], [-58, 42], [ 72, 35], [-75, -18], [ 80,-55], [-82, 40], [58, 70], [-60, -65], [ 90, 12], [-85, -50], [ 45, 82], [-48, 78], [75, -70], [-70, 75], ]; // ─── Scene Names ───────────────────────────────────────────────── const SCENE_NAMES = [ "atop a barrel", "on a hay bale", "nestled in a flower patch", "perched on a log", "between fence posts", "on a crafting table", "on a mossy ledge", "hidden in a leaf cluster", ]; // ─── Egg Basket (Cross-Server Persistence) ──────────────────────── // A nether_star item with a custom nameTag carries egg counts between servers. // nameTag format: "§6Egg Basket §8[j:N,l:N,m:N]" // Tags (egg_j_1, etc.) track which specific eggs were found per-session. // The basket is the authoritative count the lobby reads. const BASKET_ITEM = "minecraft:nether_star"; const BASKET_HEAD = "§6Egg Basket "; const BASKET_REGEX = /\[j:(\d+),l:(\d+),m:(\d+)\]/; function getBasket(player) { try { const inv = player.getComponent("minecraft:inventory"); if (!inv) return null; const container = inv.container; for (let i = 0; i < container.size; i++) { const item = container.getItem(i); if (item && item.typeId === BASKET_ITEM && item.nameTag && item.nameTag.startsWith(BASKET_HEAD)) { return { item, slot: i, container }; } } } catch (e) {} return null; } function getBasketCounts(player) { const result = { j: 0, l: 0, m: 0 }; const b = getBasket(player); if (!b) return result; const m = b.item.nameTag.match(BASKET_REGEX); if (m) { result.j = +m[1]; result.l = +m[2]; result.m = +m[3]; } return result; } function updateBasket(player, j, l, m) { try { const nameTag = `${BASKET_HEAD}§8[j:${j},l:${l},m:${m}]`; const b = getBasket(player); if (b) { b.item.nameTag = nameTag; b.container.setItem(b.slot, b.item); } else { const item = new ItemStack(BASKET_ITEM, 1); item.nameTag = nameTag; const inv = player.getComponent("minecraft:inventory"); if (inv) { const container = inv.container; for (let i = 0; i < container.size; i++) { if (!container.getItem(i)) { container.setItem(i, item); player.sendMessage("§6[Easter Eggs] §fYou received an §6Egg Basket§f — it tracks your egg count across all worlds! Keep it safe."); break; } } } } } catch (e) {} } // ─── Basket Initialisation ─────────────────────────────────────── // Give a fresh basket to players who don't have one yet. // We do NOT reconstruct tags from missing blocks on re-join because a missing // block might mean "never placed" (chunk not loaded) rather than "collected". // The block existence check inside collectEgg() is the sole guard against // re-collecting an already-found egg. function giveBasketIfMissing(player) { if (!getBasket(player)) updateBasket(player, 0, 0, 0); } // ─── Cached Positions ──────────────────────────────────────────── let eggPositions = null; function getEggPositions() { if (eggPositions !== null) return eggPositions; try { const json = world.getDynamicProperty("eggs_data"); if (json && json.length > 2) { eggPositions = JSON.parse(json); return eggPositions; } } catch (e) {} return null; } // ─── Surface Scanner ───────────────────────────────────────────── function findSurfaceY(dimension, x, spawnY, z) { const top = Math.min(320, spawnY + 60); const bottom = Math.max(-60, spawnY - 30); for (let y = top; y >= bottom; y--) { try { const block = dimension.getBlock({ x, y, z }); if (block && block.typeId !== "minecraft:air") { return y + 1; // first empty block above the topmost solid block } } catch (e) {} } return spawnY; } // ─── Scene Builder ─────────────────────────────────────────────── // Each egg is 2 blocks tall — bottom faces south (2), top faces north (3). // This opposing rotation gives the glazed terracotta an oval/egg appearance. // Stored Y is always the bottom block. function buildScene(dim, sceneIndex, x, surfY, z) { const cmds = []; let eggY = surfY; switch (sceneIndex % 8) { case 0: // Barrel nook — egg perched on a barrel cmds.push(`setblock ${x} ${surfY} ${z} barrel`); eggY = surfY + 1; break; case 1: // Hay bale — egg resting on a bale cmds.push(`setblock ${x} ${surfY} ${z} hay_block`); eggY = surfY + 1; break; case 2: // Flower patch — egg nestled among wildflowers cmds.push(`setblock ${x-1} ${surfY} ${z} poppy`); cmds.push(`setblock ${x+1} ${surfY} ${z} cornflower`); cmds.push(`setblock ${x} ${surfY} ${z-1} dandelion`); eggY = surfY; // egg rises up through the flower level break; case 3: // Log perch — egg balanced atop a cut log cmds.push(`setblock ${x} ${surfY} ${z} oak_log`); eggY = surfY + 1; break; case 4: // Fence posts — egg elevated on a fence pedestal cmds.push(`setblock ${x-1} ${surfY} ${z} oak_fence`); cmds.push(`setblock ${x} ${surfY} ${z} oak_fence`); cmds.push(`setblock ${x+1} ${surfY} ${z} oak_fence`); eggY = surfY + 1; break; case 5: // Crafting table — egg left on a workbench cmds.push(`setblock ${x} ${surfY} ${z} crafting_table`); eggY = surfY + 1; break; case 6: // Mossy ledge — egg raised on a stone platform cmds.push(`setblock ${x} ${surfY} ${z} mossy_cobblestone`); cmds.push(`setblock ${x-1} ${surfY} ${z} mossy_cobblestone`); eggY = surfY + 1; break; case 7: // Leaf cluster — egg tucked in a low leaf canopy // Side leaves frame the egg without fully hiding it cmds.push(`setblock ${x+1} ${surfY+1} ${z} oak_leaves`); cmds.push(`setblock ${x-1} ${surfY+1} ${z} oak_leaves`); cmds.push(`setblock ${x} ${surfY+1} ${z+1} oak_leaves`); cmds.push(`setblock ${x} ${surfY+1} ${z-1} oak_leaves`); eggY = surfY + 1; // bottom block nestled in the leaf ring, top sticks above break; } cmds.push(`setblock ${x} ${eggY} ${z} ${EGG_BLOCK}`); for (const cmd of cmds) { try { dim.runCommand(cmd); } catch (e) {} } return eggY; // caller stores this as the bottom block Y } // ─── Egg Placement ─────────────────────────────────────────────── function placeAllEggs(playerPos) { const overworld = world.getDimension("overworld"); if (world.getDynamicProperty("eggs_placed") === "true") { // Validate at least one stored egg block actually exists const stored = getEggPositions(); if (stored && stored.length > 0) { // Check far-ring eggs (last 5) — they're hardest to reach and least likely // to have been collected yet. Avoids false "blocks missing" detection when // only the close-ring eggs (1-5) have been collected. let found = 0; const checkAt = [45, 46, 47, 48, 49].filter(i => i < stored.length); const fallback = checkAt.length === 0; const indices = fallback ? [0, 1, 2, 3, 4].filter(i => i < stored.length) : checkAt; for (const i of indices) { try { const block = overworld.getBlock({ x: stored[i].x, y: stored[i].y, z: stored[i].z }); if (block && block.typeId.includes("glazed_terracotta")) found++; } catch (e) {} } if (found > 0) return; } // Flagged as placed but blocks are absent — clear and re-place world.setDynamicProperty("eggs_placed", "false"); world.setDynamicProperty("eggs_data", ""); world.setDynamicProperty("egg_center", ""); eggPositions = null; } // Determine centre: stored centre takes priority, then joining player, then world spawn let cx, cy, cz; try { const stored = JSON.parse(world.getDynamicProperty("egg_center") || "null"); if (stored) { cx = stored.x; cy = stored.y; cz = stored.z; } } catch (e) {} if (cx === undefined) { if (playerPos) { cx = Math.floor(playerPos.x); cy = Math.floor(playerPos.y); cz = Math.floor(playerPos.z); } else { const spawn = world.getDefaultSpawnLocation(); cx = Math.floor(spawn.x); cy = Math.floor(spawn.y); cz = Math.floor(spawn.z); } try { world.setDynamicProperty("egg_center", JSON.stringify({ x: cx, y: cy, z: cz })); } catch (e) {} } const positions = []; for (let i = 0; i < EGG_OFFSETS.length; i++) { const [dx, dz] = EGG_OFFSETS[i]; const x = cx + dx; const z = cz + dz; const surfY = findSurfaceY(overworld, x, cy, z); const eggY = buildScene(overworld, i, x, surfY, z); positions.push({ x, y: eggY, z, id: i + 1 }); } try { world.setDynamicProperty("eggs_data", JSON.stringify(positions)); world.setDynamicProperty("eggs_placed", "true"); world.sendMessage(`§6[Easter Eggs] §f${positions.length} eggs are now hidden around here — happy hunting!`); } catch (e) { world.sendMessage(`§c[Easter Eggs] §fFailed to save egg data: ${e.message}`); } } // ─── Egg Collection ────────────────────────────────────────────── function collectEgg(player, egg, tagId) { try { // Confirm the block still exists before collecting (prevents ghost re-collection) const block = player.dimension.getBlock({ x: egg.x, y: egg.y, z: egg.z }); if (!block || !block.typeId.includes("glazed_terracotta")) return; player.addTag(tagId); const count = player.getTags().filter(t => t.startsWith(`egg_${PREFIX}_`)).length; // Update basket with the new count for this world const counts = getBasketCounts(player); if (PREFIX === "j") updateBasket(player, count, counts.l, counts.m); else if (PREFIX === "l") updateBasket(player, counts.j, count, counts.m); else if (PREFIX === "m") updateBasket(player, counts.j, counts.l, count); player.dimension.runCommand(`setblock ${egg.x} ${egg.y} ${egg.z} air`); player.dimension.runCommand(`particle minecraft:egg_destroy_emitter ${egg.x} ${egg.y} ${egg.z}`); player.runCommand(`playsound random.orb @s`); player.runCommand(`playsound random.levelup @s`); player.runCommand(`give @s emerald 1`); player.runCommand(`titleraw @s title {"rawtext":[{"text":"§6Easter Egg Found!"}]}`); player.runCommand(`titleraw @s subtitle {"rawtext":[{"text":"§e${count}/50 §7found in this world"}]}`); player.sendMessage( `§6[Eggs] §fEgg §e#${egg.id} §f(${SCENE_NAMES[(egg.id - 1) % 8]}) §7| §e${count}/50 §7in this world` ); if (count === 50) { world.sendMessage(`§6§l✨ ${player.name} found all 50 eggs! ✨ §r§6Return to the lobby for your final score!`); } } catch (e) {} } // ─── Detection Loop ────────────────────────────────────────────── system.runInterval(() => { const positions = getEggPositions(); if (!positions || positions.length === 0) return; for (const player of world.getAllPlayers()) { const pos = player.location; const tags = player.getTags(); for (const egg of positions) { const tagId = `egg_${PREFIX}_${egg.id}`; if (tags.includes(tagId)) continue; const dx = Math.abs(pos.x - egg.x); const dy = Math.abs(pos.y - egg.y); const dz = Math.abs(pos.z - egg.z); if (dx <= 2.5 && dy <= 2.0 && dz <= 2.5) { collectEgg(player, egg, tagId); } } } }, 10); // ─── Chat Commands ─────────────────────────────────────────────── function handleEggCommand(player, msg) { if (msg === "!eggs") { const count = player.getTags().filter(t => t.startsWith(`egg_${PREFIX}_`)).length; player.sendMessage(`§6[Eggs] §fFound §e${count}§f/50 eggs in this world.`); if (count === 50) { player.sendMessage("§a§lYou found all 50! Return to the lobby to see the leaderboard."); } else { player.sendMessage("§7Hint: eggs are hidden in flower patches, on logs, barrels, hay bales, fences, crafting tables, mossy ledges, and leaf clusters."); } return true; } if (msg.startsWith("!seteggworld ")) { const arg = msg.split(" ")[1]; if (!["j", "l", "m"].includes(arg)) { player.sendMessage("§c[Eggs] Usage: §e!seteggworld j§c (Jamie), §el§c (Lyla), §em§c (Mya)"); return true; } const blockMap = { j: "lime_glazed_terracotta", l: "purple_glazed_terracotta", m: "cyan_glazed_terracotta" }; PREFIX = arg; EGG_BLOCK = blockMap[arg]; try { world.setDynamicProperty("egg_prefix", PREFIX); world.setDynamicProperty("egg_block", EGG_BLOCK); world.setDynamicProperty("eggs_placed", "false"); world.setDynamicProperty("eggs_data", ""); } catch (e) {} eggPositions = null; player.sendMessage(`§6[Eggs] §fWorld prefix set to §e${arg}§f — re-placing eggs...`); system.runTimeout(() => placeAllEggs(player.location), 20); return true; } if (msg === "!reseteggsworld") { try { world.setDynamicProperty("eggs_placed", "false"); world.setDynamicProperty("eggs_data", ""); world.setDynamicProperty("egg_center", ""); } catch (e) {} eggPositions = null; eggsPlacedThisSession = false; player.sendMessage("§6[Eggs] §fEgg data cleared — eggs will re-place around your position..."); const pos = player.location; system.runTimeout(() => placeAllEggs(pos), 20); return true; } return false; } try { world.beforeEvents.chatSend.subscribe((event) => { const msg = event.message.trim().toLowerCase(); if (msg === "!eggs" || msg.startsWith("!seteggworld ") || msg === "!reseteggsworld") { event.cancel = true; const player = event.sender; system.run(() => handleEggCommand(player, msg)); } }); } catch (e) {} system.afterEvents.scriptEventReceive.subscribe((event) => { if (event.id !== "eggs:cmd") return; const msg = (event.message || "").trim().toLowerCase(); // Allow reseteggsworld from console (sourceEntity is null when run via MCP/RCON) if (msg === "reseteggsworld") { try { world.setDynamicProperty("eggs_placed", "false"); world.setDynamicProperty("eggs_data", ""); world.setDynamicProperty("egg_center", ""); } catch (e) {} eggPositions = null; eggsPlacedThisSession = false; world.sendMessage("§6[Eggs] §fEgg data cleared — eggs will re-place on next player spawn."); return; } const player = event.sourceEntity; if (!player) return; handleEggCommand(player, `!${msg}`); }); // ─── Startup ───────────────────────────────────────────────────── // Egg placement fires on first player spawn so chunks are guaranteed loaded. // Tag rebuild fires for every player that (re)joins — recovers lost tags from // cross-server transfers by checking which egg blocks are actually missing. let eggsPlacedThisSession = false; world.afterEvents.playerSpawn.subscribe((event) => { const player = event.player; const playerPos = player.location; // Place eggs on first spawn of the session (40 tick delay to settle chunks) if (!eggsPlacedThisSession) { eggsPlacedThisSession = true; system.runTimeout(() => placeAllEggs(playerPos), 40); } // Give basket if player doesn't have one yet system.runTimeout(() => { try { giveBasketIfMissing(player); } catch (e) {} }, 40); }); system.run(() => { world.sendMessage("§6[Easter Eggs] §fHunt active! Type §e!eggs §fto check your progress."); });