feat(v0.3.1): 星图天文台元进程天赋系统 — 18 天赋 / 3 选 1 draft / Canvas 动态星图
- 新增 src/lib/game/constellation.ts:6 类别 × 3 天赋 = 18 个永久天赋, constellationBonuses() 聚合 19 项修饰器,rollPerkChoices() 保证不同类别 - 状态扩展:GameState.constellation / pendingPerkChoices - engine.ts: recomputeStats 聚合三层加成(技术+蓝图+成就+星图), performPrestige 触发天赋选择 + 飞升礼包补偿,新增 rollCrystalTierWithBonus - expedition.ts: 探险力/生命应用星座修饰器 - gameStore.ts: 新增 chooseConstellationPerk + rerollPerkChoices 动作, 全链路接入(tickTide/autoDecodeTick/clickNode 等用点) - 新增 ConstellationPanel.tsx: Canvas 动态星图(六边形 6 星座 × 3 星点, 闪烁/光晕/十字光线/连接线/中心星核呼吸)+ Hover 提示 + 类别图例 - 新增 ConstellationDialog.tsx: 飞升后自动弹出,3 卡片 draft + 重新抽取 - PrestigeDialog: 增加「星图觉醒预告」卡片 - page.tsx: 6 列标签栏 + 顶部「觉醒」按钮 + 统计面板加星图天赋 - audio.ts: 新增 constellation SFX(上升琶音 + 高频闪光) - achievements.ts: 新增「星图初绘」「六分星辉」2 项成就 - QA: agent-browser + VLM 全流程通过;lint 零错误;编译 < 200ms - 二次回应 Issue #1:从「连连看」扩展为 6 大玩法层 + 元进程 draft
This commit is contained in:
+39
-4
@@ -12,6 +12,8 @@ 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 {
|
||||
StarTideNotifier,
|
||||
StarTideIndicator,
|
||||
@@ -32,16 +34,19 @@ import {
|
||||
Rocket,
|
||||
Github,
|
||||
Trophy,
|
||||
Star,
|
||||
} 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";
|
||||
|
||||
export default function Page() {
|
||||
useGameLoop();
|
||||
useAudioSync();
|
||||
const [prestigeOpen, setPrestigeOpen] = useState(false);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [constellationOpen, setConstellationOpen] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const contact = useGameStore((s) => s.contact);
|
||||
@@ -51,6 +56,8 @@ export default function Page() {
|
||||
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);
|
||||
@@ -75,7 +82,9 @@ export default function Page() {
|
||||
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 ownedConstCount = ownedConstellation.length;
|
||||
const canPrestige = contact >= 100;
|
||||
const hasPendingPerk = pendingPerkChoices && pendingPerkChoices.length > 0;
|
||||
|
||||
// 仓库满仓警告
|
||||
const warehouseFull = crystals >= crystalCap * 0.98;
|
||||
@@ -95,6 +104,9 @@ export default function Page() {
|
||||
if (warehouseFull) {
|
||||
goal = "⚠ 仓库已满,产能浪费中!请解码晶体或升级仓库";
|
||||
}
|
||||
if (hasPendingPerk) {
|
||||
goal = "✦ 星图觉醒!点击顶部「星图」按钮选择一道天赋";
|
||||
}
|
||||
if (activeTide) {
|
||||
const tm = TIDE_EVENTS[activeTide.type];
|
||||
goal = `${tm.icon} 星潮「${tm.name}」进行中 · ${tm.desc}`;
|
||||
@@ -121,12 +133,23 @@ 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.3
|
||||
ECHO NEXUS · v0.3.1
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-1.5">
|
||||
<StarTideIndicator />
|
||||
{hasPendingPerk && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setConstellationOpen(true)}
|
||||
className="border-fuchsia-400/60 text-fuchsia-200 hover:bg-fuchsia-500/15 h-8 px-2.5 animate-pulse"
|
||||
>
|
||||
<Star className="h-3.5 w-3.5 mr-1" />
|
||||
觉醒
|
||||
</Button>
|
||||
)}
|
||||
{canPrestige && (
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -169,10 +192,10 @@ 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" : "tech"} className="h-full flex flex-col">
|
||||
<TabsList className="grid grid-cols-5 h-8 bg-black/30">
|
||||
<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">
|
||||
<Rocket className="h-3 w-3" />
|
||||
探险
|
||||
@@ -187,6 +210,13 @@ export default function Page() {
|
||||
<Cpu className="h-3 w-3" />
|
||||
技术
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="constellation" className="text-[11px] gap-0.5 relative px-1">
|
||||
<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">
|
||||
<BookOpen className="h-3 w-3" />
|
||||
图谱
|
||||
@@ -209,6 +239,9 @@ export default function Page() {
|
||||
<TabsContent value="tech" className="flex-1 mt-2 min-h-0 data-[state=active]:flex data-[state=active]:flex-col">
|
||||
<TechTree />
|
||||
</TabsContent>
|
||||
<TabsContent value="constellation" className="flex-1 mt-2 min-h-0 data-[state=active]:flex data-[state=active]:flex-col">
|
||||
<ConstellationPanel />
|
||||
</TabsContent>
|
||||
<TabsContent value="codex" className="flex-1 mt-2 min-h-0 data-[state=active]:flex data-[state=active]:flex-col">
|
||||
<Codex />
|
||||
</TabsContent>
|
||||
@@ -272,6 +305,7 @@ export default function Page() {
|
||||
</footer>
|
||||
|
||||
<PrestigeDialog open={prestigeOpen} onOpenChange={setPrestigeOpen} />
|
||||
<ConstellationDialog open={constellationOpen} onOpenChange={setConstellationOpen} />
|
||||
<SettingsDialog open={settingsOpen} onOpenChange={setSettingsOpen} />
|
||||
<AchievementNotifier />
|
||||
<StarTideNotifier />
|
||||
@@ -289,6 +323,7 @@ function StatsPanel() {
|
||||
{ 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)}` },
|
||||
|
||||
Reference in New Issue
Block a user