"use client"; // 回响星核 / Echo Nexus — 遗迹探险面板(v0.2 肉鸽系统,v0.9 工单修复) import { useState, useEffect } from "react"; import { useGameStore } from "@/store/gameStore"; import { useToast } from "@/hooks/use-toast"; import { sfx } from "@/hooks/useAudio"; import { EXPEDITION_CONFIG, combatWinRate, computeEnergyRegenInterval } from "@/lib/game/expedition"; import { formatNum } from "@/lib/game/config"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Swords, Gem, HelpCircle, Puzzle, Skull, Heart, Zap, ChevronRight, LogOut, Rocket, Sparkles, } from "lucide-react"; import type { ExpeditionNodeType } from "@/lib/game/types"; const NODE_META: Record< ExpeditionNodeType, { icon: React.ComponentType<{ className?: string }>; color: string; label: string; bg: string } > = { combat: { icon: Swords, color: "#fb7185", label: "战斗", bg: "rgba(251,113,133,0.15)" }, treasure: { icon: Gem, color: "#fbbf24", label: "宝藏", bg: "rgba(251,191,36,0.15)" }, choice: { icon: HelpCircle, color: "#a5f3fc", label: "抉择", bg: "rgba(165,243,252,0.15)" }, puzzle: { icon: Puzzle, color: "#e879f9", label: "解谜", bg: "rgba(232,121,249,0.15)" }, rest: { icon: Heart, color: "#34d399", label: "休整", bg: "rgba(52,211,153,0.15)" }, boss: { icon: Skull, color: "#f43f5e", label: "BOSS", bg: "rgba(244,63,94,0.2)" }, }; export function ExpeditionPanel() { const activeExpedition = useGameStore((s) => s.activeExpedition); const energy = useGameStore((s) => s.energy); const energyMax = useGameStore((s) => s.energyMax); const lastEnergyTick = useGameStore((s) => s.lastEnergyTick); const expeditionLog = useGameStore((s) => s.expeditionLog); const totalExpeditions = useGameStore((s) => s.totalExpeditions); const lastExpeditionSummary = useGameStore((s) => s.lastExpeditionSummary); // v0.8.1 动态能量恢复间隔(技术 + 探索力属性缩短) const regenIntervalSec = useGameStore((s) => computeEnergyRegenInterval(s)); const startExpedition = useGameStore((s) => s.startExpedition); const resolveCurrentNode = useGameStore((s) => s.resolveCurrentNode); const advanceNode = useGameStore((s) => s.advanceNode); const abortExpedition = useGameStore((s) => s.abortExpedition); const { toast } = useToast(); const [lastLog, setLastLog] = useState(null); const [now, setNow] = useState(() => Date.now()); // v0.9 工单修复:能量恢复实时倒计时(每秒刷新) useEffect(() => { const id = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(id); }, []); // 能量恢复进度(v0.8.1 动态间隔,v0.9 实时刷新) const regenMs = regenIntervalSec * 1000; const elapsed = now - lastEnergyTick; const regenProgress = energy >= energyMax ? 100 : Math.min(100, (elapsed / regenMs) * 100); const regenBoosted = regenIntervalSec < EXPEDITION_CONFIG.energyRegenSec; const nextEnergyInSec = energy >= energyMax ? 0 : Math.max(0, Math.ceil((regenMs - elapsed) / 1000)); const canStart = energy >= EXPEDITION_CONFIG.energyCost; const handleStart = () => { const res = startExpedition(); if (!res.ok) { toast({ title: "无法出发", description: res.reason, variant: "destructive" }); } else { sfx("expeditionStart"); toast({ title: "探险队出发", description: "深入遗迹,谨慎前行。" }); setLastLog(null); } }; const handleResolve = () => { const result = resolveCurrentNode(); if (!result) return; setLastLog(result.log); if (result.ended) { if (result.endReason === "victory") sfx("expeditionVictory"); else sfx("expeditionDefeat"); } else { sfx("expeditionNode"); } toast({ title: result.ended ? result.endReason === "victory" ? "✦ 探险胜利!" : "探险失败" : "节点结算", description: result.log, variant: result.endReason === "defeat" ? "destructive" : "default", }); }; const handleAdvance = () => { advanceNode(); setLastLog(null); }; const handleAbort = () => { abortExpedition(); toast({ title: "探险队撤退", description: "保留已获奖励,安全返回。" }); setLastLog(null); }; // ===== 无活跃探险:展示入口 ===== // v0.10 工单 #2 修复:探险结束自动清空 activeExpedition,无需 justFinished 判断 if (!activeExpedition || activeExpedition.finished) { return (

