feat(easter-eggs): add easter egg hunt addon across all worlds
All checks were successful
Deploy Addons / deploy (push) Successful in 17s

50 eggs hidden per child world (Jamie/Lyla/Mya) in 8 creative hiding
scenes (barrels, hay bales, flower patches, logs, fences, crafting
tables, mossy ledges, leaf clusters). Each world uses a distinct glazed
terracotta colour. Found eggs are tracked via player tags which persist
across server transfers; lobby reads tags and maintains a scoreboard
leaderboard (egg_count objective). Gitea deploy workflow updated to
include easter-egg-addon/ and docker-compose.yml in checkout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 02:00:11 +01:00
parent 5c4f8b9da5
commit 90a0b96634
6 changed files with 523 additions and 2 deletions

View File

@@ -0,0 +1,25 @@
{
"format_version": 2,
"header": {
"name": "Easter Egg Lobby",
"description": "Tracks egg counts from all worlds and shows a leaderboard in the lobby",
"uuid": "e3a4c5d6-7890-1234-cdef-012345678902",
"version": [1, 0, 0],
"min_engine_version": [1, 21, 0]
},
"modules": [
{
"type": "script",
"language": "javascript",
"uuid": "f4b5d6e7-8901-2345-def0-123456789013",
"version": [1, 0, 0],
"entry": "scripts/main.js"
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.17.0"
}
]
}

View File

@@ -0,0 +1,129 @@
import { world, system } from "@minecraft/server";
// ─── Constants ───────────────────────────────────────────────────
const OBJECTIVE = "egg_count";
const EGG_PATTERN = /^egg_[jlm]_\d+$/;
// ─── Scoreboard Setup ────────────────────────────────────────────
function ensureScoreboard() {
const overworld = world.getDimension("overworld");
try {
overworld.runCommand(`scoreboard objectives add ${OBJECTIVE} dummy "§6Easter Eggs §7(total)"`);
} catch (e) {
// Objective already exists — not an error
}
try {
overworld.runCommand(`scoreboard objectives setdisplay sidebar ${OBJECTIVE} descending`);
} catch (e) {}
}
// ─── Egg Counting ────────────────────────────────────────────────
function countPlayerEggs(player) {
const tags = player.getTags();
let j = 0, l = 0, m = 0;
for (const tag of tags) {
if (!EGG_PATTERN.test(tag)) continue;
if (tag.startsWith("egg_j_")) j++;
else if (tag.startsWith("egg_l_")) l++;
else if (tag.startsWith("egg_m_")) m++;
}
return { total: j + l + m, j, l, m };
}
function updatePlayerScore(player) {
const counts = countPlayerEggs(player);
try {
player.runCommand(`scoreboard players set @s ${OBJECTIVE} ${counts.total}`);
} catch (e) {}
return counts;
}
// ─── Player Spawn ────────────────────────────────────────────────
// Wait 40 ticks (2 seconds) after spawn for player state/tags to fully load,
// then update their scoreboard position and show a welcome subtitle.
world.afterEvents.playerSpawn.subscribe((event) => {
const player = event.player;
system.runTimeout(() => {
try {
const { total, j, l, m } = updatePlayerScore(player);
if (total > 0) {
player.runCommand(
`titleraw @s subtitle {"rawtext":[{"text":"§7Eggs found: §6${total}§7/150 §8(§aJ:${j} §dL:${l} §bM:${m}§8)"}]}`
);
}
if (total === 150) {
world.sendMessage(`§6§l🥚 ${player.name} has found ALL 150 easter eggs! 🥚`);
}
} catch (e) {}
}, 40);
});
// ─── Refresh on Interval ─────────────────────────────────────────
// Keep scores up-to-date as players arrive from child worlds.
system.runInterval(() => {
for (const player of world.getAllPlayers()) {
updatePlayerScore(player);
}
}, 100); // every 5 seconds
// ─── Chat Commands ───────────────────────────────────────────────
function handleLobbyEggCommand(player) {
const { total, j, l, m } = countPlayerEggs(player);
player.sendMessage("§6[Easter Eggs] §fYour collection:");
player.sendMessage(` §aJamie's World: §e${j}§7/50 ${"§2▓".repeat(Math.floor(j / 5))}§8${"░".repeat(10 - Math.floor(j / 5))}`);
player.sendMessage(` §dLyla's World: §e${l}§7/50 ${"§5▓".repeat(Math.floor(l / 5))}§8${"░".repeat(10 - Math.floor(l / 5))}`);
player.sendMessage(` §bMya's World: §e${m}§7/50 ${"§3▓".repeat(Math.floor(m / 5))}§8${"░".repeat(10 - Math.floor(m / 5))}`);
player.sendMessage(` §6Total: §e${total}§7/150`);
if (total === 150) {
player.sendMessage("§a§l🥚 YOU FOUND ALL 150 EGGS! You are the Easter Champion! 🥚");
} else if (total >= 100) {
player.sendMessage("§6Almost there! Just " + (150 - total) + " more to go!");
} else if (total >= 50) {
player.sendMessage("§eGreat progress! Keep exploring each world.");
} else if (total === 0) {
player.sendMessage("§7Visit Jamie, Lyla, and Mya's worlds to find hidden eggs!");
}
}
try {
world.beforeEvents.chatSend.subscribe((event) => {
const msg = event.message.trim().toLowerCase();
if (msg === "!eggs") {
event.cancel = true;
const player = event.sender;
system.run(() => handleLobbyEggCommand(player));
}
});
} catch (e) {
// beforeEvents.chatSend requires beta API — chat commands unavailable
}
system.afterEvents.scriptEventReceive.subscribe((event) => {
if (event.id !== "eggs:cmd") return;
const player = event.sourceEntity;
if (!player) return;
if ((event.message || "").trim().toLowerCase() === "eggs") {
handleLobbyEggCommand(player);
}
});
// ─── Startup ─────────────────────────────────────────────────────
system.runTimeout(() => {
ensureScoreboard();
// Update scores for anyone already online
for (const player of world.getAllPlayers()) {
updatePlayerScore(player);
}
}, 20);
system.run(() => {
world.sendMessage("§6[Easter Eggs] §fLeaderboard active! Type §e!eggs §fto see your full collection.");
});