190 lines
7.3 KiB
TypeScript
Executable File
190 lines
7.3 KiB
TypeScript
Executable File
"use client";
|
||
// 回响星核 / Echo Nexus — 飞升(Prestige)对话框
|
||
import { useGameStore } from "@/store/gameStore";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
} from "@/components/ui/dialog";
|
||
import { Button } from "@/components/ui/button";
|
||
import { RotateCcw, Sparkles, Star, BookOpen } from "lucide-react";
|
||
import { PRESTIGE } from "@/lib/game/config";
|
||
import { computeNewBlueprints, computePrestigeBonus } from "@/lib/game/engine";
|
||
import { constellationProgress } from "@/lib/game/constellation";
|
||
import { useState } from "react";
|
||
import { useToast } from "@/hooks/use-toast";
|
||
import { sfx } from "@/hooks/useAudio";
|
||
|
||
export function PrestigeDialog({
|
||
open,
|
||
onOpenChange,
|
||
}: {
|
||
open: boolean;
|
||
onOpenChange: (v: boolean) => void;
|
||
}) {
|
||
const state = useGameStore();
|
||
const doPrestige = useGameStore((s) => s.doPrestige);
|
||
const { toast } = useToast();
|
||
const [confirming, setConfirming] = useState(false);
|
||
const [canReconfirm, setCanReconfirm] = useState(false);
|
||
|
||
const newBp = computeNewBlueprints(state);
|
||
const bonus = computePrestigeBonus(state);
|
||
const totalBpAfter = Math.min(PRESTIGE.maxBlueprints, state.blueprints.length + newBp);
|
||
const cProgress = constellationProgress(state.constellation ?? []);
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent className="bg-gradient-to-br from-fuchsia-950/70 to-black/90 border-fuchsia-400/40">
|
||
<DialogHeader>
|
||
<DialogTitle className="flex items-center gap-2 text-fuchsia-100">
|
||
<RotateCcw className="h-5 w-5" />
|
||
飞升 · 跨越维度
|
||
</DialogTitle>
|
||
<DialogDescription className="text-fuchsia-200/70">
|
||
当接触进度满溢,你将跨越维度与以太族相会。本次飞升将重置资源与技术,但保留记忆图谱与蓝图加成。
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<div className="space-y-3 py-2">
|
||
{/* 本次飞升获得 */}
|
||
<div className="rounded-lg border border-fuchsia-400/30 bg-fuchsia-950/30 p-3">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm text-fuchsia-200">本次获得蓝图</span>
|
||
<span className="text-2xl font-mono font-bold text-fuchsia-100">
|
||
+{newBp}
|
||
</span>
|
||
</div>
|
||
<p className="text-[11px] text-muted-foreground mt-1">
|
||
蓝图总数将达 {totalBpAfter}/{PRESTIGE.maxBlueprints}(每 {PRESTIGE.blueprintsPerDecode} 次解码产出 1 张)
|
||
</p>
|
||
</div>
|
||
|
||
{/* 永久加成 */}
|
||
<div className="grid grid-cols-3 gap-2">
|
||
<BonusCard
|
||
label="产能"
|
||
value={`+${(totalBpAfter * PRESTIGE.perBlueprint.crystalsPerSecMult * 100).toFixed(0)}%`}
|
||
color="#34d399"
|
||
/>
|
||
<BonusCard
|
||
label="洞见"
|
||
value={`+${(totalBpAfter * PRESTIGE.perBlueprint.insightMult * 100).toFixed(0)}%`}
|
||
color="#fbbf24"
|
||
/>
|
||
<BonusCard
|
||
label="接触"
|
||
value={`+${(totalBpAfter * PRESTIGE.perBlueprint.contactRateMult * 100).toFixed(0)}%`}
|
||
color="#e879f9"
|
||
/>
|
||
</div>
|
||
|
||
{/* 保留 / 重置 */}
|
||
<div className="text-[11px] text-muted-foreground space-y-1 pt-1">
|
||
<div className="flex items-center gap-1.5">
|
||
<Sparkles className="h-3 w-3 text-fuchsia-400" />
|
||
保留:记忆图谱、累计解码数、飞升次数、蓝图、星图天赋
|
||
</div>
|
||
<div className="flex items-center gap-1.5">
|
||
<RotateCcw className="h-3 w-3 text-rose-400" />
|
||
重置:晶体、洞见、技术树、接触进度、待解码晶体
|
||
</div>
|
||
</div>
|
||
|
||
{/* 星图觉醒预告 */}
|
||
<div className="rounded-lg border border-fuchsia-400/30 bg-gradient-to-r from-fuchsia-950/40 to-purple-950/40 p-2.5">
|
||
<div className="flex items-center gap-2">
|
||
<Star className="h-4 w-4 text-fuchsia-300" />
|
||
<div className="flex-1">
|
||
<div className="text-[12px] text-fuchsia-200 font-bold">星图觉醒预告</div>
|
||
<div className="text-[10px] text-muted-foreground">
|
||
飞升后将从 3 道随机天赋中选 1,永久获得加成
|
||
{cProgress.unlocked < cProgress.total && (
|
||
<span className="text-fuchsia-300/80"> · 当前 {cProgress.unlocked}/{cProgress.total}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="text-[10px] text-fuchsia-300/70 font-mono">
|
||
{cProgress.unlocked < cProgress.total ? "✦ 可觉醒" : "✧ 已圆满"}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 编年史铭刻提示(v0.4 新增) */}
|
||
<div className="rounded-lg border border-violet-400/25 bg-violet-950/20 p-2.5 flex items-center gap-2">
|
||
<BookOpen className="h-4 w-4 text-violet-300 flex-shrink-0" />
|
||
<div className="flex-1 text-[10px] text-violet-100/70 leading-relaxed">
|
||
本次飞升将铭刻入<span className="text-violet-200 font-bold">回响编年史</span>,
|
||
记录本周目的解码、探险、星潮与觉醒天赋,形成永恒叙事时间轴。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||
取消
|
||
</Button>
|
||
<Button
|
||
disabled={confirming && !canReconfirm}
|
||
onClick={() => {
|
||
if (!confirming) {
|
||
setConfirming(true);
|
||
setCanReconfirm(false);
|
||
window.setTimeout(() => setCanReconfirm(true), 900);
|
||
return;
|
||
}
|
||
const res = doPrestige();
|
||
onOpenChange(false);
|
||
setConfirming(false);
|
||
setCanReconfirm(false);
|
||
if (res) {
|
||
sfx("prestige");
|
||
// 编年史铭刻音效(稍延迟,与飞升音分层)
|
||
window.setTimeout(() => sfx("chronicle"), 600);
|
||
toast({
|
||
title: "✦ 飞升成功",
|
||
description: `获得 ${res.newBp} 张蓝图,进入第 ${state.ascensions + 2} 周目。编年史已铭刻新纪元。`,
|
||
});
|
||
}
|
||
}}
|
||
className="bg-gradient-to-r from-fuchsia-600 to-rose-500 hover:from-fuchsia-500 hover:to-rose-400 text-white border-0"
|
||
>
|
||
{confirming
|
||
? canReconfirm
|
||
? "再次确认飞升"
|
||
: "请确认…"
|
||
: newBp <= 0
|
||
? "飞升(无新蓝图)"
|
||
: "飞升"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|
||
|
||
function BonusCard({
|
||
label,
|
||
value,
|
||
color,
|
||
}: {
|
||
label: string;
|
||
value: string;
|
||
color: string;
|
||
}) {
|
||
return (
|
||
<div
|
||
className="rounded-lg border p-2 text-center"
|
||
style={{ borderColor: `${color}44`, background: `${color}11` }}
|
||
>
|
||
<div className="text-[10px] text-muted-foreground">{label}</div>
|
||
<div className="text-sm font-mono font-bold" style={{ color }}>
|
||
{value}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|