1
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-20 09:54:17 +08:00
parent 67c584b4df
commit 50759f3c1e
159 changed files with 16938 additions and 16925 deletions

View File

@@ -28,7 +28,11 @@ import { resolveInventoryItemUseEffect } from '../data/inventoryEffects';
import { isQuestReadyToClaim } from '../data/questFlow';
import { getScenePresetById } from '../data/scenePresets';
import { getOptionImpactSummary } from '../hooks/combatStoryUtils';
import type { BattleRewardUi, QuestFlowUi } from '../hooks/useStoryGeneration';
import type {
BattleRewardUi,
NpcChatQuestOfferUi,
QuestFlowUi,
} from '../hooks/useStoryGeneration';
import type {
ChapterState,
Character,
@@ -70,6 +74,7 @@ interface AdventurePanelProps {
worldType: WorldType | null;
quests: QuestLogEntry[];
questUi: QuestFlowUi;
npcChatQuestOfferUi: NpcChatQuestOfferUi;
goalStack: GoalStackState;
goalPulse: GoalPulseEvent | null;
onDismissGoalPulse: () => void;
@@ -625,6 +630,7 @@ export function AdventurePanel({
worldType,
quests,
questUi,
npcChatQuestOfferUi,
goalStack,
goalPulse,
onDismissGoalPulse,
@@ -646,6 +652,8 @@ export function AdventurePanel({
const dialogueTurns = currentStory.dialogue ?? [];
const npcChatState = currentStory.npcChatState ?? null;
const isNpcChatMode = Boolean(npcChatState);
const pendingNpcQuestOffer = npcChatState?.pendingQuestOffer?.quest ?? null;
const isNpcQuestOfferMode = Boolean(pendingNpcQuestOffer);
const isStoryStreaming = Boolean(currentStory.streaming);
const shouldHideChoiceUi = hideOptions;
const storyScrollContainerRef = useRef<HTMLDivElement | null>(null);
@@ -689,8 +697,10 @@ export function AdventurePanel({
[quests],
);
const selectedQuest = useMemo(
() => quests.find((quest) => quest.id === selectedQuestId) ?? null,
[quests, selectedQuestId],
() =>
quests.find((quest) => quest.id === selectedQuestId) ??
(pendingNpcQuestOffer?.id === selectedQuestId ? pendingNpcQuestOffer : null),
[pendingNpcQuestOffer, quests, selectedQuestId],
);
const rewardQuest = useMemo(
() => quests.find((quest) => quest.id === rewardQuestId) ?? null,
@@ -901,6 +911,27 @@ export function AdventurePanel({
Boolean(selectedRewardItem);
const handleOptionChoice = (option: StoryOption) => {
const pendingQuestAction =
typeof option.runtimePayload?.npcChatQuestOfferAction === 'string'
? option.runtimePayload.npcChatQuestOfferAction
: null;
if (pendingQuestAction && pendingNpcQuestOffer) {
if (pendingQuestAction === 'view') {
setSelectedQuestId(pendingNpcQuestOffer.id);
return;
}
if (pendingQuestAction === 'replace') {
void npcChatQuestOfferUi.replacePendingOffer();
return;
}
if (pendingQuestAction === 'abandon') {
npcChatQuestOfferUi.abandonPendingOffer();
return;
}
}
if (
option.interaction?.kind === 'npc' &&
option.interaction.action === 'quest_accept'
@@ -1179,7 +1210,7 @@ export function AdventurePanel({
</button>
);
})}
{isNpcChatMode ? (
{isNpcChatMode && !isNpcQuestOfferMode ? (
<div className="pixel-nine-slice pixel-panel mt-0.5 border border-white/10 bg-black/25 p-1.5">
<div className="flex min-w-0 items-center gap-2">
<input
@@ -1263,6 +1294,13 @@ export function AdventurePanel({
selectedRewardEquipSlot={selectedRewardEquipSlot}
selectedQuestSceneName={selectedQuestSceneName}
getQuestStatusLabel={getQuestStatusLabel}
pendingNpcQuestOffer={pendingNpcQuestOffer}
onAcceptPendingNpcQuestOffer={() => {
const acceptedQuestId = npcChatQuestOfferUi.acceptPendingOffer();
if (!acceptedQuestId) return null;
setSelectedQuestId(null);
return acceptedQuestId;
}}
/>
</Suspense>
)}