遗迹探险

已探索 {totalExpeditions} 次
{/* v0.9 工单修复:上次探险结果摘要(不限时间,常驻展示) */} {lastExpeditionSummary && (
{lastExpeditionSummary.result === "victory" ? "✦ 上次探险:胜利" : lastExpeditionSummary.result === "defeat" ? "✕ 上次探险:失败" : "→ 上次探险:撤退"} {lastExpeditionSummary.nodeCount} 节点
{lastExpeditionSummary.rewards.crystals > 0 && ( +{formatNum(lastExpeditionSummary.rewards.crystals)} 晶体 )} {lastExpeditionSummary.rewards.insights > 0 && ( +{lastExpeditionSummary.rewards.insights} 洞见 )} {lastExpeditionSummary.rewards.contact > 0 && ( +{lastExpeditionSummary.rewards.contact.toFixed(1)} 接触 )} {lastExpeditionSummary.rewards.crystals === 0 && lastExpeditionSummary.rewards.insights === 0 && ( 无奖励 )}
)} {/* 能量条 */}
探险能量
{energy} / {energyMax}
{energy < energyMax ? ( <>

下一点能量 {nextEnergyInSec}s 后恢复 {regenBoosted && ( ⚡ {regenIntervalSec.toFixed(0)}s/点 (已加速) )}

) : (

能量已满,可立即出发

)}
{/* 出发按钮 */}

深入以太遗迹

程序化生成的 5-7 节点路径,每节点触发战斗/宝藏/抉择/解谜/休整事件,终点 BOSS 必给记忆碎片。

{/* v0.13 探险目的说明(issue #3 反馈"不知道有啥用") */}
晶体/洞见
主线成长资源
接触进度
飞升必需
记忆碎片
BOSS 必给
{/* v0.13 节点类型图例(让玩家知道 6 种节点) */}
{Object.entries(NODE_META).map(([key, meta]) => { const Icon = meta.icon; return ( {meta.label} ); })}
{/* 探险日志 */} {expeditionLog.length > 0 && (
最近探险记录
{expeditionLog.slice(0, 4).map((entry, i) => (
{entry.result} {entry.rewards && {entry.rewards}}
))}
)}
); } // ===== 活跃探险:展示地图 + 事件 ===== const exp = activeExpedition; const currentNode = exp.nodes[exp.currentNode]; const nodeMeta = NODE_META[currentNode.type]; const NodeIcon = nodeMeta.icon; const hpPct = (exp.hp / exp.maxHp) * 100; const pathProgress = ((exp.currentNode + (currentNode.cleared ? 1 : 0)) / exp.nodes.length) * 100; return (
{/* 头部:生命 + 路径进度 */}
遗迹探险 第 {exp.currentNode + 1}/{exp.nodes.length} 节点
探险力 {exp.power}
{/* 生命条 */}
探险队生命
{exp.hp} / {exp.maxHp}
{/* 节点路径图 */}
{exp.nodes.map((node, i) => { const meta = NODE_META[node.type]; const Icon = meta.icon; const isCurrent = i === exp.currentNode; const isCleared = node.cleared; const isPast = i < exp.currentNode; return (
{isCleared && (
)}
{i < exp.nodes.length - 1 && ( )}
); })}
{/* 当前节点事件卡 */}
{nodeMeta.label} {currentNode.type === "combat" && ( 胜率 {Math.round(combatWinRate(exp.power, currentNode.difficulty) * 100)}% )} {currentNode.difficulty > 0 && ( 难度 {currentNode.difficulty} )}

{currentNode.title}

{currentNode.desc}

{/* 上次结算结果 */} {lastLog && (

{lastLog}

)}
{/* 操作按钮 */}
{!currentNode.cleared ? ( ) : exp.currentNode < exp.nodes.length - 1 ? ( ) : (
探险已完成 ✓
)}
{/* 本次奖励累计 */}
本次已获: {exp.rewards.crystals > 0 && {formatNum(exp.rewards.crystals)}晶体} {exp.rewards.insights > 0 && {exp.rewards.insights}洞见} {exp.rewards.contact > 0 && {exp.rewards.contact.toFixed(1)}接触} {(exp.rewards.crystals === 0 && exp.rewards.insights === 0) && ( 暂无 )}
); }