216 lines
7.0 KiB
TypeScript
216 lines
7.0 KiB
TypeScript
"use client";
|
|
// 回响星核 / Echo Nexus — 探索目标雷达(v0.10 新增)
|
|
// 动态展示当前可解锁/可探索的下一目标,让"等待"变"期待"
|
|
// 缓解工单 #2 "占地方没用" 反馈 — 晶体球下方提供持续探索动力
|
|
import { useGameStore } from "@/store/gameStore";
|
|
import { TECH_TREE, FRAGMENTS, formatNum } from "@/lib/game/config";
|
|
import { ACHIEVEMENTS } from "@/lib/game/achievements";
|
|
import { CONSTELLATION_PERKS } from "@/lib/game/constellation";
|
|
import { EXPEDITION_CONFIG } from "@/lib/game/expedition";
|
|
import {
|
|
Target,
|
|
Cpu,
|
|
Trophy,
|
|
BookOpen,
|
|
Rocket,
|
|
Star,
|
|
TrendingDown,
|
|
CheckCircle2,
|
|
Sparkles,
|
|
} from "lucide-react";
|
|
|
|
interface TargetItem {
|
|
id: string;
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
color: string;
|
|
label: string;
|
|
detail: string;
|
|
progress: number; // 0-100
|
|
hint: string;
|
|
}
|
|
|
|
export function ExplorationRadar() {
|
|
const s = useGameStore();
|
|
|
|
// ===== 计算各系统的下一个目标 =====
|
|
const targets: TargetItem[] = [];
|
|
|
|
// 1. 下一个技术节点(按洞见排序)
|
|
const nextTech = TECH_TREE.filter((t) => !s.tech[t.id]).sort((a, b) => a.cost - b.cost)[0];
|
|
if (nextTech) {
|
|
const insightProgress = Math.min(100, (s.insights / nextTech.cost) * 100);
|
|
targets.push({
|
|
id: "tech_" + nextTech.id,
|
|
icon: Cpu,
|
|
color: "#34d399",
|
|
label: nextTech.name,
|
|
detail: `需 ${nextTech.cost} 洞见 · ${nextTech.desc}`,
|
|
progress: insightProgress,
|
|
hint: s.insights >= nextTech.cost ? "可解锁" : `还差 ${nextTech.cost - s.insights} 洞见`,
|
|
});
|
|
}
|
|
|
|
// 2. 下一个成就(按达成难度近似)
|
|
const nextAch = ACHIEVEMENTS.find((a) => !s.achievements[a.id]);
|
|
if (nextAch) {
|
|
targets.push({
|
|
id: "ach_" + nextAch.id,
|
|
icon: Trophy,
|
|
color: "#fbbf24",
|
|
label: nextAch.name,
|
|
detail: nextAch.desc,
|
|
progress: 30, // 成就进度难以精确计算,给固定视觉提示
|
|
hint: nextAch.rewardText,
|
|
});
|
|
}
|
|
|
|
// 3. 下一个记忆碎片
|
|
const nextFrag = FRAGMENTS.find((f) => !s.fragments[f.id]);
|
|
if (nextFrag) {
|
|
const ownedFrag = FRAGMENTS.filter((f) => s.fragments[f.id]).length;
|
|
targets.push({
|
|
id: "frag_" + nextFrag.id,
|
|
icon: BookOpen,
|
|
color: "#e879f9",
|
|
label: nextFrag.title,
|
|
detail: "解码晶体有概率获取",
|
|
progress: (ownedFrag / FRAGMENTS.length) * 100,
|
|
hint: `图谱 ${ownedFrag}/${FRAGMENTS.length}`,
|
|
});
|
|
}
|
|
|
|
// 4. 探险能量
|
|
if (s.energy < EXPEDITION_CONFIG.energyCost) {
|
|
targets.push({
|
|
id: "expedition_energy",
|
|
icon: Rocket,
|
|
color: "#fb7185",
|
|
label: "探险能量恢复中",
|
|
detail: `满能量可深入遗迹`,
|
|
progress: (s.energy / EXPEDITION_CONFIG.energyCost) * 100,
|
|
hint: `${s.energy}/${EXPEDITION_CONFIG.energyCost}`,
|
|
});
|
|
}
|
|
|
|
// 5. 飞升进度(接触<100 时)
|
|
if (s.contact < 100) {
|
|
targets.push({
|
|
id: "ascension",
|
|
icon: Star,
|
|
color: "#a78bfa",
|
|
label: "接触进度",
|
|
detail: "满 100 可飞升新周目",
|
|
progress: s.contact,
|
|
hint: `${s.contact.toFixed(1)}/100`,
|
|
});
|
|
}
|
|
|
|
// 6. 星图天赋(飞升后未选择)
|
|
const ownedConstellation = s.constellation ?? [];
|
|
if (s.pendingPerkChoices && s.pendingPerkChoices.length > 0) {
|
|
targets.push({
|
|
id: "constellation_pending",
|
|
icon: Sparkles,
|
|
color: "#e879f9",
|
|
label: "星图觉醒待选择",
|
|
detail: "3 选 1 永久天赋",
|
|
progress: 100,
|
|
hint: "立即选择",
|
|
});
|
|
} else if (ownedConstellation.length < CONSTELLATION_PERKS.length && s.ascensions > 0) {
|
|
targets.push({
|
|
id: "constellation_next",
|
|
icon: Star,
|
|
color: "#e879f9",
|
|
label: "下一道星图天赋",
|
|
detail: `已觉醒 ${ownedConstellation.length}/${CONSTELLATION_PERKS.length}`,
|
|
progress: (ownedConstellation.length / CONSTELLATION_PERKS.length) * 100,
|
|
hint: "飞升后解锁",
|
|
});
|
|
}
|
|
|
|
// 取前 3 个最相关目标展示
|
|
const topTargets = targets.slice(0, 3);
|
|
|
|
if (topTargets.length === 0) {
|
|
return (
|
|
<div className="rounded-xl border border-emerald-400/20 bg-emerald-950/10 p-3 text-center">
|
|
<CheckCircle2 className="h-5 w-5 text-emerald-400 mx-auto mb-1" />
|
|
<p className="text-[11px] text-emerald-200/80">所有目标已达成</p>
|
|
<p className="text-[9px] text-muted-foreground/60 mt-0.5">继续飞升开启新周目</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-xl border border-white/10 bg-black/30 p-2.5 space-y-2">
|
|
{/* 标题 */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-1.5">
|
|
<Target className="h-3.5 w-3.5 text-emerald-400" />
|
|
<span className="text-[11px] font-semibold text-emerald-200">探索目标</span>
|
|
</div>
|
|
<span className="text-[9px] text-muted-foreground/50 font-mono">RADAR</span>
|
|
</div>
|
|
|
|
{/* 目标列表 */}
|
|
<div className="space-y-1.5">
|
|
{topTargets.map((t) => {
|
|
const Icon = t.icon;
|
|
const isReady = t.progress >= 100;
|
|
return (
|
|
<div
|
|
key={t.id}
|
|
className={`rounded-lg p-1.5 transition-all ${
|
|
isReady
|
|
? "bg-white/8 border border-white/15"
|
|
: "bg-black/20 border border-white/5"
|
|
}`}
|
|
style={
|
|
isReady
|
|
? { boxShadow: `0 0 10px ${t.color}33` }
|
|
: undefined
|
|
}
|
|
>
|
|
<div className="flex items-center gap-1.5 mb-1">
|
|
<Icon
|
|
className={`h-3 w-3 shrink-0 ${isReady ? "animate-pulse" : ""}`}
|
|
style={{ color: t.color }}
|
|
/>
|
|
<span
|
|
className="text-[10px] font-medium truncate flex-1"
|
|
style={{ color: isReady ? t.color : "rgba(255,255,255,0.75)" }}
|
|
>
|
|
{t.label}
|
|
</span>
|
|
<span className="text-[8px] font-mono text-muted-foreground/70 shrink-0">
|
|
{t.hint}
|
|
</span>
|
|
</div>
|
|
{/* 进度条 */}
|
|
<div className="h-0.5 rounded-full bg-black/50 overflow-hidden">
|
|
<div
|
|
className="h-full transition-all duration-500"
|
|
style={{
|
|
width: `${Math.min(100, t.progress)}%`,
|
|
background: t.color,
|
|
boxShadow: isReady ? `0 0 6px ${t.color}` : "none",
|
|
}}
|
|
/>
|
|
</div>
|
|
{/* 详情 */}
|
|
<p className="text-[8px] text-muted-foreground/60 mt-0.5 truncate">{t.detail}</p>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* 底部提示 */}
|
|
<div className="flex items-center gap-1 text-[8px] text-muted-foreground/50 pt-0.5 border-t border-white/5">
|
|
<TrendingDown className="h-2.5 w-2.5" />
|
|
<span>持续探索 · 解锁深空奥秘</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|