From d7f9fe9af38b871462ec8f685c2197f71300a1f1 Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Sun, 14 Dec 2025 13:28:04 +0100 Subject: [PATCH] Stagger floating goal reminders --- src/main.js | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/main.js b/src/main.js index b2104d5..9d7602f 100644 --- a/src/main.js +++ b/src/main.js @@ -139,6 +139,8 @@ let score = 0; let highScore = 0; let longestChainRecord = 0; + const goalMilestoneThresholds = [0.5, 0.75, 0.9]; + let announcedGoalMilestones = new Set(); let clearedCount = 0; let clearedByColor = {}; let gameOver = false; @@ -483,6 +485,7 @@ score = 0; clearedCount = 0; clearedByColor = {}; + announcedGoalMilestones.clear(); endDrag(); const winCond = currentScene?.config?.winCondition; if (winCond?.type === "timer") { @@ -517,7 +520,6 @@ spawnInitialBurst(); startSpawner(); } - announceGoalMessage(); }; const setHighlight = (body, on) => { @@ -896,6 +898,7 @@ }); const goal = getGoalState(); ui.setGoal(goal || { label: "—", progress: 0 }); + maybeAnnounceGoalProgress(goal); }; const buildLegend = () => { @@ -1017,6 +1020,7 @@ label: `${String(Math.floor(remainingSec / 60)).padStart(2, "0")}:${String(remainingSec % 60).padStart(2, "0")}`, progress: duration > 0 ? (100 * elapsed) / duration : 0, met: remainingMs <= 0, + hint: "Survive until the timer ends", }; } if (winCond.type === "clearCount") { @@ -1026,6 +1030,7 @@ label: `Clear ${target} balls (${remaining} left)`, progress: target > 0 ? (100 * clearedCount) / target : 0, met: clearedCount >= target, + hint: "Clear the required balls", }; } if (winCond.type === "score") { @@ -1035,6 +1040,7 @@ label: `Score ${target} (${remaining} left)`, progress: target > 0 ? (100 * score) / target : 0, met: score >= target, + hint: "Reach the score target", }; } if (winCond.type === "colorClear" && Array.isArray(winCond.targets)) { @@ -1062,14 +1068,16 @@ (clearedByColor[normalizeColor(t.color)] || 0) >= (t.count || 0), ), colors: targets.map((t) => t.color), + hint: "Clear the target colors", }; } return null; }; const formatGoalMessage = (goal) => { - if (!goal || !goal.label || goal.label === "—") return null; - const pieces = [`Goal: ${goal.label}`]; + if (!goal) return null; + const title = goal.hint || goal.label || "Goal"; + const pieces = [`Goal: ${title}`]; if (Number.isFinite(goal.progress)) { const pct = Math.max(0, Math.min(100, Math.round(goal.progress))); pieces.push(`${pct}% complete`); @@ -1077,21 +1085,28 @@ return pieces.join(" • "); }; - const announceGoalMessage = () => { - const goal = getGoalState(); - const text = config.messages?.text || formatGoalMessage(goal); - if (!text) return; - const colors = - (Array.isArray(config.messages?.colors) && config.messages.colors) || - goal?.colors || - null; - ui.showFloatingMessage( - { text, colors }, - { - durationMs: config.messages.durationMs, - position: config.messages.position, - }, - ); + const maybeAnnounceGoalProgress = (goal) => { + if (!goal || !Number.isFinite(goal.progress)) return; + const fraction = Math.max(0, Math.min(1, goal.progress / 100)); + for (const threshold of goalMilestoneThresholds) { + if (fraction >= threshold && !announcedGoalMilestones.has(threshold)) { + announcedGoalMilestones.add(threshold); + const text = config.messages?.text || formatGoalMessage(goal); + if (!text) return; + const colors = + (Array.isArray(config.messages?.colors) && config.messages.colors) || + goal?.colors || + null; + ui.showFloatingMessage( + { text, colors }, + { + durationMs: config.messages.durationMs, + position: config.messages.position, + }, + ); + break; + } + } }; const clampBodiesIntoView = (prevWidth, prevHeight) => {