v0.5: 深空信标系统 + 编年史历史 BUG 修复
新功能:深空信标(Deep Space Beacon) - 每日挑战:UTC 日期种子确定性生成,5 种类型 × 3 档难度 - 本地排行榜 Top 20,奖牌图标 + 难度色点 + 今日高亮 - 进度追踪独立 localStorage,不污染 GameState - 倒计时 + 领取奖励发放到游戏状态 BUG 修复:编年史历史条目显示原始 tide 键名 - 新增 regenerateLoreFromEntry(),显示时重新生成 lore - 修复 v0.4 之前条目 lore 中 tide_ruins 等原始键名 UI:第 7 标签页「信标」+ 统计面板「信标最高分」行 版本号 v0.4 → v0.5 详见 docs/10-深空信标系统-v0.5.md
This commit is contained in:
+57
-9
@@ -15,6 +15,7 @@ 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 {
|
||||
StarTideNotifier,
|
||||
StarTideIndicator,
|
||||
@@ -36,11 +37,13 @@ import {
|
||||
Github,
|
||||
Trophy,
|
||||
Star,
|
||||
Radio,
|
||||
} 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();
|
||||
@@ -67,6 +70,29 @@ export default function Page() {
|
||||
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
|
||||
@@ -138,7 +164,7 @@ export default function Page() {
|
||||
<div className="leading-tight">
|
||||
<h1 className="text-base sm:text-lg font-bold text-gradient">回响星核</h1>
|
||||
<p className="text-[9px] sm:text-[10px] text-muted-foreground/70 -mt-0.5 tracking-wider">
|
||||
ECHO NEXUS · v0.4
|
||||
ECHO NEXUS · v0.5
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -212,11 +238,11 @@ export default function Page() {
|
||||
<div className="glass rounded-2xl p-4 flex-1 min-h-[360px] max-h-[560px]">
|
||||
<DecodePanel />
|
||||
</div>
|
||||
{/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 统计 */}
|
||||
{/* 标签面板:探险 / 技术 / 星图 / 图谱 / 成就 / 信标 / 统计 */}
|
||||
<div className="glass rounded-2xl p-3 min-h-[320px] max-h-[440px]">
|
||||
<Tabs defaultValue={hasActiveExpedition ? "expedition" : hasPendingPerk ? "constellation" : "tech"} className="h-full flex flex-col">
|
||||
<TabsList className="grid grid-cols-6 h-8 bg-black/30">
|
||||
<TabsTrigger value="expedition" className="text-[11px] gap-0.5 relative px-1">
|
||||
<TabsList className="grid grid-cols-7 h-8 bg-black/30">
|
||||
<TabsTrigger value="expedition" className="text-[11px] gap-0.5 relative px-0.5">
|
||||
<Rocket className="h-3 w-3" />
|
||||
探险
|
||||
{energy >= 1 && !hasActiveExpedition && (
|
||||
@@ -226,29 +252,36 @@ export default function Page() {
|
||||
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-rose-400 animate-pulse" />
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="tech" className="text-[11px] gap-0.5 px-1">
|
||||
<TabsTrigger value="tech" className="text-[11px] gap-0.5 px-0.5">
|
||||
<Cpu className="h-3 w-3" />
|
||||
技术
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="constellation" className="text-[11px] gap-0.5 relative px-1">
|
||||
<TabsTrigger value="constellation" className="text-[11px] gap-0.5 relative px-0.5">
|
||||
<Star className="h-3 w-3" />
|
||||
星图
|
||||
{hasPendingPerk && (
|
||||
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-fuchsia-400 animate-pulse" />
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="codex" className="text-[11px] gap-0.5 px-1">
|
||||
<TabsTrigger value="codex" className="text-[11px] gap-0.5 px-0.5">
|
||||
<BookOpen className="h-3 w-3" />
|
||||
图谱
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ach" className="text-[11px] gap-0.5 relative px-1">
|
||||
<TabsTrigger value="ach" className="text-[11px] gap-0.5 relative px-0.5">
|
||||
<Trophy className="h-3 w-3" />
|
||||
成就
|
||||
{ownedAchCount < ACHIEVEMENTS.length && (
|
||||
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-amber-400 animate-pulse" />
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="stats" className="text-[11px] gap-0.5 px-1">
|
||||
<TabsTrigger value="beacon" className="text-[11px] gap-0.5 relative px-0.5">
|
||||
<Radio className="h-3 w-3" />
|
||||
信标
|
||||
{beaconClaimable && (
|
||||
<span className="absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="stats" className="text-[11px] gap-0.5 px-0.5">
|
||||
<BarChart3 className="h-3 w-3" />
|
||||
统计
|
||||
</TabsTrigger>
|
||||
@@ -268,6 +301,9 @@ export default function Page() {
|
||||
<TabsContent value="ach" className="flex-1 mt-2 min-h-0 data-[state=active]:flex data-[state=active]:flex-col">
|
||||
<AchievementsPanel />
|
||||
</TabsContent>
|
||||
<TabsContent value="beacon" className="flex-1 mt-2 min-h-0 data-[state=active]:flex data-[state=active]:flex-col">
|
||||
<BeaconPanel />
|
||||
</TabsContent>
|
||||
<TabsContent value="stats" className="flex-1 mt-2 min-h-0">
|
||||
<StatsPanel />
|
||||
</TabsContent>
|
||||
@@ -337,6 +373,17 @@ export default function Page() {
|
||||
function StatsPanel() {
|
||||
const s = useGameStore();
|
||||
const achCount = Object.values(s.achievements).filter(Boolean).length;
|
||||
// 深空信标本地排行榜最高分(独立 localStorage)
|
||||
const [beaconBest, setBeaconBest] = useState<number | null>(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}` },
|
||||
@@ -354,6 +401,7 @@ function StatsPanel() {
|
||||
{ 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 (
|
||||
<div className="grid grid-cols-2 gap-1.5 text-xs">
|
||||
|
||||
Reference in New Issue
Block a user