This commit is contained in:
308
src/components/rpg-runtime-panels/RpgRuntimePanelRouter.tsx
Normal file
308
src/components/rpg-runtime-panels/RpgRuntimePanelRouter.tsx
Normal file
@@ -0,0 +1,308 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
import type {
|
||||
BattleRewardUi,
|
||||
CharacterChatUi,
|
||||
GoalFlowUi,
|
||||
InventoryFlowUi,
|
||||
NpcChatQuestOfferUi,
|
||||
QuestFlowUi,
|
||||
} from '../../hooks/rpg-runtime-story';
|
||||
import type { BottomTab } from '../../hooks/rpg-session';
|
||||
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 '../rpg-runtime-shell/rpgRuntimeLoaders';
|
||||
import type { RpgAdventureStatistics } from '../rpg-runtime-shell/types';
|
||||
|
||||
const RpgAdventurePanel = lazy(async () => {
|
||||
const module = await import('./RpgAdventurePanel');
|
||||
|
||||
return {
|
||||
default: module.RpgAdventurePanel,
|
||||
};
|
||||
});
|
||||
|
||||
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 interface RpgRuntimePanelRouterProps {
|
||||
visibleGameState: GameState;
|
||||
visibleCurrentStory: StoryMoment;
|
||||
isLoading: boolean;
|
||||
aiError: string | null;
|
||||
bottomTab: BottomTab;
|
||||
setBottomTab: (tab: BottomTab) => void;
|
||||
displayedOptions: StoryOption[];
|
||||
hideStoryOptions: boolean;
|
||||
canRefreshOptions: boolean;
|
||||
handleRefreshOptions: () => void;
|
||||
refreshNpcChatOptions: () => boolean;
|
||||
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: RpgAdventureStatistics;
|
||||
musicVolume: number;
|
||||
onMusicVolumeChange: (value: number) => void;
|
||||
onSaveAndExit: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* RPG 运行态主面板路由器。
|
||||
* 只负责冒险 / 角色 / 背包三个主标签的切换和装配。
|
||||
*/
|
||||
export function RpgRuntimePanelRouter({
|
||||
visibleGameState,
|
||||
visibleCurrentStory,
|
||||
isLoading,
|
||||
aiError,
|
||||
bottomTab,
|
||||
setBottomTab,
|
||||
displayedOptions,
|
||||
hideStoryOptions,
|
||||
canRefreshOptions,
|
||||
handleRefreshOptions,
|
||||
refreshNpcChatOptions,
|
||||
handleSceneTransitionChoice,
|
||||
handleNpcChatInput,
|
||||
exitNpcChat,
|
||||
characterChatUi,
|
||||
inventoryUi,
|
||||
battleRewardUi,
|
||||
questUi,
|
||||
npcChatQuestOfferUi,
|
||||
goalUi,
|
||||
companionRenderStates,
|
||||
characterChatSummaries,
|
||||
openOverlayPanel,
|
||||
openCampModal,
|
||||
openPartyMemberDetails,
|
||||
adventureStatistics,
|
||||
musicVolume,
|
||||
onMusicVolumeChange,
|
||||
onSaveAndExit,
|
||||
}: RpgRuntimePanelRouterProps) {
|
||||
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}
|
||||
playerProgression={visibleGameState.playerProgression ?? null}
|
||||
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="正在加载冒险面板" />}>
|
||||
<RpgAdventurePanel
|
||||
aiError={aiError}
|
||||
currentStory={visibleCurrentStory}
|
||||
isLoading={isLoading}
|
||||
displayedOptions={displayedOptions}
|
||||
hideOptions={hideStoryOptions}
|
||||
canRefreshOptions={
|
||||
visibleCurrentStory.npcChatState
|
||||
? visibleCurrentStory.options.length > 1
|
||||
: canRefreshOptions
|
||||
}
|
||||
onRefreshOptions={() => {
|
||||
if (visibleCurrentStory.npcChatState) {
|
||||
refreshNpcChatOptions();
|
||||
return;
|
||||
}
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RpgRuntimePanelRouter;
|
||||
Reference in New Issue
Block a user