import {
BarChart3,
Coins,
Heart,
LogOut,
type LucideIcon,
MapPinned,
PackageOpen,
ScrollText,
Volume2,
} from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import { formatCurrency } from '../../data/economy';
import { getHostileNpcPresetById } from '../../data/hostileNpcPresets';
import {
type InventoryUseEffect,
isInventoryItemUsable,
} from '../../data/inventoryEffects';
import {
buildInventoryItemDescription,
getInventoryTagLabels,
} from '../../data/itemPresentation';
import { getRarityLabel } from '../../data/npcInteractions';
import { isQuestReadyToClaim } from '../../data/questFlow';
import { getScenePresetById } from '../../data/scenePresets';
import type {
BattleRewardUi,
QuestFlowUi,
} from '../../hooks/rpg-runtime-story';
import { sortQuestsForGoalPanel } from '../../services/storyEngine/goalDirector';
import type {
ChapterState,
EquipmentSlotId,
GoalHandoff,
GoalPulseEvent,
GoalStackState,
InventoryItem,
JourneyBeat,
QuestLogEntry,
WorldType,
} from '../../types';
import {
CHROME_ICONS,
getInventoryItemVisualSrc,
getNineSliceStyle,
UI_CHROME,
} from '../../uiAssets';
import { HostileNpcAnimator } from '../HostileNpcAnimator';
import { PixelIcon } from '../PixelIcon';
type AdventureStatisticCard = {
key: string;
label: string;
value: string;
detail: string;
icon: LucideIcon;
};
type AdventureStatistics = {
playTimeMs: number;
hostileNpcsDefeated: number;
questsAccepted: number;
questsCompleted: number;
questsTurnedIn: number;
itemsUsed: number;
scenesTraveled: number;
currentSceneName: string;
playerCurrency: number;
inventoryItemCount: number;
inventoryStackCount: number;
activeCompanionCount: number;
rosterCompanionCount: number;
};
interface RpgAdventurePanelOverlaysProps {
worldType: WorldType | null;
quests: QuestLogEntry[];
questUi: QuestFlowUi;
battleRewardUi: BattleRewardUi;
statistics: AdventureStatistics;
statisticsCards: AdventureStatisticCard[];
musicVolume: number;
onMusicVolumeChange: (value: number) => void;
onSaveAndExit: () => void;
saveAndExitDisabled: boolean;
isGoalPanelOpen: boolean;
setIsGoalPanelOpen: (open: boolean) => void;
isQuestPanelOpen: boolean;
setIsQuestPanelOpen: (open: boolean) => void;
isSettingsPanelOpen: boolean;
setIsSettingsPanelOpen: (open: boolean) => void;
isStatsPanelOpen: boolean;
setIsStatsPanelOpen: (open: boolean) => void;
chapterState: ChapterState | null;
journeyBeat: JourneyBeat | null;
goalStack: GoalStackState;
goalPulse: GoalPulseEvent | null;
onDismissGoalPulse: () => void;
selectedQuest: QuestLogEntry | null;
setSelectedQuestId: (questId: string | null) => void;
completionNoticeQuest: QuestLogEntry | null;
setCompletionNoticeQuestId: (questId: string | null) => void;
rewardQuest: QuestLogEntry | null;
setRewardQuestId: (questId: string | null) => void;
rewardQuestHandoff: GoalHandoff | null;
setRewardQuestHandoff: (handoff: GoalHandoff | null) => void;
selectedRewardItemQuestId: string | null;
setSelectedRewardItemQuestId: (questId: string | null) => void;
selectedRewardItemId: string | null;
setSelectedRewardItemId: (itemId: string | null) => void;
selectedBattleRewardItemId: string | null;
setSelectedBattleRewardItemId: (itemId: string | null) => void;
selectedRewardItem: InventoryItem | null;
selectedRewardUseEffect: InventoryUseEffect | null;
selectedRewardEquipSlot: EquipmentSlotId | null;
selectedQuestSceneName: string;
getQuestStatusLabel: (status: QuestLogEntry['status']) => string;
pendingNpcQuestOffer: QuestLogEntry | null;
onAcceptPendingNpcQuestOffer: () => string | null;
}
function compactSceneTaskLabel(
sceneName: string | null | undefined,
fallback: string,
) {
if (!sceneName?.trim()) {
return fallback;
}
const cleaned = sceneName.replace(/[《》「」“”"']/gu, '').trim();
return cleaned.length > 8 ? cleaned.slice(0, 8) : cleaned;
}
function formatTaskTitle(title: string, fallback = '当前任务') {
const cleaned = title
.replace(/[《》「」“”"']/gu, '')
.replace(/[·||::].*$/u, '')
.replace(/[,。!?;,.!?;].*$/u, '')
.trim();
if (!cleaned) {
return fallback;
}
return cleaned.length > 12 ? cleaned.slice(0, 10) : cleaned;
}
function buildJourneyTaskCardCopy(params: {
beatType: JourneyBeat['beatType'] | null | undefined;
sceneName: string;
fallbackCondition: string;
}) {
const { beatType, sceneName, fallbackCondition } = params;
const sceneLabel = compactSceneTaskLabel(sceneName, '前方区域');
switch (beatType) {
case 'approach':
return {
title: `前往${sceneLabel}`,
description: `${sceneLabel} 一带出现了值得跟进的新线索,继续靠近,看看那里到底发生了什么。`,
condition: `前往 ${sceneName},确认新的线索。`,
progress: '靠近线索',
};
case 'investigation':
return {
title: `调查${sceneLabel}`,
description: `${sceneLabel} 出现了新的异常和痕迹,继续调查,查清这里到底隐藏着什么。`,
condition: `在 ${sceneName} 调查线索或异常。`,
progress: '调查进行中',
};
case 'camp':
return {
title: '回营整备',
description: '先整理队伍、资源和状态,再决定下一段任务的推进方式。',
condition: '返回营地,整理队伍或与同伴交谈。',
progress: '整备中',
};
case 'conflict':
return {
title: `处理${sceneLabel}`,
description: `${sceneLabel} 的冲突已经浮出水面,需要继续推进并正面处理。`,
condition: `在 ${sceneName} 处理当前冲突。`,
progress: '冲突处理中',
};
case 'boss_prelude':
return {
title: `备战${sceneLabel}`,
description: '关键战斗已经逼近,先把线索和状态准备好。',
condition: fallbackCondition,
progress: '战前准备',
};
case 'climax':
return {
title: `决断${sceneLabel}`,
description: '决定结果的对峙已经临近,继续推进到最终现场。',
condition: fallbackCondition,
progress: '决战临近',
};
case 'recovery':
return {
title: '收束结果',
description: '刚结束的事件还在留下影响,先整理结果,再决定下一步去向。',
condition: fallbackCondition,
progress: '结果收束中',
};
default:
return {
title: '继续推进',
description: `${sceneLabel} 一带还有没查清的事,继续推进当前线索。`,
condition: fallbackCondition,
progress: '推进中',
};
}
}
function buildCurrentTaskCardCopy(params: {
goalStack: GoalStackState;
goalPulse: GoalPulseEvent | null;
journeyBeat: JourneyBeat | null;
sceneName: string;
}) {
const { goalStack, goalPulse, journeyBeat, sceneName } = params;
const primaryGoal = goalStack.activeGoal ?? goalStack.northStarGoal;
const stepGoal = goalStack.immediateStepGoal ?? primaryGoal;
if (!primaryGoal || !stepGoal) {
return null;
}
if (primaryGoal.sourceKind === 'quest') {
const description = primaryGoal.whyNow || primaryGoal.promiseText;
return {
eyebrow: goalPulse?.title ?? '当前任务',
title: formatTaskTitle(primaryGoal.title, '当前任务'),
description,
condition: stepGoal.nextStepText,
progress: stepGoal.progressLabel ?? primaryGoal.progressLabel ?? '推进中',
pulseNote:
goalPulse?.detail &&
goalPulse.detail !== description &&
goalPulse.detail !== stepGoal.nextStepText
? goalPulse.detail
: null,
};
}
const journeyCopy = buildJourneyTaskCardCopy({
beatType: journeyBeat?.beatType,
sceneName,
fallbackCondition: stepGoal.nextStepText,
});
return {
eyebrow: goalPulse?.title ?? '当前任务',
title: journeyCopy.title,
description: journeyCopy.description,
condition: journeyCopy.condition,
progress: journeyCopy.progress,
pulseNote:
goalPulse?.detail &&
goalPulse.detail !== journeyCopy.description &&
goalPulse.detail !== journeyCopy.condition
? goalPulse.detail
: null,
};
}
function getQuestSceneName(quest: QuestLogEntry, worldType: WorldType | null) {
if (!quest.sceneId) {
return '当前区域';
}
if (!worldType) {
return quest.sceneId;
}
return getScenePresetById(worldType, quest.sceneId)?.name ?? quest.sceneId;
}
function getQuestHostileNpcName(
quest: QuestLogEntry,
worldType: WorldType | null,
) {
if (!quest.objective.targetHostileNpcId) {
return null;
}
return worldType
? (getHostileNpcPresetById(worldType, quest.objective.targetHostileNpcId)
?.name ?? quest.objective.targetHostileNpcId)
: quest.objective.targetHostileNpcId;
}
function buildQuestConditionText(
quest: QuestLogEntry,
worldType: WorldType | null,
) {
if (isQuestReadyToClaim(quest)) {
return `返回找 ${quest.issuerNpcName} 交付任务并领取奖励。`;
}
if (quest.status === 'turned_in') {
return '任务已经交付。';
}
const activeStep =
quest.steps?.find((step) => step.id === quest.activeStepId) ??
quest.steps?.find((step) => step.progress < step.requiredCount) ??
null;
const objective = activeStep ?? quest.objective;
const sceneName = getQuestSceneName(quest, worldType);
switch (objective.kind) {
case 'defeat_hostile_npc':
return `击败 ${getQuestHostileNpcName(quest, worldType) ?? '指定敌人'}。`;
case 'inspect_treasure':
return `在 ${sceneName} 调查宝藏或异常线索。`;
case 'spar_with_npc':
return `与 ${quest.issuerNpcName} 完成一场切磋。`;
case 'talk_to_npc':
return `返回找 ${quest.issuerNpcName} 对话。`;
case 'reach_scene':
return `前往 ${objective.targetSceneId ?? sceneName}。`;
case 'deliver_item':
return `把指定物品交给 ${quest.issuerNpcName}。`;
default:
return activeStep?.revealText ?? quest.summary;
}
}
function getQuestProgressText(quest: QuestLogEntry) {
if (isQuestReadyToClaim(quest)) {
return '待交付';
}
if (quest.status === 'turned_in') {
return '已交付';
}
const activeStep =
quest.steps?.find((step) => step.id === quest.activeStepId) ??
quest.steps?.find((step) => step.progress < step.requiredCount) ??
null;
const progressSource = activeStep ?? quest;
const requiredCount =
'requiredCount' in progressSource
? progressSource.requiredCount
: quest.objective.requiredCount;
return `${progressSource.progress}/${requiredCount}`;
}
function TaskTemplateCard({
eyebrow,
title,
description,
condition,
progress,
reward,
onRewardItemSelect,
tone = 'default',
}: {
eyebrow: string;
title: string;
description: string;
condition: string;
progress?: string | null;
reward?: QuestLogEntry['reward'] | null;
onRewardItemSelect?: ((itemId: string) => void) | null;
tone?: 'default' | 'main';
}) {
if (!title.trim() && !description.trim() && !condition.trim()) {
return null;
}
return (
{eyebrow}
{formatTaskTitle(title)}
任务描述:
{description}
达成条件:
{condition}
{progress ? (
任务进度:{progress}
) : null}
{reward ? (
) : null}
);
}
function QuestRewardIconStrip({
reward,
onSelectItem,
}: {
reward: QuestLogEntry['reward'];
onSelectItem?: (itemId: string) => void;
}) {
const hasItems = reward.items.length > 0;
return (
任务奖励
好感 +{reward.affinityBonus}
{(reward.experience ?? 0) > 0 ? ` · 经验 +${reward.experience}` : ''}
{` · ${reward.currency}`}
{hasItems ? (
{reward.items.map((item) => (
))}
) : (
无物品奖励
)}
);
}
function GoalFocusCard({
goalStack,
goalPulse,
journeyBeat,
sceneName,
}: {
goalStack: GoalStackState;
goalPulse: GoalPulseEvent | null;
journeyBeat: JourneyBeat | null;
sceneName: string;
}) {
const cardCopy = buildCurrentTaskCardCopy({
goalStack,
goalPulse,
journeyBeat,
sceneName,
});
const primaryGoal = goalStack.activeGoal ?? goalStack.northStarGoal;
if (!cardCopy || primaryGoal?.sourceKind !== 'quest') {
return null;
}
return (
{cardCopy.pulseNote ? (
{cardCopy.pulseNote}
) : null}
{(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint ||
(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint ? (
{(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint
? `地点:${(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint}`
: null}
{(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.sceneHint &&
(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint
? ' · '
: null}
{(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint
? `相关人物:${(goalStack.immediateStepGoal ?? goalStack.activeGoal)?.npcHint}`
: null}
) : null}
);
}
function getQuestRewardItemIcon(item: InventoryItem) {
return getInventoryItemVisualSrc(item);
}
function getRewardItemFrameClass(rarity: InventoryItem['rarity']) {
switch (rarity) {
case 'legendary':
return 'border-amber-300/45 bg-gradient-to-br from-amber-500/18 via-orange-500/10 to-transparent shadow-[0_0_28px_rgba(245,158,11,0.12)]';
case 'epic':
return 'border-fuchsia-300/40 bg-gradient-to-br from-fuchsia-500/16 via-indigo-500/10 to-transparent shadow-[0_0_26px_rgba(217,70,239,0.1)]';
case 'rare':
return 'border-sky-300/40 bg-gradient-to-br from-sky-500/16 via-cyan-500/10 to-transparent shadow-[0_0_24px_rgba(56,189,248,0.08)]';
case 'uncommon':
return 'border-emerald-300/35 bg-gradient-to-br from-emerald-500/14 via-lime-500/8 to-transparent';
default:
return 'border-white/10 bg-white/[0.04]';
}
}
function buildRewardItemDescription(item: InventoryItem) {
return buildInventoryItemDescription(item);
}
function getQuestObjectivePresentation(
quest: QuestLogEntry,
worldType: WorldType | null,
sceneName: string,
) {
const hostileNpcPreset =
worldType && quest.objective.targetHostileNpcId
? getHostileNpcPresetById(worldType, quest.objective.targetHostileNpcId)
: null;
switch (quest.objective.kind) {
case 'defeat_hostile_npc':
return {
eyebrow: '悬赏目标',
accentClass: 'from-rose-500/20 via-orange-500/10 to-transparent',
labelClass: 'border-rose-300/25 bg-rose-500/12 text-rose-50',
primaryLabel:
hostileNpcPreset?.name ??
quest.objective.targetHostileNpcId ??
'未知敌对角色',
secondaryLabel: sceneName,
hostileNpcPreset,
iconSrc: null as string | null,
};
case 'inspect_treasure':
return {
eyebrow: '宝藏踪迹',
accentClass: 'from-amber-500/20 via-yellow-500/10 to-transparent',
labelClass: 'border-amber-300/25 bg-amber-500/12 text-amber-50',
primaryLabel: sceneName,
secondaryLabel: '探索隐藏的奖励地点',
hostileNpcPreset: null,
iconSrc: '/Icons/47_treasure.png',
};
case 'spar_with_npc':
default:
return {
eyebrow: '切磋会话',
accentClass: 'from-sky-500/20 via-cyan-500/10 to-transparent',
labelClass: 'border-sky-300/25 bg-sky-500/12 text-sky-50',
primaryLabel: quest.issuerNpcName,
secondaryLabel: sceneName,
hostileNpcPreset: null,
iconSrc: '/UI/1_weapon.png',
};
}
}
function RewardItemIconGrid({
items,
selectedItemId,
onSelectItem,
emptyText,
}: {
items: InventoryItem[];
selectedItemId: string | null;
onSelectItem: (itemId: string) => void;
emptyText: string;
}) {
if (items.length === 0) {
return (
{emptyText}
);
}
return (
{items.map((item) => (
))}
);
}
function QuestProgressPips({
progress,
total,
activeClassName,
}: {
progress: number;
total: number;
activeClassName: string;
}) {
const safeTotal = Math.max(1, total);
return (
{Array.from({ length: safeTotal }, (_, index) => (
))}
);
}
function QuestRewardGrid({
quest,
worldType,
selectedItemId,
onSelectItem,
}: {
quest: QuestLogEntry;
worldType: WorldType | null;
selectedItemId: string | null;
onSelectItem: (itemId: string) => void;
}) {
return (
+{quest.reward.affinityBonus}
好感度
{formatCurrency(quest.reward.currency, worldType)}
货币
+{quest.reward.experience ?? 0}
经验
);
}
function QuestObjectiveCard({
quest,
worldType,
sceneName,
statusLabel,
}: {
quest: QuestLogEntry;
worldType: WorldType | null;
sceneName: string;
statusLabel: string;
}) {
const presentation = getQuestObjectivePresentation(
quest,
worldType,
sceneName,
);
return (
{presentation.eyebrow}
{presentation.primaryLabel}
{statusLabel}
{presentation.hostileNpcPreset ? (
) : (
{presentation.iconSrc && (
)}
)}
任务目标
{presentation.primaryLabel}
区域
{presentation.secondaryLabel}
进度
{quest.progress}/{quest.objective.requiredCount}
);
}
export function RpgAdventurePanelOverlays({
worldType,
quests,
questUi,
battleRewardUi,
statistics,
statisticsCards,
musicVolume,
onMusicVolumeChange,
onSaveAndExit,
saveAndExitDisabled,
isGoalPanelOpen,
setIsGoalPanelOpen,
isQuestPanelOpen,
setIsQuestPanelOpen,
isSettingsPanelOpen,
setIsSettingsPanelOpen,
isStatsPanelOpen,
setIsStatsPanelOpen,
chapterState,
journeyBeat,
goalStack,
goalPulse,
onDismissGoalPulse,
selectedQuest,
setSelectedQuestId,
completionNoticeQuest,
setCompletionNoticeQuestId,
rewardQuest,
setRewardQuestId,
rewardQuestHandoff,
setRewardQuestHandoff,
selectedRewardItemQuestId,
setSelectedRewardItemQuestId,
selectedRewardItemId,
setSelectedRewardItemId,
selectedBattleRewardItemId,
setSelectedBattleRewardItemId,
selectedRewardItem,
selectedRewardUseEffect,
selectedRewardEquipSlot,
selectedQuestSceneName,
getQuestStatusLabel,
pendingNpcQuestOffer,
onAcceptPendingNpcQuestOffer,
}: RpgAdventurePanelOverlaysProps) {
const battleReward = battleRewardUi.reward;
const sortedQuests = sortQuestsForGoalPanel(quests, goalStack);
const activeGoalQuest =
goalStack.activeGoal?.sourceKind === 'quest'
? (quests.find((quest) => quest.id === goalStack.activeGoal?.sourceId) ??
null)
: null;
const shouldShowQuestUpdateModal = Boolean(
activeGoalQuest && isGoalPanelOpen,
);
const selectQuestRewardItem = (quest: QuestLogEntry, itemId: string) => {
setSelectedBattleRewardItemId(null);
setSelectedRewardItemQuestId(quest.id);
setSelectedRewardItemId(itemId);
};
const isPendingSelectedQuest = Boolean(
selectedQuest && pendingNpcQuestOffer?.id === selectedQuest.id,
);
const closeGoalPanel = () => {
setIsGoalPanelOpen(false);
onDismissGoalPulse();
};
return (
<>
{shouldShowQuestUpdateModal && (
event.stopPropagation()}
>
{goalPulse ? '任务更新' : '当前任务'}
只展示这一步推进最需要知道的信息
{goalStack.activeGoal?.sourceKind === 'quest' ||
goalStack.immediateStepGoal?.sourceKind === 'quest' ? (
) : null}
)}
{isSettingsPanelOpen && (
setIsSettingsPanelOpen(false)}
>
event.stopPropagation()}
>
冒险设置
调整音乐音量,查看统计数据,或保存并退出。
{saveAndExitDisabled && (
故事内容仍在加载或流式传输时,保存功能暂时禁用。
)}
)}
{isStatsPanelOpen && (
setIsStatsPanelOpen(false)}
>
event.stopPropagation()}
>
冒险统计
当前区域: {statistics.currentSceneName}
冒险总览
已击败 {statistics.hostileNpcsDefeated} 个敌人,背包中有{' '}
{statistics.inventoryItemCount} 件物品,已探索{' '}
{statistics.scenesTraveled} 个场景。
{statisticsCards.map((card) => {
const Icon = card.icon;
return (
{card.label}
{card.value}
{card.detail}
);
})}
)}
{isQuestPanelOpen && (
{
setIsQuestPanelOpen(false);
setSelectedQuestId(null);
}}
>
event.stopPropagation()}
>
任务日志
总任务数: {quests.length}
{quests.length > 0 ? (
{sortedQuests.map((quest) => (
selectQuestRewardItem(quest, itemId)
}
/>
))}
) : (
暂无活跃任务。
)}
)}
{selectedQuest && (
setSelectedQuestId(null)}
>
event.stopPropagation()}
>
{selectedQuest.title}
{selectedQuest.issuerNpcName}
selectQuestRewardItem(selectedQuest, itemId)
}
tone="main"
/>
{
setSelectedBattleRewardItemId(null);
setSelectedRewardItemQuestId(selectedQuest.id);
setSelectedRewardItemId(itemId);
}}
/>
{isPendingSelectedQuest && (
)}
{isQuestReadyToClaim(selectedQuest) &&
!isPendingSelectedQuest && (
)}
)}
{completionNoticeQuest && (
{
questUi.acknowledgeQuestCompletion(completionNoticeQuest.id);
setCompletionNoticeQuestId(null);
}}
>
event.stopPropagation()}
>
任务完成
奖励已准备
{completionNoticeQuest.title}
{goalStack.immediateStepGoal?.sourceKind === 'quest'
? goalStack.immediateStepGoal.nextStepText
: '可前往任务日志领取奖励。'}
)}
{rewardQuest && (
{
setRewardQuestId(null);
setRewardQuestHandoff(null);
setSelectedRewardItemId(null);
setSelectedRewardItemQuestId(null);
}}
>
event.stopPropagation()}
>
任务奖励已领取
{rewardQuest.title}
{rewardQuestHandoff ? (
下一步建议
{rewardQuestHandoff.title}
{rewardQuestHandoff.detail}
) : null}
{
setSelectedBattleRewardItemId(null);
setSelectedRewardItemQuestId(rewardQuest.id);
setSelectedRewardItemId(itemId);
}}
/>
)}
{battleReward && (
{
battleRewardUi.dismiss();
setSelectedBattleRewardItemId(null);
}}
>
event.stopPropagation()}
>
战斗奖励
已击败敌人: {battleReward.defeatedHostileNpcs.length}
战斗结束
战利品已添加至背包。可查看下方掉落物品详情。
{battleReward.defeatedHostileNpcs.map((hostileNpc) => (
{hostileNpc.name}
))}
战利品
{battleReward.items.length > 0
? '点击物品图标查看详情。'
: '本次战斗无可用战利品。'}
{
setSelectedRewardItemQuestId(null);
setSelectedRewardItemId(null);
setSelectedBattleRewardItemId(itemId);
}}
emptyText="本次战斗无战利品掉落。"
/>
)}
{selectedRewardItem && (
{
setSelectedRewardItemId(null);
setSelectedRewardItemQuestId(null);
setSelectedBattleRewardItemId(null);
}}
>
event.stopPropagation()}
>
{selectedRewardItem.name}
{selectedRewardItem.category}
稀有度: {getRarityLabel(selectedRewardItem.rarity)}
数量: {selectedRewardItem.quantity}
{selectedRewardEquipSlot
? `装备槽: ${selectedRewardEquipSlot}`
: '不可装备'}
{isInventoryItemUsable(selectedRewardItem)
? '可直接使用'
: '被动/非即时生效物品'}
{buildRewardItemDescription(selectedRewardItem)}
{selectedRewardUseEffect && (
效果预览:生命 +{selectedRewardUseEffect.hpRestore} / 灵力 +
{selectedRewardUseEffect.manaRestore} / 冷却 -
{selectedRewardUseEffect.cooldownReduction}
)}
标签:{' '}
{getInventoryTagLabels(selectedRewardItem.tags).join(' / ') ||
'无'}
)}
>
);
}