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

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>
);
}