192 lines
5.8 KiB
TypeScript
192 lines
5.8 KiB
TypeScript
import { useEffect } from 'react';
|
|
|
|
import { DEFAULT_MUSIC_VOLUME } from '../../../packages/shared/src/contracts/runtime';
|
|
import { useAuthUi } from '../../components/auth/AuthUiContext';
|
|
import type { RpgRuntimeShellProps } from '../../components/rpg-runtime-shell';
|
|
import { activateRosterCompanion, benchActiveCompanion } from '../../data/companionRoster';
|
|
import { syncGameStatePlayTime } from '../../data/runtimeStats';
|
|
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
|
import { useBackgroundMusic } from '../useBackgroundMusic';
|
|
import { useCombatFlow } from '../useCombatFlow';
|
|
import { useNpcInteractionFlow } from '../useNpcInteractionFlow';
|
|
import { useRpgRuntimeStory } from '../rpg-runtime-story';
|
|
import { useRpgSessionBootstrap } from './useRpgSessionBootstrap';
|
|
import { useRpgSessionPersistence } from './useRpgSessionPersistence';
|
|
|
|
/**
|
|
* RPG 主运行态装配器真实实现。
|
|
* 工作包 C 起主链改为组合 `rpg-session` 下的 bootstrap / persistence 新入口。
|
|
*/
|
|
export function useRpgRuntimeSession(): RpgRuntimeShellProps {
|
|
const authUi = useAuthUi();
|
|
const {
|
|
gameState,
|
|
setGameState,
|
|
bottomTab,
|
|
setBottomTab,
|
|
isMapOpen,
|
|
setIsMapOpen,
|
|
resetGame,
|
|
handleCustomWorldSelect: selectCustomWorld,
|
|
handleBackToWorldSelect: backToWorldSelect,
|
|
handleCharacterSelect: selectCharacter,
|
|
} = useRpgSessionBootstrap();
|
|
|
|
const combatFlow = useCombatFlow({
|
|
setGameState,
|
|
});
|
|
|
|
const storyFlow = useRpgRuntimeStory({
|
|
gameState,
|
|
setGameState,
|
|
buildResolvedChoiceState: combatFlow.buildResolvedChoiceState,
|
|
playResolvedChoice: combatFlow.playResolvedChoice,
|
|
});
|
|
|
|
const { companionRenderStates, buildCompanionRenderStates } =
|
|
useNpcInteractionFlow(gameState);
|
|
const persistence = useRpgSessionPersistence({
|
|
authenticatedUserId: authUi?.user?.id ?? null,
|
|
gameState,
|
|
bottomTab,
|
|
currentStory: storyFlow.currentStory,
|
|
isLoading: storyFlow.isLoading,
|
|
setGameState,
|
|
setBottomTab,
|
|
hydrateStoryState: storyFlow.hydrateStoryState,
|
|
resetStoryState: storyFlow.resetStoryState,
|
|
});
|
|
|
|
useBackgroundMusic({
|
|
active: Boolean(
|
|
gameState.playerCharacter && gameState.currentScene === 'Story',
|
|
),
|
|
volume: authUi?.musicVolume ?? DEFAULT_MUSIC_VOLUME,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!gameState.playerCharacter || gameState.currentScene !== 'Story') {
|
|
return;
|
|
}
|
|
|
|
const intervalId = window.setInterval(() => {
|
|
setGameState((currentState) => {
|
|
if (
|
|
!currentState.playerCharacter ||
|
|
currentState.currentScene !== 'Story'
|
|
) {
|
|
return currentState;
|
|
}
|
|
|
|
return syncGameStatePlayTime(currentState);
|
|
});
|
|
}, 15000);
|
|
|
|
return () => window.clearInterval(intervalId);
|
|
}, [gameState.currentScene, gameState.playerCharacter, setGameState]);
|
|
|
|
const handleCustomWorldSelect = (
|
|
customWorldProfile: Parameters<typeof selectCustomWorld>[0],
|
|
) => {
|
|
storyFlow.resetStoryState();
|
|
selectCustomWorld(customWorldProfile);
|
|
};
|
|
|
|
const handleCharacterSelect = (
|
|
character: Parameters<typeof selectCharacter>[0],
|
|
) => {
|
|
storyFlow.resetStoryState();
|
|
selectCharacter(character);
|
|
};
|
|
|
|
const handleBackToWorldSelect = () => {
|
|
storyFlow.resetStoryState();
|
|
backToWorldSelect();
|
|
};
|
|
|
|
const handleContinueGame = (snapshot?: HydratedSavedGameSnapshot | null) => {
|
|
void persistence.continueSavedGame(snapshot);
|
|
};
|
|
|
|
const handleStartNewGame = () => {
|
|
void persistence.clearSavedGame();
|
|
storyFlow.resetStoryState();
|
|
resetGame();
|
|
};
|
|
|
|
const handleSaveAndExit = () => {
|
|
const syncedGameState = syncGameStatePlayTime(gameState);
|
|
void persistence.saveCurrentGame({
|
|
gameState: syncedGameState,
|
|
bottomTab,
|
|
currentStory: storyFlow.currentStory,
|
|
});
|
|
storyFlow.resetStoryState();
|
|
resetGame();
|
|
};
|
|
|
|
const handleBenchCompanion = (npcId: string) => {
|
|
setGameState((currentState) => benchActiveCompanion(currentState, npcId));
|
|
};
|
|
|
|
const handleActivateRosterCompanion = (
|
|
npcId: string,
|
|
swapNpcId?: string | null,
|
|
) => {
|
|
setGameState((currentState) =>
|
|
activateRosterCompanion(currentState, npcId, swapNpcId),
|
|
);
|
|
};
|
|
|
|
return {
|
|
session: {
|
|
gameState,
|
|
currentStory: storyFlow.currentStory,
|
|
isLoading: storyFlow.isLoading,
|
|
aiError: storyFlow.aiError,
|
|
bottomTab,
|
|
setBottomTab,
|
|
isMapOpen,
|
|
setIsMapOpen,
|
|
},
|
|
story: {
|
|
displayedOptions: storyFlow.displayedOptions,
|
|
canRefreshOptions: storyFlow.canRefreshOptions,
|
|
handleRefreshOptions: storyFlow.handleRefreshOptions,
|
|
handleChoice: storyFlow.handleChoice,
|
|
handleNpcChatInput: storyFlow.handleNpcChatInput,
|
|
exitNpcChat: storyFlow.exitNpcChat,
|
|
handleMapTravelToScene: storyFlow.travelToSceneFromMap,
|
|
npcUi: storyFlow.npcUi,
|
|
characterChatUi: storyFlow.characterChatUi,
|
|
inventoryUi: storyFlow.inventoryUi,
|
|
battleRewardUi: storyFlow.battleRewardUi,
|
|
questUi: storyFlow.questUi,
|
|
npcChatQuestOfferUi: storyFlow.npcChatQuestOfferUi,
|
|
goalUi: storyFlow.goalUi,
|
|
},
|
|
entry: {
|
|
hasSavedGame: persistence.hasSavedGame,
|
|
savedSnapshot: persistence.savedSnapshot,
|
|
handleContinueGame,
|
|
handleStartNewGame,
|
|
handleSaveAndExit,
|
|
handleCustomWorldSelect,
|
|
handleBackToWorldSelect,
|
|
handleCharacterSelect,
|
|
},
|
|
companions: {
|
|
companionRenderStates,
|
|
buildCompanionRenderStates,
|
|
onBenchCompanion: handleBenchCompanion,
|
|
onActivateRosterCompanion: handleActivateRosterCompanion,
|
|
},
|
|
audio: {
|
|
musicVolume: authUi?.musicVolume ?? DEFAULT_MUSIC_VOLUME,
|
|
onMusicVolumeChange: authUi?.setMusicVolume ?? (() => {}),
|
|
},
|
|
};
|
|
}
|
|
|
|
export type RpgRuntimeSessionResult = ReturnType<typeof useRpgRuntimeSession>;
|