feat(easter-eggs): add easter egg hunt addon across all worlds
All checks were successful
Deploy Addons / deploy (push) Successful in 17s
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:
@@ -7,6 +7,7 @@ on:
|
|||||||
- 'addon/**'
|
- 'addon/**'
|
||||||
- 'lobby-addon/**'
|
- 'lobby-addon/**'
|
||||||
- 'hub-return-addon/**'
|
- 'hub-return-addon/**'
|
||||||
|
- 'easter-egg-addon/**'
|
||||||
- 'docker-compose.yml'
|
- 'docker-compose.yml'
|
||||||
- 'scripts/**'
|
- 'scripts/**'
|
||||||
|
|
||||||
@@ -31,11 +32,11 @@ jobs:
|
|||||||
git init
|
git init
|
||||||
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
git remote add origin https://git.silverlabs.uk/SilverLABS/minecraft-aiworld.git
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/
|
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ docker-compose.yml
|
||||||
else
|
else
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
git fetch origin main
|
git fetch origin main
|
||||||
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/
|
git checkout -f origin/main -- addon/ lobby-addon/ hub-return-addon/ easter-egg-addon/ docker-compose.yml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Restart all 4 Minecraft containers to pick up changes
|
# Restart all 4 Minecraft containers to pick up changes
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ services:
|
|||||||
- ./lobby-addon/lobby_transfer_RP:/data/resource_packs/lobby_transfer_RP
|
- ./lobby-addon/lobby_transfer_RP:/data/resource_packs/lobby_transfer_RP
|
||||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
|
- ./easter-egg-addon/easter_egg_lobby_BP:/data/behavior_packs/easter_egg_lobby_BP
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
@@ -48,6 +49,7 @@ services:
|
|||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
|
- ./easter-egg-addon/easter_egg_child_BP:/data/behavior_packs/easter_egg_child_BP
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
@@ -74,6 +76,7 @@ services:
|
|||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
|
- ./easter-egg-addon/easter_egg_child_BP:/data/behavior_packs/easter_egg_child_BP
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
@@ -100,6 +103,7 @@ services:
|
|||||||
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
- ./hub-return-addon/hub_return_transfer_BP:/data/behavior_packs/hub_return_transfer_BP
|
||||||
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
- ./addon/spark_pet_BP:/data/behavior_packs/spark_pet_BP
|
||||||
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
- ./addon/spark_pet_RP:/data/resource_packs/spark_pet_RP
|
||||||
|
- ./easter-egg-addon/easter_egg_child_BP:/data/behavior_packs/easter_egg_child_BP
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- mc-network
|
- mc-network
|
||||||
|
|||||||
29
easter-egg-addon/easter_egg_child_BP/manifest.json
Normal file
29
easter-egg-addon/easter_egg_child_BP/manifest.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "Easter Egg Hunt",
|
||||||
|
"description": "Hides 50 easter eggs around spawn for players to find",
|
||||||
|
"uuid": "c1e2a3b4-5678-9012-abcd-ef1234567890",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1, 21, 0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "script",
|
||||||
|
"language": "javascript",
|
||||||
|
"uuid": "d2f3b4c5-6789-0123-bcde-f12345678901",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"entry": "scripts/main.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server",
|
||||||
|
"version": "1.17.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server-admin",
|
||||||
|
"version": "1.0.0-beta"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
333
easter-egg-addon/easter_egg_child_BP/scripts/main.js
Normal file
333
easter-egg-addon/easter_egg_child_BP/scripts/main.js
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
import { world, system } from "@minecraft/server";
|
||||||
|
import { variables } from "@minecraft/server-admin";
|
||||||
|
|
||||||
|
// ─── World Detection ─────────────────────────────────────────────
|
||||||
|
// Each world uses a unique prefix and egg colour.
|
||||||
|
// Detection follows the hub-return pattern: read variables.json world_name.
|
||||||
|
// Persisted in dynamic properties so it only detects once.
|
||||||
|
|
||||||
|
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) { /* will detect below */ }
|
||||||
|
|
||||||
|
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 world spawn.
|
||||||
|
// 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 Descriptions ──────────────────────────────────────────
|
||||||
|
// 8 hiding scenes cycle across the 50 eggs (6-7 per type).
|
||||||
|
// Each scene places supporting blocks then the egg itself.
|
||||||
|
// Eggs are visible but require exploration to find.
|
||||||
|
|
||||||
|
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",
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Cached Positions ────────────────────────────────────────────
|
||||||
|
|
||||||
|
let eggPositions = null; // null = not yet loaded from storage
|
||||||
|
|
||||||
|
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) { /* chunk not loaded — skip */ }
|
||||||
|
}
|
||||||
|
return spawnY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scene Builder ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a hiding scene and places the egg.
|
||||||
|
* Returns the actual Y coordinate the egg was placed at.
|
||||||
|
*/
|
||||||
|
function buildScene(dim, sceneIndex, x, surfY, z) {
|
||||||
|
const cmds = [];
|
||||||
|
let eggY = surfY;
|
||||||
|
|
||||||
|
switch (sceneIndex % 8) {
|
||||||
|
case 0: // Barrel nook — coloured 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 of hay
|
||||||
|
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 at centre, same level as the flowers
|
||||||
|
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 post 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; // sits atop the centre post
|
||||||
|
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 small 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 up in a low canopy of leaves
|
||||||
|
// Lower ring of leaves at surfY+1 (partial enclosure)
|
||||||
|
cmds.push(`setblock ${x+1} ${surfY+1} ${z} oak_leaves`);
|
||||||
|
cmds.push(`setblock ${x-1} ${surfY+1} ${z} oak_leaves`);
|
||||||
|
// Upper ring of leaves at surfY+2 (surrounds the egg)
|
||||||
|
cmds.push(`setblock ${x+1} ${surfY+2} ${z} oak_leaves`);
|
||||||
|
cmds.push(`setblock ${x-1} ${surfY+2} ${z} oak_leaves`);
|
||||||
|
cmds.push(`setblock ${x} ${surfY+2} ${z+1} oak_leaves`);
|
||||||
|
cmds.push(`setblock ${x} ${surfY+2} ${z-1} oak_leaves`);
|
||||||
|
eggY = surfY + 2; // visible from the side through the semi-transparent leaves
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always place the egg block last so it overwrites anything at its position
|
||||||
|
cmds.push(`setblock ${x} ${eggY} ${z} ${EGG_BLOCK}`);
|
||||||
|
|
||||||
|
for (const cmd of cmds) {
|
||||||
|
try { dim.runCommand(cmd); } catch (e) { /* non-fatal — chunk may not be loaded */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
return eggY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Egg Placement ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function placeAllEggs() {
|
||||||
|
if (world.getDynamicProperty("eggs_placed") === "true") return;
|
||||||
|
|
||||||
|
const spawn = world.getDefaultSpawnLocation();
|
||||||
|
const overworld = world.getDimension("overworld");
|
||||||
|
const positions = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < EGG_OFFSETS.length; i++) {
|
||||||
|
const [dx, dz] = EGG_OFFSETS[i];
|
||||||
|
const x = Math.floor(spawn.x) + dx;
|
||||||
|
const z = Math.floor(spawn.z) + dz;
|
||||||
|
const surfY = findSurfaceY(overworld, x, Math.floor(spawn.y), 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 spawn — happy hunting!`);
|
||||||
|
} catch (e) {
|
||||||
|
world.sendMessage(`§c[Easter Eggs] §fFailed to save egg data: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Egg Collection ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
function collectEgg(player, egg, tagId) {
|
||||||
|
try {
|
||||||
|
// Count before adding the tag (getTags is synchronous)
|
||||||
|
const before = player.getTags().filter(t => t.startsWith(`egg_${PREFIX}_`)).length;
|
||||||
|
const newCount = before + 1;
|
||||||
|
|
||||||
|
player.runCommand(`tag @s add ${tagId}`);
|
||||||
|
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${newCount}/50 §7found in this world"}]}`);
|
||||||
|
player.sendMessage(
|
||||||
|
`§6[Eggs] §fEgg §e#${egg.id} §f(${SCENE_NAMES[(egg.id - 1) % 8]}) §7| §e${newCount}/50 §7in this world`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newCount === 50) {
|
||||||
|
world.sendMessage(`§6§l✨ ${player.name} found all 50 eggs! ✨ §r§6Return to the lobby for your final score!`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Player may have disconnected — non-fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 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; // already found
|
||||||
|
|
||||||
|
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;
|
||||||
|
const color = PREFIX === "j" ? "§a" : PREFIX === "l" ? "§d" : "§b";
|
||||||
|
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(`§7${color}Hint: 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(), 20);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg === "!reseteggsworld") {
|
||||||
|
try {
|
||||||
|
world.setDynamicProperty("eggs_placed", "false");
|
||||||
|
world.setDynamicProperty("eggs_data", "");
|
||||||
|
} catch (e) {}
|
||||||
|
eggPositions = null;
|
||||||
|
player.sendMessage("§6[Eggs] §fEgg data cleared — re-placing all eggs...");
|
||||||
|
system.runTimeout(() => placeAllEggs(), 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) {
|
||||||
|
// 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;
|
||||||
|
handleEggCommand(player, `!${(event.message || "").trim().toLowerCase()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Startup ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
system.runTimeout(() => {
|
||||||
|
placeAllEggs();
|
||||||
|
}, 120); // 6 second delay to allow world chunks to load near spawn
|
||||||
|
|
||||||
|
system.run(() => {
|
||||||
|
world.sendMessage("§6[Easter Eggs] §fHunt active! Type §e!eggs §fto check your progress.");
|
||||||
|
});
|
||||||
25
easter-egg-addon/easter_egg_lobby_BP/manifest.json
Normal file
25
easter-egg-addon/easter_egg_lobby_BP/manifest.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
129
easter-egg-addon/easter_egg_lobby_BP/scripts/main.js
Normal file
129
easter-egg-addon/easter_egg_lobby_BP/scripts/main.js
Normal 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.");
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user