"use client"; // 回响星核 / Echo Nexus — 游戏主入口 import { useState, useEffect, useRef } 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 { AttributesPanel } from "@/components/game/AttributesPanel"; import { IdleOperationsPanel } from "@/components/game/IdleOperationsPanel"; import { IdleStatusBadge } from "@/components/game/IdleStatusBadge"; import { IdleProjectBar } from "@/components/game/IdleProjectBar"; import { StatsPanel } from "@/components/game/StatsPanel"; 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, User, Clock, } from "lucide-react"; import { FRAGMENTS, formatNum } from "@/lib/game/config"; import { ACHIEVEMENTS } from "@/lib/game/achievements"; import { TIDE_EVENTS } from "@/lib/game/starTide"; import { generateDailyChallenge, loadDailyProgress } from "@/lib/game/beacon"; import { getUnlockedIdleProjects } from "@/lib/game/idle"; 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); const pendingAttrPoints = useGameStore((s) => s.pendingAttrPoints ?? 0); // v0.14 放置系统:用于驱动 goal 提示 + 标签默认值 const idleProjectSlots = useGameStore( (s) => (s.idleProjectSlots ?? [null, null, null]) as ( | { projectId: string; completed: boolean } | null )[] ); const createdAt = useGameStore((s) => s.createdAt); // 深空信标:检测是否有可领取的奖励(独立 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(() => { setMounted(true); }, []); // v0.14 放置系统:默认放置标签(在挂载后按优先级切换一次) // 注意:所有 hooks 必须在早期 return 之前调用,保持调用顺序一致 const [activeTab, setActiveTab] = useState("idle"); const tabInited = useRef(false); const hasPendingPerkEarly = !!(pendingPerkChoices && pendingPerkChoices.length > 0); useEffect(() => { if (tabInited.current) return; tabInited.current = true; if (hasActiveExpedition) { setActiveTab("expedition"); } else if (hasPendingPerkEarly) { setActiveTab("constellation"); } else { setActiveTab("idle"); } }, [hasActiveExpedition, hasPendingPerkEarly]); // 防止 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; // v0.14 放置系统相关 goal 提示 const idleClaimableCount = idleProjectSlots.filter( (s) => s !== null && s.completed ).length; const idleRunningCount = idleProjectSlots.filter( (s) => s !== null && !s.completed ).length; const playtimeSec = (Date.now() - (createdAt ?? Date.now())) / 1000; const unlockedIdleCount = getUnlockedIdleProjects({ ascensions, crystalsPerSec, }).length; // 目标提示 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 = "✦ 接触进度已满,可发起飞升进入新周目"; // v0.14 放置工程 goal 提示(高优先级,覆盖默认) if (idleClaimableCount > 0) { goal = `✦ 放置工程已完成 ${idleClaimableCount} 项,请前往「放置」标签领取奖励`; } else if ( idleRunningCount === 0 && unlockedIdleCount > 0 && playtimeSec > 60 ) { goal = "「放置」标签可派遣工程项目,离线自动产出"; } return (
{/* 星空背景 */} {/* 星潮背景叠层 */} {/* 顶部 Header */}

回响星核

ECHO NEXUS · v0.15

setActiveTab("idle")} /> {hasPendingPerk && ( )} {canPrestige && ( )}
setPrestigeOpen(true)} />
{/* 主体 — 小屏允许自然滚动,避免 min-h 总和超过视口导致挤压重叠 */}
{/* 左侧:脉冲晶体 */}
{/* 装饰光圈 */}
{/* v0.14 放置工程进度条(晶体球下方) */}
{/* 右侧:解码 + 标签面板 */}
{/* 解码面板 */}
{/* 标签面板:放置 / 探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 / 角色 */}
放置 {idleClaimableCount > 0 && ( {idleClaimableCount} )} 探险 {energy >= 1 && !hasActiveExpedition && ( )} {hasActiveExpedition && ( )} 技术 星图 {hasPendingPerk && ( )} 图谱 成就 {ownedAchCount < ACHIEVEMENTS.length && ( )} 信标 {beaconClaimable && ( )} 统计 角色 {pendingAttrPoints > 0 && ( )}
{/* 底部 Footer */}
{goal} | 产能 {formatNum(crystalsPerSec)}/s
{cruiseOpen && setCruiseOpen(false)} />}
); }