256 lines
9.8 KiB
TypeScript
256 lines
9.8 KiB
TypeScript
import { lazy, Suspense } from 'react';
|
|
|
|
import type { BottomTab } from '../../hooks/useGameFlow';
|
|
import type {
|
|
BattleRewardUi,
|
|
CharacterChatUi,
|
|
GoalFlowUi,
|
|
InventoryFlowUi,
|
|
NpcChatQuestOfferUi,
|
|
QuestFlowUi,
|
|
} from '../../hooks/useStoryGeneration';
|
|
import type { CompanionRenderState, GameState, StoryMoment, StoryOption } from '../../types';
|
|
import { getNineSliceStyle,TAB_ICONS, UI_CHROME } from '../../uiAssets';
|
|
import type { GameCanvasEntitySelection } from '../GameCanvas';
|
|
import { PixelIcon } from '../PixelIcon';
|
|
import { PanelLoadingFallback } from './GameShellLoaders';
|
|
import type { GameShellAdventureStatistics } from './types';
|
|
|
|
const AdventurePanel = lazy(async () => {
|
|
const module = await import('../AdventurePanel');
|
|
|
|
return {
|
|
default: module.AdventurePanel,
|
|
};
|
|
});
|
|
|
|
const CharacterPanel = lazy(async () => {
|
|
const module = await import('../CharacterPanel');
|
|
|
|
return {
|
|
default: module.CharacterPanel,
|
|
};
|
|
});
|
|
|
|
const InventoryPanel = lazy(async () => {
|
|
const module = await import('../InventoryPanel');
|
|
|
|
return {
|
|
default: module.InventoryPanel,
|
|
};
|
|
});
|
|
|
|
export function GameShellStoryPanels({
|
|
visibleGameState,
|
|
visibleCurrentStory,
|
|
isLoading,
|
|
aiError,
|
|
bottomTab,
|
|
setBottomTab,
|
|
displayedOptions,
|
|
hideStoryOptions,
|
|
canRefreshOptions,
|
|
handleRefreshOptions,
|
|
handleSceneTransitionChoice,
|
|
handleNpcChatInput,
|
|
exitNpcChat,
|
|
characterChatUi,
|
|
inventoryUi,
|
|
battleRewardUi,
|
|
questUi,
|
|
npcChatQuestOfferUi,
|
|
goalUi,
|
|
companionRenderStates,
|
|
characterChatSummaries,
|
|
openOverlayPanel,
|
|
openCampModal,
|
|
openPartyMemberDetails,
|
|
adventureStatistics,
|
|
musicVolume,
|
|
onMusicVolumeChange,
|
|
onSaveAndExit,
|
|
}: {
|
|
visibleGameState: GameState;
|
|
visibleCurrentStory: StoryMoment;
|
|
isLoading: boolean;
|
|
aiError: string | null;
|
|
bottomTab: BottomTab;
|
|
setBottomTab: (tab: BottomTab) => void;
|
|
displayedOptions: StoryOption[];
|
|
hideStoryOptions: boolean;
|
|
canRefreshOptions: boolean;
|
|
handleRefreshOptions: () => void;
|
|
handleSceneTransitionChoice: (option: StoryOption) => void;
|
|
handleNpcChatInput: (input: string) => boolean;
|
|
exitNpcChat: () => boolean;
|
|
characterChatUi: CharacterChatUi;
|
|
inventoryUi: InventoryFlowUi;
|
|
battleRewardUi: BattleRewardUi;
|
|
questUi: QuestFlowUi;
|
|
npcChatQuestOfferUi: NpcChatQuestOfferUi;
|
|
goalUi: GoalFlowUi;
|
|
companionRenderStates: CompanionRenderState[];
|
|
characterChatSummaries: Record<string, string>;
|
|
openOverlayPanel: (panel: 'character' | 'inventory') => void;
|
|
openCampModal: () => void;
|
|
openPartyMemberDetails: (selection: GameCanvasEntitySelection) => void;
|
|
adventureStatistics: GameShellAdventureStatistics;
|
|
musicVolume: number;
|
|
onMusicVolumeChange: (value: number) => void;
|
|
onSaveAndExit: () => void;
|
|
}) {
|
|
const playerCharacter = visibleGameState.playerCharacter;
|
|
if (!playerCharacter) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="story-top-tabs mb-3 grid grid-cols-3 gap-2 sm:gap-3">
|
|
<button
|
|
onClick={() => setBottomTab('character')}
|
|
className={`pixel-nine-slice pixel-pressable pixel-tab-button ${bottomTab === 'character' ? 'pixel-tab-button--active text-white' : 'text-zinc-300'}`}
|
|
style={getNineSliceStyle(bottomTab === 'character' ? UI_CHROME.tabActive : UI_CHROME.tabInactive, { paddingX: 10, paddingY: 8 })}
|
|
>
|
|
<span className="pixel-tab-button__inner">
|
|
<PixelIcon
|
|
src={bottomTab === 'character' ? TAB_ICONS.character.active : TAB_ICONS.character.inactive}
|
|
className={`pixel-tab-button__icon ${bottomTab === 'character' ? 'opacity-100' : 'opacity-70'}`}
|
|
/>
|
|
<span className="pixel-tab-button__label">角色</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setBottomTab('adventure')}
|
|
className={`pixel-nine-slice pixel-pressable pixel-tab-button ${bottomTab === 'adventure' ? 'pixel-tab-button--active text-white' : 'text-zinc-300'}`}
|
|
style={getNineSliceStyle(bottomTab === 'adventure' ? UI_CHROME.tabActive : UI_CHROME.tabInactive, { paddingX: 10, paddingY: 8 })}
|
|
>
|
|
<span className="pixel-tab-button__inner">
|
|
<PixelIcon
|
|
src={bottomTab === 'adventure' ? TAB_ICONS.adventure.active : TAB_ICONS.adventure.inactive}
|
|
className={`pixel-tab-button__icon ${bottomTab === 'adventure' ? 'opacity-100' : 'opacity-70'}`}
|
|
/>
|
|
<span className="pixel-tab-button__label">冒险</span>
|
|
</span>
|
|
</button>
|
|
<button
|
|
onClick={() => setBottomTab('inventory')}
|
|
className={`pixel-nine-slice pixel-pressable pixel-tab-button ${bottomTab === 'inventory' ? 'pixel-tab-button--active text-white' : 'text-zinc-300'}`}
|
|
style={getNineSliceStyle(bottomTab === 'inventory' ? UI_CHROME.tabActive : UI_CHROME.tabInactive, { paddingX: 10, paddingY: 8 })}
|
|
>
|
|
<span className="pixel-tab-button__inner">
|
|
<PixelIcon
|
|
src={bottomTab === 'inventory' ? TAB_ICONS.inventory.active : TAB_ICONS.inventory.inactive}
|
|
className={`pixel-tab-button__icon ${bottomTab === 'inventory' ? 'opacity-100' : 'opacity-70'}`}
|
|
/>
|
|
<span className="pixel-tab-button__label">背包</span>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
{bottomTab === 'character' && (
|
|
<Suspense fallback={<PanelLoadingFallback label="正在加载角色面板" />}>
|
|
<CharacterPanel
|
|
worldType={visibleGameState.worldType}
|
|
customWorldProfile={visibleGameState.customWorldProfile}
|
|
playerCharacter={playerCharacter}
|
|
playerHp={visibleGameState.playerHp}
|
|
playerMaxHp={visibleGameState.playerMaxHp}
|
|
playerMana={visibleGameState.playerMana}
|
|
playerMaxMana={visibleGameState.playerMaxMana}
|
|
playerEquipment={visibleGameState.playerEquipment}
|
|
activeBuildBuffs={visibleGameState.activeBuildBuffs}
|
|
companionRenderStates={companionRenderStates}
|
|
npcStates={visibleGameState.npcStates}
|
|
quests={visibleGameState.quests}
|
|
companionArcStates={
|
|
visibleGameState.storyEngineMemory?.companionArcStates ?? []
|
|
}
|
|
companionResolutions={
|
|
visibleGameState.storyEngineMemory?.companionResolutions ?? []
|
|
}
|
|
onOpenCamp={openCampModal}
|
|
onOpenCharacterChat={characterChatUi.openChat}
|
|
chatSummaries={characterChatSummaries}
|
|
onInspectMember={openPartyMemberDetails}
|
|
/>
|
|
</Suspense>
|
|
)}
|
|
|
|
{bottomTab === 'adventure' && (
|
|
<Suspense fallback={<PanelLoadingFallback label="正在加载冒险面板" />}>
|
|
<AdventurePanel
|
|
aiError={aiError}
|
|
currentStory={visibleCurrentStory}
|
|
isLoading={isLoading}
|
|
displayedOptions={displayedOptions}
|
|
hideOptions={hideStoryOptions}
|
|
canRefreshOptions={canRefreshOptions}
|
|
onRefreshOptions={handleRefreshOptions}
|
|
onChoice={handleSceneTransitionChoice}
|
|
onSubmitNpcChatInput={handleNpcChatInput}
|
|
onExitNpcChat={exitNpcChat}
|
|
onOpenCharacter={() => openOverlayPanel('character')}
|
|
onOpenInventory={() => openOverlayPanel('inventory')}
|
|
playerCharacter={playerCharacter}
|
|
worldType={visibleGameState.worldType}
|
|
quests={visibleGameState.quests}
|
|
questUi={questUi}
|
|
npcChatQuestOfferUi={npcChatQuestOfferUi}
|
|
goalStack={goalUi.goalStack}
|
|
goalPulse={goalUi.pulse}
|
|
onDismissGoalPulse={goalUi.dismissPulse}
|
|
battleRewardUi={battleRewardUi}
|
|
playerHp={visibleGameState.playerHp}
|
|
playerMaxHp={visibleGameState.playerMaxHp}
|
|
playerMana={visibleGameState.playerMana}
|
|
playerMaxMana={visibleGameState.playerMaxMana}
|
|
playerSkillCooldowns={visibleGameState.playerSkillCooldowns}
|
|
inBattle={visibleGameState.inBattle}
|
|
currentNpcBattleMode={visibleGameState.currentNpcBattleMode}
|
|
chapterState={visibleGameState.chapterState ?? null}
|
|
journeyBeat={
|
|
visibleGameState.storyEngineMemory?.currentJourneyBeat ?? null
|
|
}
|
|
statistics={adventureStatistics}
|
|
musicVolume={musicVolume}
|
|
onMusicVolumeChange={onMusicVolumeChange}
|
|
onSaveAndExit={onSaveAndExit}
|
|
/>
|
|
</Suspense>
|
|
)}
|
|
|
|
{bottomTab === 'inventory' && (
|
|
<Suspense fallback={<PanelLoadingFallback label="正在加载背包面板" />}>
|
|
<InventoryPanel
|
|
playerCharacter={playerCharacter}
|
|
worldType={visibleGameState.worldType}
|
|
playerInventory={visibleGameState.playerInventory}
|
|
playerCurrency={visibleGameState.playerCurrency}
|
|
playerHp={visibleGameState.playerHp}
|
|
playerMaxHp={visibleGameState.playerMaxHp}
|
|
playerMana={visibleGameState.playerMana}
|
|
playerMaxMana={visibleGameState.playerMaxMana}
|
|
inBattle={visibleGameState.inBattle}
|
|
onUseItem={inventoryUi.useInventoryItem}
|
|
onEquipItem={inventoryUi.equipInventoryItem}
|
|
forgeRecipes={inventoryUi.forgeRecipes}
|
|
onCraftRecipe={inventoryUi.craftRecipe}
|
|
onDismantleItem={inventoryUi.dismantleItem}
|
|
onReforgeItem={inventoryUi.reforgeItem}
|
|
continueGameDigest={
|
|
visibleGameState.storyEngineMemory?.continueGameDigest ?? null
|
|
}
|
|
narrativeCodex={
|
|
visibleGameState.storyEngineMemory?.narrativeCodex ?? []
|
|
}
|
|
narrativeQaReport={
|
|
visibleGameState.storyEngineMemory?.narrativeQaReport ?? null
|
|
}
|
|
/>
|
|
</Suspense>
|
|
)}
|
|
</>
|
|
);
|
|
}
|