This commit is contained in:
2026-04-26 14:27:48 +08:00
parent f68f4914ec
commit ea33413187
155 changed files with 8130 additions and 1740 deletions

View File

@@ -18,11 +18,7 @@ import { lazy, Suspense, useEffect, useMemo, useRef, useState } from 'react';
import { formatCurrency } from '../../data/economy';
import { getEquipmentSlotFromItem } from '../../data/equipmentEffects';
import {
getFunctionDocumentationById,
isContinueAdventureOption,
NPC_CHAT_FUNCTION,
} from '../../data/functionCatalog';
import { isContinueAdventureOption } from '../../data/functionCatalog';
import { getHostileNpcPresetById } from '../../data/hostileNpcPresets';
import { resolveInventoryItemUseEffect } from '../../data/inventoryEffects';
import { isQuestReadyToClaim } from '../../data/questFlow';
@@ -136,22 +132,6 @@ function AdventurePanelOverlayLoadingFallback() {
);
}
function getCompactOptionDetailText(option: StoryOption) {
if (option.functionId === NPC_CHAT_FUNCTION.id) {
return (
option.detailText ||
getFunctionDocumentationById(option.functionId)?.runtime
?.compactDetailText ||
'聊聊并试探口风'
);
}
return (
getFunctionDocumentationById(option.functionId)?.runtime
?.compactDetailText || option.detailText
);
}
function getOptionActionTextClass(option: StoryOption) {
if ((option.priority ?? 1) >= 3)
return 'text-fuchsia-200 group-hover:text-fuchsia-100';
@@ -160,6 +140,67 @@ function getOptionActionTextClass(option: StoryOption) {
return 'text-zinc-300 group-hover:text-white';
}
function getOptionFunctionTagText(option: StoryOption) {
const tagByFunctionId: Record<string, string> = {
battle_all_in_crush: '战斗',
battle_attack_basic: '战斗',
battle_escape_breakout: '逃跑',
battle_feint_step: '战斗',
battle_finisher_window: '战斗',
battle_guard_break: '战斗',
battle_probe_pressure: '战斗',
battle_recover_breath: '调息',
battle_use_skill: '技能',
camp_travel_home_scene: '场景',
idle_call_out: '试探',
idle_explore_forward: '探索',
idle_follow_clue: '线索',
idle_observe_signs: '观察',
idle_rest_focus: '调息',
idle_travel_next_scene: '场景',
npc_chat: '聊天',
npc_fight: '战斗',
npc_gift: '送礼',
npc_help: '求助',
npc_leave: '离开',
npc_preview_talk: '聊天',
npc_quest_accept: '任务',
npc_quest_turn_in: '任务',
npc_recruit: '招募',
npc_spar: '切磋',
npc_trade: '交易',
story_continue_adventure: '继续',
treasure_inspect: '探查',
treasure_leave: '离开',
treasure_secure: '收取',
};
if (option.functionId.startsWith('npc_chat_quest_offer_')) {
return '任务';
}
return tagByFunctionId[option.functionId] ?? null;
}
function RpgOptionActionLabel({ option }: { option: StoryOption }) {
const tagText = getOptionFunctionTagText(option);
return (
<span className="flex min-w-0 flex-wrap items-center gap-1.5">
{tagText ? (
<span className="shrink-0 rounded border border-white/10 bg-white/10 px-1.5 py-0.5 text-[9px] leading-none text-zinc-300">
{tagText}
</span>
) : null}
<span
className={`min-w-0 break-words text-sm sm:text-[15px] ${getOptionActionTextClass(option)}`}
>
{option.actionText}
</span>
</span>
);
}
function getDialogueTurnAlignmentClass(
turn: NonNullable<StoryMoment['dialogue']>[number],
) {
@@ -798,29 +839,45 @@ function RpgAdventureChoiceSection(props: {
</button>
</div>
{isNpcChatMode ? (
<button
type="button"
onClick={() => onExitNpcChat?.()}
aria-label="退出聊天"
className="inline-flex h-8 shrink-0 items-center gap-1.5 self-start rounded-md border border-rose-300/20 bg-rose-500/10 px-2 text-rose-100 transition-colors hover:bg-rose-500/15"
>
<span className="text-xs leading-none">退</span>
</button>
) : canRefreshOptions && !shouldHideChoiceUi ? (
<button
type="button"
onClick={onRefreshOptions}
aria-label="换一换选项"
className="inline-flex h-8 shrink-0 items-center gap-1.5 self-start rounded-md border border-white/10 bg-black/20 px-2 text-zinc-300 transition-colors hover:text-white"
>
<PixelIcon
src={CHROME_ICONS.refreshOptions}
className="h-4 w-4"
/>
<span className="text-xs leading-none"></span>
</button>
) : null}
<div className="flex shrink-0 items-center gap-2">
{isNpcChatMode && canRefreshOptions && !shouldHideChoiceUi ? (
<button
type="button"
onClick={onRefreshOptions}
aria-label="换一换选项"
className="inline-flex h-8 shrink-0 items-center gap-1.5 self-start rounded-md border border-white/10 bg-black/20 px-2 text-zinc-300 transition-colors hover:text-white"
>
<PixelIcon
src={CHROME_ICONS.refreshOptions}
className="h-4 w-4"
/>
<span className="text-xs leading-none"></span>
</button>
) : !isNpcChatMode && canRefreshOptions && !shouldHideChoiceUi ? (
<button
type="button"
onClick={onRefreshOptions}
aria-label="换一换选项"
className="inline-flex h-8 shrink-0 items-center gap-1.5 self-start rounded-md border border-white/10 bg-black/20 px-2 text-zinc-300 transition-colors hover:text-white"
>
<PixelIcon
src={CHROME_ICONS.refreshOptions}
className="h-4 w-4"
/>
<span className="text-xs leading-none"></span>
</button>
) : null}
{isNpcChatMode ? (
<button
type="button"
onClick={() => onExitNpcChat?.()}
aria-label="退出聊天"
className="inline-flex h-8 shrink-0 items-center gap-1.5 self-start rounded-md border border-rose-300/20 bg-rose-500/10 px-2 text-rose-100 transition-colors hover:bg-rose-500/15"
>
<span className="text-xs leading-none">退</span>
</button>
) : null}
</div>
</div>
<div className="space-y-1.5">
@@ -867,12 +924,8 @@ function RpgAdventureChoiceSection(props: {
className="pixel-nine-slice pixel-pressable pixel-choice-button group w-full text-left"
style={getNineSliceStyle(UI_CHROME.choiceButton)}
>
<div className="flex items-center justify-between">
<span
className={`text-sm sm:text-[15px] ${getOptionActionTextClass(option)}`}
>
{option.actionText}
</span>
<div className="flex items-center justify-between gap-3">
<RpgOptionActionLabel option={option} />
<PixelIcon
src={CHROME_ICONS.optionArrow}
className="h-3 w-3 opacity-70 transition-opacity group-hover:opacity-100"
@@ -894,12 +947,8 @@ function RpgAdventureChoiceSection(props: {
className={`pixel-nine-slice pixel-choice-button group w-full text-left ${optionDisabled ? 'cursor-not-allowed opacity-55' : 'pixel-pressable'}`}
style={getNineSliceStyle(UI_CHROME.choiceButton)}
>
<div className="flex items-center justify-between">
<span
className={`text-sm sm:text-[15px] ${getOptionActionTextClass(option)}`}
>
{option.actionText}
</span>
<div className="flex items-center justify-between gap-3">
<RpgOptionActionLabel option={option} />
<PixelIcon
src={CHROME_ICONS.optionArrow}
className="h-3 w-3 opacity-70 transition-opacity group-hover:opacity-100"