fix(easter-egg): persistent cross-server counts via inventory basket; 2-block tall eggs
All checks were successful
Deploy Addons / deploy (push) Successful in 15s
All checks were successful
Deploy Addons / deploy (push) Successful in 15s
Player tags are server-local and wiped on transfer, breaking lobby scores. Switch to an Egg Basket (nether_star, custom nameTag "[j:N,l:N,m:N]") that travels with the player's inventory so the lobby can always read accurate counts. Child worlds rebuild lost tags from block-state on each player join (missing egg block = previously collected), keeping within-world deduplication intact. Basket is updated on every collection and after every tag rebuild. Lobby reads basket counts directly instead of tags. Also make eggs visually egg-shaped: 2 glazed-terracotta blocks tall with opposing facing_direction (2 bottom, 3 top) to create an oval silhouette. collectEgg() now clears both Y and Y+1; detection dy extended to 3.0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
import { world, system } from "@minecraft/server";
|
||||
import { world, system, ItemStack } 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 = "";
|
||||
@@ -13,7 +10,7 @@ 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 */ }
|
||||
} catch (e) {}
|
||||
|
||||
if (!PREFIX) {
|
||||
let worldName = "";
|
||||
@@ -31,7 +28,7 @@ if (!PREFIX) {
|
||||
}
|
||||
|
||||
// ─── Egg Positions ───────────────────────────────────────────────
|
||||
// 50 (dx, dz) offsets from world spawn.
|
||||
// 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 = [
|
||||
@@ -49,10 +46,7 @@ const EGG_OFFSETS = [
|
||||
[ 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.
|
||||
// ─── Scene Names ─────────────────────────────────────────────────
|
||||
|
||||
const SCENE_NAMES = [
|
||||
"atop a barrel",
|
||||
@@ -65,9 +59,102 @@ const SCENE_NAMES = [
|
||||
"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) {}
|
||||
}
|
||||
|
||||
// ─── Tag Rebuild ─────────────────────────────────────────────────
|
||||
// When a player (re)joins, rebuild their local found-egg tags based on which
|
||||
// egg blocks are actually missing from the world. This handles the case where
|
||||
// a player transferred away and their server-local tags were wiped.
|
||||
// After rebuilding, sync the basket count to the accurate value.
|
||||
|
||||
function rebuildTagsAndBasket(player) {
|
||||
const positions = getEggPositions();
|
||||
if (!positions || positions.length === 0) return;
|
||||
const overworld = world.getDimension("overworld");
|
||||
let found = 0;
|
||||
for (const egg of positions) {
|
||||
const tagId = `egg_${PREFIX}_${egg.id}`;
|
||||
if (player.hasTag(tagId)) {
|
||||
found++;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const block = overworld.getBlock({ x: egg.x, y: egg.y, z: egg.z });
|
||||
// Block gone (air/non-terracotta) means this egg was previously collected
|
||||
if (!block || !block.typeId.includes("glazed_terracotta")) {
|
||||
player.addTag(tagId);
|
||||
found++;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
// Update basket with accurate count for this world
|
||||
const counts = getBasketCounts(player);
|
||||
if (PREFIX === "j") updateBasket(player, found, counts.l, counts.m);
|
||||
else if (PREFIX === "l") updateBasket(player, counts.j, found, counts.m);
|
||||
else if (PREFIX === "m") updateBasket(player, counts.j, counts.l, found);
|
||||
}
|
||||
|
||||
// ─── Cached Positions ────────────────────────────────────────────
|
||||
|
||||
let eggPositions = null; // null = not yet loaded from storage
|
||||
let eggPositions = null;
|
||||
|
||||
function getEggPositions() {
|
||||
if (eggPositions !== null) return eggPositions;
|
||||
@@ -92,28 +179,27 @@ function findSurfaceY(dimension, x, spawnY, z) {
|
||||
if (block && block.typeId !== "minecraft:air") {
|
||||
return y + 1; // first empty block above the topmost solid block
|
||||
}
|
||||
} catch (e) { /* chunk not loaded — skip */ }
|
||||
} 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.
|
||||
|
||||
/**
|
||||
* 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
|
||||
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 of hay
|
||||
case 1: // Hay bale — egg resting on a bale
|
||||
cmds.push(`setblock ${x} ${surfY} ${z} hay_block`);
|
||||
eggY = surfY + 1;
|
||||
break;
|
||||
@@ -122,7 +208,7 @@ function buildScene(dim, sceneIndex, x, surfY, z) {
|
||||
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
|
||||
eggY = surfY; // egg rises up through the flower level
|
||||
break;
|
||||
|
||||
case 3: // Log perch — egg balanced atop a cut log
|
||||
@@ -130,11 +216,11 @@ function buildScene(dim, sceneIndex, x, surfY, z) {
|
||||
eggY = surfY + 1;
|
||||
break;
|
||||
|
||||
case 4: // Fence posts — egg elevated on a fence post pedestal
|
||||
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; // sits atop the centre post
|
||||
eggY = surfY + 1;
|
||||
break;
|
||||
|
||||
case 5: // Crafting table — egg left on a workbench
|
||||
@@ -142,46 +228,35 @@ function buildScene(dim, sceneIndex, x, surfY, z) {
|
||||
eggY = surfY + 1;
|
||||
break;
|
||||
|
||||
case 6: // Mossy ledge — egg raised on a small stone platform
|
||||
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 up in a low canopy of leaves
|
||||
// Lower ring of leaves at surfY+1 (partial enclosure)
|
||||
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`);
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
|
||||
// Always place the egg block last so it overwrites anything at its position
|
||||
cmds.push(`setblock ${x} ${eggY} ${z} ${EGG_BLOCK}`);
|
||||
// 2-block tall egg: opposing facing directions create an oval silhouette
|
||||
cmds.push(`setblock ${x} ${eggY} ${z} ${EGG_BLOCK} ["facing_direction":2]`);
|
||||
cmds.push(`setblock ${x} ${eggY + 1} ${z} ${EGG_BLOCK} ["facing_direction":3]`);
|
||||
|
||||
for (const cmd of cmds) {
|
||||
try { dim.runCommand(cmd); } catch (e) { /* non-fatal — chunk may not be loaded */ }
|
||||
try { dim.runCommand(cmd); } catch (e) {}
|
||||
}
|
||||
|
||||
return eggY;
|
||||
return eggY; // caller stores this as the bottom block Y
|
||||
}
|
||||
|
||||
// ─── Egg Placement ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Place all eggs around a centre position.
|
||||
* On first call: centre comes from the joining player's location.
|
||||
* Centre is stored so all players (and resets) use the same area.
|
||||
*
|
||||
* If eggs_placed is "true" but no egg blocks actually exist in the world
|
||||
* (e.g. blocks were never written because chunks weren't loaded), the flag
|
||||
* is cleared automatically and eggs are re-placed.
|
||||
*/
|
||||
function placeAllEggs(playerPos) {
|
||||
const overworld = world.getDimension("overworld");
|
||||
|
||||
@@ -196,16 +271,16 @@ function placeAllEggs(playerPos) {
|
||||
if (block && block.typeId.includes("glazed_terracotta")) found++;
|
||||
} catch (e) {}
|
||||
}
|
||||
if (found > 0) return; // eggs are genuinely there
|
||||
if (found > 0) return;
|
||||
}
|
||||
// Eggs flagged as placed but none found — clear and re-place
|
||||
// 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: use stored centre, or the joining player's position
|
||||
// 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");
|
||||
@@ -225,7 +300,6 @@ function placeAllEggs(playerPos) {
|
||||
}
|
||||
|
||||
const positions = [];
|
||||
|
||||
for (let i = 0; i < EGG_OFFSETS.length; i++) {
|
||||
const [dx, dz] = EGG_OFFSETS[i];
|
||||
const x = cx + dx;
|
||||
@@ -248,32 +322,41 @@ function placeAllEggs(playerPos) {
|
||||
|
||||
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;
|
||||
// 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.runCommand(`tag @s add ${tagId}`);
|
||||
player.dimension.runCommand(`setblock ${egg.x} ${egg.y} ${egg.z} air`);
|
||||
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);
|
||||
|
||||
// Remove both blocks of the 2-block tall egg
|
||||
player.dimension.runCommand(`setblock ${egg.x} ${egg.y} ${egg.z} air`);
|
||||
player.dimension.runCommand(`setblock ${egg.x} ${egg.y + 1} ${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.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${newCount}/50 §7in this world`
|
||||
`§6[Eggs] §fEgg §e#${egg.id} §f(${SCENE_NAMES[(egg.id - 1) % 8]}) §7| §e${count}/50 §7in this world`
|
||||
);
|
||||
|
||||
if (newCount === 50) {
|
||||
if (count === 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
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ─── Detection Loop ──────────────────────────────────────────────
|
||||
// dy <= 3.0 covers both blocks of a 2-block tall egg (bottom at eggY, top at eggY+1).
|
||||
|
||||
system.runInterval(() => {
|
||||
const positions = getEggPositions();
|
||||
@@ -285,13 +368,13 @@ system.runInterval(() => {
|
||||
|
||||
for (const egg of positions) {
|
||||
const tagId = `egg_${PREFIX}_${egg.id}`;
|
||||
if (tags.includes(tagId)) continue; // already found
|
||||
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) {
|
||||
if (dx <= 2.5 && dy <= 3.0 && dz <= 2.5) {
|
||||
collectEgg(player, egg, tagId);
|
||||
}
|
||||
}
|
||||
@@ -303,12 +386,11 @@ system.runInterval(() => {
|
||||
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.`);
|
||||
player.sendMessage("§7Hint: eggs are hidden in flower patches, on logs, barrels, hay bales, fences, crafting tables, mossy ledges, and leaf clusters.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -330,7 +412,7 @@ function handleEggCommand(player, msg) {
|
||||
} catch (e) {}
|
||||
eggPositions = null;
|
||||
player.sendMessage(`§6[Eggs] §fWorld prefix set to §e${arg}§f — re-placing eggs...`);
|
||||
system.runTimeout(() => placeAllEggs(), 20);
|
||||
system.runTimeout(() => placeAllEggs(player.location), 20);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -360,9 +442,7 @@ try {
|
||||
system.run(() => handleEggCommand(player, msg));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// beforeEvents.chatSend requires beta API — chat commands unavailable
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
system.afterEvents.scriptEventReceive.subscribe((event) => {
|
||||
if (event.id !== "eggs:cmd") return;
|
||||
@@ -372,18 +452,26 @@ system.afterEvents.scriptEventReceive.subscribe((event) => {
|
||||
});
|
||||
|
||||
// ─── Startup ─────────────────────────────────────────────────────
|
||||
// Egg placement fires on first player spawn, not server startup.
|
||||
// This guarantees spawn chunks are loaded before setblock commands run.
|
||||
// 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) => {
|
||||
if (eggsPlacedThisSession) return;
|
||||
eggsPlacedThisSession = true;
|
||||
// Capture player position now; chunks around them are guaranteed loaded
|
||||
const playerPos = event.player.location;
|
||||
// Short delay so the player is fully settled before blocks are placed
|
||||
system.runTimeout(() => placeAllEggs(playerPos), 40);
|
||||
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);
|
||||
}
|
||||
|
||||
// Rebuild lost tags and sync basket (80 ticks gives placeAllEggs time to finish)
|
||||
system.runTimeout(() => {
|
||||
try { rebuildTagsAndBasket(player); } catch (e) {}
|
||||
}, 80);
|
||||
});
|
||||
|
||||
system.run(() => {
|
||||
|
||||
@@ -2,8 +2,10 @@ import { world, system } from "@minecraft/server";
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────
|
||||
|
||||
const OBJECTIVE = "egg_count";
|
||||
const EGG_PATTERN = /^egg_[jlm]_\d+$/;
|
||||
const OBJECTIVE = "egg_count";
|
||||
const BASKET_ITEM = "minecraft:nether_star";
|
||||
const BASKET_HEAD = "§6Egg Basket ";
|
||||
const BASKET_REGEX = /\[j:(\d+),l:(\d+),m:(\d+)\]/;
|
||||
|
||||
// ─── Scoreboard Setup ────────────────────────────────────────────
|
||||
|
||||
@@ -11,28 +13,47 @@ 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
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
overworld.runCommand(`scoreboard objectives setdisplay sidebar ${OBJECTIVE} descending`);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ─── Egg Counting ────────────────────────────────────────────────
|
||||
// ─── Basket Reading ──────────────────────────────────────────────
|
||||
// Counts are read from the player's Egg Basket (nether_star with encoded nameTag).
|
||||
// The basket travels with the player's inventory across server transfers, making
|
||||
// it the reliable cross-world persistence mechanism — unlike tags which are
|
||||
// server-local and wiped on each transfer.
|
||||
|
||||
function getEggBasket(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;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
const basket = getEggBasket(player);
|
||||
if (!basket || !basket.nameTag) return { total: 0, j: 0, l: 0, m: 0 };
|
||||
const match = basket.nameTag.match(BASKET_REGEX);
|
||||
if (!match) return { total: 0, j: 0, l: 0, m: 0 };
|
||||
const j = parseInt(match[1], 10);
|
||||
const l = parseInt(match[2], 10);
|
||||
const m = parseInt(match[3], 10);
|
||||
return { total: j + l + m, j, l, m };
|
||||
}
|
||||
|
||||
// ─── Score Update ────────────────────────────────────────────────
|
||||
|
||||
function updatePlayerScore(player) {
|
||||
const counts = countPlayerEggs(player);
|
||||
try {
|
||||
@@ -42,8 +63,8 @@ function updatePlayerScore(player) {
|
||||
}
|
||||
|
||||
// ─── 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.
|
||||
// Wait 40 ticks (2 s) after spawn for inventory to fully load,
|
||||
// then update scoreboard and show a welcome subtitle.
|
||||
|
||||
world.afterEvents.playerSpawn.subscribe((event) => {
|
||||
const player = event.player;
|
||||
@@ -81,14 +102,14 @@ function handleLobbyEggCommand(player) {
|
||||
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) {
|
||||
if (total === 0) {
|
||||
player.sendMessage("§7Visit Jamie, Lyla, and Mya's worlds to find hidden eggs! Look in flower patches, on barrels, hay bales, logs and more.");
|
||||
} else 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!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +122,7 @@ try {
|
||||
system.run(() => handleLobbyEggCommand(player));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// beforeEvents.chatSend requires beta API — chat commands unavailable
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
system.afterEvents.scriptEventReceive.subscribe((event) => {
|
||||
if (event.id !== "eggs:cmd") return;
|
||||
@@ -118,7 +137,6 @@ system.afterEvents.scriptEventReceive.subscribe((event) => {
|
||||
|
||||
system.runTimeout(() => {
|
||||
ensureScoreboard();
|
||||
// Update scores for anyone already online
|
||||
for (const player of world.getAllPlayers()) {
|
||||
updatePlayerScore(player);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user