From e3b49f3ecc5c75b74a00d2c576dd60980c81ed33 Mon Sep 17 00:00:00 2001 From: JesseRWeigel Date: Mon, 16 Mar 2026 13:43:08 -0400 Subject: [PATCH] fix: music autoplay on first interaction and robust rune usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Music: - Listen for click/touch/keydown to start ambient music, working around browser autoplay policy that blocks audio before user interaction - Keep the setTimeout fallback for browsers that allow autoplay Runes: - Use item.stats object for stat type/value instead of only parsing name strings — fixes runes from old saves or non-standard formats - Fall back to random stat as last resort instead of silently failing Co-Authored-By: Claude Opus 4.6 (1M context) --- client/src/components/game/HuntersPath.tsx | 60 ++++++++++++++-------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/client/src/components/game/HuntersPath.tsx b/client/src/components/game/HuntersPath.tsx index 4aa4af2..4b06499 100644 --- a/client/src/components/game/HuntersPath.tsx +++ b/client/src/components/game/HuntersPath.tsx @@ -998,10 +998,27 @@ export default function HuntersPath() { return () => clearTimeout(timer); }, []); // Only run once on mount - // Start ambient music on mount + // Start ambient music after first user interaction (browsers block autoplay) useEffect(() => { - const timer = setTimeout(() => playMusic("ambient_music"), 2000); - return () => clearTimeout(timer); + let started = false; + const startMusic = () => { + if (started) return; + started = true; + playMusic("ambient_music"); + document.removeEventListener("click", startMusic); + document.removeEventListener("touchstart", startMusic); + document.removeEventListener("keydown", startMusic); + }; + document.addEventListener("click", startMusic, { once: false }); + document.addEventListener("touchstart", startMusic, { once: false }); + document.addEventListener("keydown", startMusic, { once: false }); + // Also try immediately in case autoplay is allowed + setTimeout(() => playMusic("ambient_music"), 2000); + return () => { + document.removeEventListener("click", startMusic); + document.removeEventListener("touchstart", startMusic); + document.removeEventListener("keydown", startMusic); + }; }, []); // eslint-disable-line react-hooks/exhaustive-deps // Periodic save for ancillary data (daily quests, statistics, prestige) @@ -2398,27 +2415,20 @@ export default function HuntersPath() { const rankMatch = runeName.match(/([A-Z])-grade/); const typeMatch = runeName.match(/([A-Z]+)-grade/); - if (!rankMatch || !typeMatch) { - logPush("Invalid rune format. Cannot use this rune."); - return; - } - - const rank = rankMatch[1]; - const rankIdx = RANKS.indexOf(rank as BossRank); - if (rankIdx === -1) { - logPush("Unknown rune rank. Cannot use this rune."); - return; - } - - // Determine stat type from rune name + // Determine stat type from rune name or stats let statType: keyof Player["stats"] | null = null; if (runeName.includes("STR")) statType = "STR"; else if (runeName.includes("AGI")) statType = "AGI"; else if (runeName.includes("INT")) statType = "INT"; else if (runeName.includes("VIT")) statType = "VIT"; else if (runeName.includes("LUCK")) statType = "LUCK"; - else { - // Random stat if not specified + else if (item.stats) { + // Try to get stat type from item stats object + const statKey = Object.keys(item.stats).find(k => ["STR", "AGI", "INT", "VIT", "LUCK"].includes(k)); + if (statKey) statType = statKey as keyof Player["stats"]; + } + if (!statType) { + // Random stat as last resort const statKeys: (keyof Player["stats"])[] = [ "STR", "AGI", @@ -2429,10 +2439,16 @@ export default function HuntersPath() { statType = statKeys[rand(0, statKeys.length - 1)]; } - // Calculate stat boost based on rank - const baseBoost = rankIdx + 1; // E=1, D=2, C=3, B=4, A=5, S=6 - const variance = rand(-1, 1); // Small variance - const statBoost = Math.max(1, baseBoost + variance); + // Get stat bonus: prefer item.stats value, fall back to rank-based calculation + let statBoost = 1; + if (item.stats && item.stats[statType]) { + statBoost = Math.max(1, item.stats[statType]); + } else { + const rankMatch = runeName.match(/([A-Z])-grade/); + const rankIdx = rankMatch ? Math.max(0, RANKS.indexOf(rankMatch[1] as BossRank)) : 0; + const baseBoost = rankIdx + 1; + statBoost = Math.max(1, baseBoost + rand(-1, 1)); + } // Apply stat boost setPlayer((p) => ({