"use client"; // 回响星核 / Echo Nexus — 游戏主入口 import { useState, useEffect } from "react"; import { StarfieldCanvas } from "@/components/game/StarfieldCanvas"; import { ResourceBar } from "@/components/game/ResourceBar"; import { CrystalOrb } from "@/components/game/CrystalOrb"; import { DecodePanel } from "@/components/game/DecodeArray"; import { TechTree } from "@/components/game/TechTree"; import { Codex } from "@/components/game/Codex"; import { PrestigeDialog } from "@/components/game/PrestigeDialog"; import { SettingsDialog } from "@/components/game/SettingsDialog"; import { ExpeditionPanel } from "@/components/game/ExpeditionPanel"; import { AchievementsPanel } from "@/components/game/AchievementsPanel"; import { AchievementNotifier } from "@/components/game/AchievementNotifier"; import { ConstellationPanel } from "@/components/game/ConstellationPanel"; import { ConstellationDialog } from "@/components/game/ConstellationDialog"; import { ChronicleDialog } from "@/components/game/ChronicleDialog"; import { BeaconPanel } from "@/components/game/BeaconPanel"; import { TutorialOverlay } from "@/components/game/TutorialOverlay"; import { OfflineReportDialog } from "@/components/game/OfflineReportDialog"; import { CruiseMode } from "@/components/game/CruiseMode"; import { StarTideNotifier, StarTideIndicator, StarTideOverlay, } from "@/components/game/StarTide"; import { useGameLoop } from "@/hooks/useGameLoop"; import { useAudioSync } from "@/hooks/useAudio"; import { useGameStore } from "@/store/gameStore"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { Settings, RotateCcw, Sparkles, Cpu, BookOpen, BarChart3, Rocket, Github, Trophy, Star, Radio, Navigation, } from "lucide-react"; import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config"; import { ACHIEVEMENTS } from "@/lib/game/achievements"; import { TIDE_EVENTS } from "@/lib/game/starTide"; import { CONSTELLATION_PERKS } from "@/lib/game/constellation"; import { generateDailyChallenge, loadDailyProgress, loadLeaderboard } from "@/lib/game/beacon"; export default function Page() { useGameLoop(); useAudioSync(); const [prestigeOpen, setPrestigeOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const [constellationOpen, setConstellationOpen] = useState(false); const [chronicleOpen, setChronicleOpen] = useState(false); const [cruiseOpen, setCruiseOpen] = useState(false); const [mounted, setMounted] = useState(false); const contact = useGameStore((s) => s.contact); const totalDecoded = useGameStore((s) => s.totalDecoded); const crystalsPerSec = useGameStore((s) => s.crystalsPerSec); const ascensions = useGameStore((s) => s.ascensions); const ownedTech = useGameStore((s) => s.tech); const ownedFragments = useGameStore((s) => s.fragments); const ownedAchievements = useGameStore((s) => s.achievements); const ownedConstellation = useGameStore((s) => s.constellation ?? []); const pendingPerkChoices = useGameStore((s) => s.pendingPerkChoices); const crystals = useGameStore((s) => s.crystals); const crystalCap = useGameStore((s) => s.crystalCap); const activeTide = useGameStore((s) => s.activeTide); const energy = useGameStore((s) => s.energy); const hasActiveExpedition = useGameStore((s) => !!s.activeExpedition && !s.activeExpedition.finished); const chronicleCount = useGameStore((s) => s.chronicle?.length ?? 0); // 深空信标:检测是否有可领取的奖励(独立 localStorage) const [beaconClaimable, setBeaconClaimable] = useState(false); useEffect(() => { let cancelled = false; const check = () => { try { const c = generateDailyChallenge(); const p = loadDailyProgress(); if (!cancelled) { setBeaconClaimable(p.completedAt !== null && !p.claimed && p.dateKey === c.dateKey); } } catch { if (!cancelled) setBeaconClaimable(false); } }; check(); const id = setInterval(check, 2000); return () => { cancelled = true; clearInterval(id); }; }, []); // 挂载检测:避免持久化 store 在 SSR/CSR 间产生 hydration 不一致 useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect setMounted(true); }, []); // 防止 SSR/CSR 不一致 if (!mounted) { return (
唤醒回响中…
); } const ownedTechCount = Object.values(ownedTech).filter((v) => v > 0).length; const ownedFragCount = Object.values(ownedFragments).filter(Boolean).length; const ownedAchCount = Object.values(ownedAchievements).filter(Boolean).length; const canPrestige = contact >= 100; const hasPendingPerk = pendingPerkChoices && pendingPerkChoices.length > 0; // 仓库满仓警告 const warehouseFull = crystals >= crystalCap * 0.98; // 目标提示 let goal = "点击中央晶体发起脉冲,累积记忆晶体"; if (totalDecoded === 0 && crystals >= 5) { goal = "右侧「待解码晶体」点击一颗晶体,开始解码"; } else if (totalDecoded > 0 && ownedTechCount === 0) { goal = "用洞见解锁技术树,提升产能"; } else if (totalDecoded > 0 && ownedFragCount < FRAGMENTS.length) { goal = `继续解码,拼凑记忆图谱(${ownedFragCount}/${FRAGMENTS.length})`; } if (energy >= 1 && totalDecoded >= 3) { goal = "「探险」标签可深入遗迹,获取丰厚奖励"; } if (ascensions >= 1 && ownedConstellation.length > 0) { goal = `星图天赋已觉醒 ${ownedConstellation.length}/18,继续飞升获取更多`; } if (warehouseFull) { goal = "⚠ 仓库已满,产能浪费中!请解码晶体或升级仓库"; } if (hasPendingPerk) { goal = "✦ 星图觉醒!点击顶部「星图」按钮选择一道天赋"; } if (activeTide) { const tm = TIDE_EVENTS[activeTide.type]; goal = `${tm.icon} 星潮「${tm.name}」进行中 · ${tm.desc}`; } if (canPrestige) goal = "✦ 接触进度已满,可发起飞升进入新周目"; return (
{/* 星空背景 */} {/* 星潮背景叠层 */} {/* 顶部 Header */}

