Files
echo-nexus/src/components/game/PrestigeDialog.tsx
T

190 lines
7.3 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}