@@ -62,6 +62,8 @@ interface AdventurePanelProps {
|
||||
canRefreshOptions: boolean;
|
||||
onRefreshOptions: () => void;
|
||||
onChoice: (option: StoryOption) => void;
|
||||
onSubmitNpcChatInput?: (input: string) => boolean;
|
||||
onExitNpcChat?: () => boolean;
|
||||
onOpenCharacter: () => void;
|
||||
onOpenInventory: () => void;
|
||||
playerCharacter: Character;
|
||||
@@ -149,12 +151,22 @@ function getOptionActionTextClass(option: StoryOption) {
|
||||
function getDialogueTurnAlignmentClass(
|
||||
turn: NonNullable<StoryMoment['dialogue']>[number],
|
||||
) {
|
||||
if (turn.speaker === 'system') {
|
||||
return 'justify-center';
|
||||
}
|
||||
|
||||
return turn.speaker === 'player' ? 'justify-end' : 'justify-start';
|
||||
}
|
||||
|
||||
function getDialogueTurnBubbleClass(
|
||||
turn: NonNullable<StoryMoment['dialogue']>[number],
|
||||
) {
|
||||
if (turn.speaker === 'system') {
|
||||
return turn.affinityDelta && turn.affinityDelta > 0
|
||||
? 'border-rose-400/30 bg-rose-500/12 text-rose-50'
|
||||
: 'border-white/12 bg-white/[0.06] text-zinc-100';
|
||||
}
|
||||
|
||||
if (turn.speaker === 'player') {
|
||||
return 'border-sky-400/20 bg-sky-500/10 text-sky-50';
|
||||
}
|
||||
@@ -169,6 +181,10 @@ function getDialogueTurnBubbleClass(
|
||||
function getDialogueTurnBubbleShapeClass(
|
||||
turn: NonNullable<StoryMoment['dialogue']>[number],
|
||||
) {
|
||||
if (turn.speaker === 'system') {
|
||||
return 'rounded-full';
|
||||
}
|
||||
|
||||
if (turn.speaker === 'player') {
|
||||
return 'rounded-2xl rounded-br-none';
|
||||
}
|
||||
@@ -183,6 +199,10 @@ function getDialogueTurnBubbleShapeClass(
|
||||
function getDialogueTurnLabel(
|
||||
turn: NonNullable<StoryMoment['dialogue']>[number],
|
||||
) {
|
||||
if (turn.speaker === 'system') {
|
||||
return turn.affinityDelta && turn.affinityDelta > 0 ? '关系变化' : '系统';
|
||||
}
|
||||
|
||||
if (turn.speaker === 'player') {
|
||||
return '\u4f60';
|
||||
}
|
||||
@@ -597,6 +617,8 @@ export function AdventurePanel({
|
||||
canRefreshOptions,
|
||||
onRefreshOptions,
|
||||
onChoice,
|
||||
onSubmitNpcChatInput,
|
||||
onExitNpcChat,
|
||||
onOpenCharacter,
|
||||
onOpenInventory,
|
||||
playerCharacter,
|
||||
@@ -622,6 +644,8 @@ export function AdventurePanel({
|
||||
}: AdventurePanelProps) {
|
||||
const isDialogueStory = currentStory.displayMode === 'dialogue';
|
||||
const dialogueTurns = currentStory.dialogue ?? [];
|
||||
const npcChatState = currentStory.npcChatState ?? null;
|
||||
const isNpcChatMode = Boolean(npcChatState);
|
||||
const isStoryStreaming = Boolean(currentStory.streaming);
|
||||
const shouldHideChoiceUi = hideOptions;
|
||||
const storyScrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -656,6 +680,7 @@ export function AdventurePanel({
|
||||
const [selectedBattleRewardItemId, setSelectedBattleRewardItemId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [npcChatDraft, setNpcChatDraft] = useState('');
|
||||
const lastAutoOpenedGoalRef = useRef<string | null>(null);
|
||||
const lastAutoOpenedPulseRef = useRef<string | null>(null);
|
||||
const battleReward = battleRewardUi.reward;
|
||||
@@ -734,6 +759,10 @@ export function AdventurePanel({
|
||||
setSelectedBattleRewardItemId(null);
|
||||
}, [battleReward]);
|
||||
|
||||
useEffect(() => {
|
||||
setNpcChatDraft('');
|
||||
}, [npcChatState?.npcId, npcChatState?.turnCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!primaryQuestGoal) {
|
||||
return;
|
||||
@@ -887,6 +916,18 @@ export function AdventurePanel({
|
||||
onDismissGoalPulse();
|
||||
};
|
||||
|
||||
const submitNpcChatDraft = () => {
|
||||
const nextInput = npcChatDraft.trim();
|
||||
if (!nextInput || !onSubmitNpcChatInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const submitted = onSubmitNpcChatInput(nextInput);
|
||||
if (submitted) {
|
||||
setNpcChatDraft('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-0 flex-1 flex-col">
|
||||
<button
|
||||
|
||||
@@ -45,6 +45,8 @@ interface GameShellStoryProps {
|
||||
canRefreshOptions: boolean;
|
||||
handleRefreshOptions: () => void;
|
||||
handleChoice: (option: StoryOption) => void;
|
||||
handleNpcChatInput: (input: string) => boolean;
|
||||
exitNpcChat: () => boolean;
|
||||
handleMapTravelToScene: (sceneId: string) => boolean;
|
||||
npcUi: StoryGenerationNpcUi;
|
||||
characterChatUi: CharacterChatUi;
|
||||
@@ -200,6 +202,8 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
|
||||
canRefreshOptions,
|
||||
handleRefreshOptions,
|
||||
handleChoice,
|
||||
handleNpcChatInput,
|
||||
exitNpcChat,
|
||||
handleMapTravelToScene,
|
||||
npcUi,
|
||||
characterChatUi,
|
||||
@@ -533,6 +537,8 @@ export function GameShell({session, story, entry, companions, audio}: GameShellP
|
||||
canRefreshOptions={canRefreshOptions}
|
||||
onRefreshOptions={handleRefreshOptions}
|
||||
onChoice={handleSceneTransitionChoice}
|
||||
onSubmitNpcChatInput={handleNpcChatInput}
|
||||
onExitNpcChat={exitNpcChat}
|
||||
onOpenCharacter={() => openOverlayPanel('character')}
|
||||
onOpenInventory={() => openOverlayPanel('inventory')}
|
||||
playerCharacter={visibleGameState.playerCharacter}
|
||||
|
||||
@@ -74,6 +74,8 @@ export function GameShellMainContent({
|
||||
canRefreshOptions,
|
||||
handleRefreshOptions,
|
||||
handleSceneTransitionChoice,
|
||||
handleNpcChatInput,
|
||||
exitNpcChat,
|
||||
characterChatUi,
|
||||
inventoryUi,
|
||||
battleRewardUi,
|
||||
@@ -112,6 +114,8 @@ export function GameShellMainContent({
|
||||
canRefreshOptions: boolean;
|
||||
handleRefreshOptions: () => void;
|
||||
handleSceneTransitionChoice: (option: StoryOption) => void;
|
||||
handleNpcChatInput: (input: string) => boolean;
|
||||
exitNpcChat: () => boolean;
|
||||
characterChatUi: CharacterChatUi;
|
||||
inventoryUi: InventoryFlowUi;
|
||||
battleRewardUi: BattleRewardUi;
|
||||
@@ -198,6 +202,8 @@ export function GameShellMainContent({
|
||||
canRefreshOptions={canRefreshOptions}
|
||||
handleRefreshOptions={handleRefreshOptions}
|
||||
handleSceneTransitionChoice={handleSceneTransitionChoice}
|
||||
handleNpcChatInput={handleNpcChatInput}
|
||||
exitNpcChat={exitNpcChat}
|
||||
characterChatUi={characterChatUi}
|
||||
inventoryUi={inventoryUi}
|
||||
battleRewardUi={battleRewardUi}
|
||||
|
||||
@@ -34,6 +34,8 @@ export function GameShellRuntime({session, story, entry, companions, audio}: Gam
|
||||
displayedOptions,
|
||||
canRefreshOptions,
|
||||
handleRefreshOptions,
|
||||
handleNpcChatInput,
|
||||
exitNpcChat,
|
||||
handleMapTravelToScene,
|
||||
npcUi,
|
||||
characterChatUi,
|
||||
@@ -151,6 +153,8 @@ export function GameShellRuntime({session, story, entry, companions, audio}: Gam
|
||||
canRefreshOptions={canRefreshOptions}
|
||||
handleRefreshOptions={handleRefreshOptions}
|
||||
handleSceneTransitionChoice={handleSceneTransitionChoice}
|
||||
handleNpcChatInput={handleNpcChatInput}
|
||||
exitNpcChat={exitNpcChat}
|
||||
characterChatUi={characterChatUi}
|
||||
inventoryUi={inventoryUi}
|
||||
battleRewardUi={battleRewardUi}
|
||||
|
||||
@@ -51,6 +51,8 @@ export function GameShellStoryPanels({
|
||||
canRefreshOptions,
|
||||
handleRefreshOptions,
|
||||
handleSceneTransitionChoice,
|
||||
handleNpcChatInput,
|
||||
exitNpcChat,
|
||||
characterChatUi,
|
||||
inventoryUi,
|
||||
battleRewardUi,
|
||||
@@ -77,6 +79,8 @@ export function GameShellStoryPanels({
|
||||
canRefreshOptions: boolean;
|
||||
handleRefreshOptions: () => void;
|
||||
handleSceneTransitionChoice: (option: StoryOption) => void;
|
||||
handleNpcChatInput: (input: string) => boolean;
|
||||
exitNpcChat: () => boolean;
|
||||
characterChatUi: CharacterChatUi;
|
||||
inventoryUi: InventoryFlowUi;
|
||||
battleRewardUi: BattleRewardUi;
|
||||
@@ -181,6 +185,8 @@ export function GameShellStoryPanels({
|
||||
canRefreshOptions={canRefreshOptions}
|
||||
onRefreshOptions={handleRefreshOptions}
|
||||
onChoice={handleSceneTransitionChoice}
|
||||
onSubmitNpcChatInput={handleNpcChatInput}
|
||||
onExitNpcChat={exitNpcChat}
|
||||
onOpenCharacter={() => openOverlayPanel('character')}
|
||||
onOpenInventory={() => openOverlayPanel('inventory')}
|
||||
playerCharacter={playerCharacter}
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface GameShellStoryProps {
|
||||
canRefreshOptions: boolean;
|
||||
handleRefreshOptions: () => void;
|
||||
handleChoice: (option: StoryOption) => void;
|
||||
handleNpcChatInput: (input: string) => boolean;
|
||||
exitNpcChat: () => boolean;
|
||||
handleMapTravelToScene: (sceneId: string) => boolean;
|
||||
npcUi: StoryGenerationNpcUi;
|
||||
characterChatUi: CharacterChatUi;
|
||||
|
||||
Reference in New Issue
Block a user