回响星核

ECHO NEXUS · v0.6

{hasPendingPerk && ( )} {canPrestige && ( )}
setPrestigeOpen(true)} />
{/* 主体 — 小屏允许自然滚动,避免 min-h 总和超过视口导致挤压重叠 */}
{/* 左侧:脉冲晶体 */}
{/* 装饰光圈 */}
{/* 右侧:解码 + 标签面板 */}
{/* 解码面板 */}
{/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 */}
探险 {energy >= 1 && !hasActiveExpedition && ( )} {hasActiveExpedition && ( )} 技术 星图 {hasPendingPerk && ( )} 图谱 成就 {ownedAchCount < ACHIEVEMENTS.length && ( )} 信标 {beaconClaimable && ( )} 统计
{/* 底部 Footer */}
{goal} | 产能 {formatNum(crystalsPerSec)}/s
{cruiseOpen && setCruiseOpen(false)} />}
); } function StatsPanel() { const s = useGameStore(); const achCount = Object.values(s.achievements).filter(Boolean).length; // 深空信标本地排行榜最高分(独立 localStorage) const [beaconBest, setBeaconBest] = useState(null); useEffect(() => { try { const lb = loadLeaderboard(); // eslint-disable-next-line react-hooks/set-state-in-effect setBeaconBest(lb.length > 0 ? lb[0].score : null); } catch { setBeaconBest(null); } }, []); const rows = [ { label: "累计解码晶体", value: `${s.totalDecoded} 颗` }, { label: "飞升周目", value: `${s.ascensions}` }, { label: "持有蓝图", value: `${s.blueprints.length} / 6` }, { label: "已学技术", value: `${Object.values(s.tech).filter((v) => v > 0).length} / ${TECH_TREE.length}` }, { label: "已获碎片", value: `${Object.values(s.fragments).filter(Boolean).length} / ${FRAGMENTS.length}` }, { label: "已解锁成就", value: `${achCount} / ${ACHIEVEMENTS.length}` }, { label: "星图天赋", value: `${s.constellation?.length ?? 0} / ${CONSTELLATION_PERKS.length}` }, { label: "接触进度", value: `${s.contact.toFixed(1)}%` }, { label: "晶体产能", value: `${s.crystalsPerSec.toFixed(2)} /s` }, { label: "仓库上限", value: `${formatNum(s.crystalCap)}` }, { label: "洞见倍率", value: `×${s.insightMult.toFixed(2)}` }, { label: "累计探险", value: `${s.totalExpeditions} 次` }, { label: "BOSS 击破", value: `${s.bossKills ?? 0} 次` }, { label: "星潮亲历", value: `${(s.starTidesEncountered ?? []).length} / 6` }, { label: "编年史条目", value: `${(s.chronicle ?? []).length} 纪元` }, { label: "探险能量", value: `${Math.floor(s.energy)} / ${s.energyMax}` }, { label: "信标最高分", value: beaconBest !== null ? formatNum(beaconBest) : "—" }, ]; return (
{rows.map((r) => (
{r.label} {r.value}
))}
); }