This commit is contained in:
174
src/hooks/rpg-runtime-story/storyGenerationState.ts
Normal file
174
src/hooks/rpg-runtime-story/storyGenerationState.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
buildNpcGiftModalState,
|
||||
buildNpcRecruitModalState,
|
||||
buildNpcTradeModalState,
|
||||
NPC_GIFT_FUNCTION,
|
||||
NPC_RECRUIT_FUNCTION,
|
||||
NPC_TRADE_FUNCTION,
|
||||
shouldNpcRecruitOpenModal,
|
||||
} from '../../data/functionCatalog';
|
||||
import {
|
||||
applyQuestProgressFromSceneReached,
|
||||
} from '../../data/questFlow';
|
||||
import {
|
||||
buildInitialNpcState,
|
||||
getPreferredGiftItemId,
|
||||
MAX_COMPANIONS,
|
||||
} from '../../data/npcInteractions';
|
||||
import { incrementGameRuntimeStats } from '../../data/runtimeStats';
|
||||
import { ensureSceneEncounterPreview } from '../../data/sceneEncounterPreviews';
|
||||
import { getScenePresetById } from '../../data/scenePresets';
|
||||
import {
|
||||
AnimationState,
|
||||
type Encounter,
|
||||
type GameState,
|
||||
type StoryOption,
|
||||
} from '../../types';
|
||||
import type {
|
||||
GiftModalState,
|
||||
RecruitModalState,
|
||||
TradeModalState,
|
||||
} from './uiTypes';
|
||||
|
||||
export type NpcInteractionDecision =
|
||||
| { kind: 'none' }
|
||||
| { kind: 'trade_modal'; modal: TradeModalState }
|
||||
| { kind: 'gift_modal'; modal: GiftModalState }
|
||||
| { kind: 'recruit_modal'; modal: RecruitModalState }
|
||||
| { kind: 'recruit_immediate' };
|
||||
|
||||
export type MapTravelResolution = {
|
||||
nextState: GameState;
|
||||
actionText: string;
|
||||
travelResultText: string;
|
||||
};
|
||||
|
||||
function isNpcEncounter(
|
||||
encounter: GameState['currentEncounter'],
|
||||
): encounter is Encounter {
|
||||
return Boolean(encounter?.kind === 'npc');
|
||||
}
|
||||
|
||||
export function getNpcEncounterKey(encounter: Encounter) {
|
||||
return encounter.id ?? encounter.npcName;
|
||||
}
|
||||
|
||||
function getResolvedNpcState(state: GameState, encounter: Encounter) {
|
||||
return (
|
||||
state.npcStates[getNpcEncounterKey(encounter)] ??
|
||||
buildInitialNpcState(encounter, state.worldType)
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveNpcInteractionDecision(
|
||||
state: GameState,
|
||||
option: StoryOption,
|
||||
): NpcInteractionDecision {
|
||||
if (
|
||||
!state.playerCharacter ||
|
||||
!option.interaction ||
|
||||
!isNpcEncounter(state.currentEncounter)
|
||||
) {
|
||||
return { kind: 'none' };
|
||||
}
|
||||
|
||||
const encounter = state.currentEncounter;
|
||||
const npcState = getResolvedNpcState(state, encounter);
|
||||
|
||||
switch (option.functionId) {
|
||||
case NPC_TRADE_FUNCTION.id:
|
||||
return {
|
||||
kind: 'trade_modal',
|
||||
modal: buildNpcTradeModalState(
|
||||
state,
|
||||
encounter,
|
||||
option.actionText,
|
||||
npcState.inventory,
|
||||
),
|
||||
};
|
||||
case NPC_GIFT_FUNCTION.id:
|
||||
{
|
||||
const selectedGiftItemId = getPreferredGiftItemId(
|
||||
state.playerInventory,
|
||||
encounter,
|
||||
{
|
||||
worldType: state.worldType,
|
||||
customWorldProfile: state.customWorldProfile,
|
||||
},
|
||||
);
|
||||
if (!selectedGiftItemId) {
|
||||
return { kind: 'none' };
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'gift_modal',
|
||||
modal: buildNpcGiftModalState(
|
||||
state,
|
||||
encounter,
|
||||
option.actionText,
|
||||
selectedGiftItemId,
|
||||
),
|
||||
};
|
||||
}
|
||||
case NPC_RECRUIT_FUNCTION.id:
|
||||
if (shouldNpcRecruitOpenModal(state.companions.length, MAX_COMPANIONS)) {
|
||||
return {
|
||||
kind: 'recruit_modal',
|
||||
modal: buildNpcRecruitModalState(state, encounter, option.actionText),
|
||||
};
|
||||
}
|
||||
|
||||
return { kind: 'recruit_immediate' };
|
||||
default:
|
||||
return { kind: 'none' };
|
||||
}
|
||||
}
|
||||
|
||||
export function buildMapTravelResolution(
|
||||
state: GameState,
|
||||
sceneId: string,
|
||||
): MapTravelResolution | null {
|
||||
if (!state.worldType || !state.playerCharacter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetScene = getScenePresetById(state.worldType, sceneId);
|
||||
if (!targetScene || targetScene.id === state.currentScenePreset?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextState = ensureSceneEncounterPreview({
|
||||
...state,
|
||||
runtimeStats: incrementGameRuntimeStats(state.runtimeStats, {
|
||||
scenesTraveled: 1,
|
||||
}),
|
||||
quests: applyQuestProgressFromSceneReached(state.quests, targetScene.id),
|
||||
currentScenePreset: targetScene,
|
||||
currentEncounter: null,
|
||||
npcInteractionActive: false,
|
||||
sceneHostileNpcs: [],
|
||||
playerX: 0,
|
||||
playerFacing: 'right' as const,
|
||||
animationState: AnimationState.IDLE,
|
||||
playerActionMode: 'idle' as const,
|
||||
activeCombatEffects: [],
|
||||
scrollWorld: false,
|
||||
inBattle: false,
|
||||
lastObserveSignsSceneId: null,
|
||||
lastObserveSignsReport: null,
|
||||
currentBattleNpcId: null,
|
||||
currentNpcBattleMode: null,
|
||||
currentNpcBattleOutcome: null,
|
||||
sparReturnEncounter: null,
|
||||
sparPlayerHpBefore: null,
|
||||
sparPlayerMaxHpBefore: null,
|
||||
sparStoryHistoryBefore: null,
|
||||
});
|
||||
const travelResultText = `你离开${state.currentScenePreset?.name ?? '当前位置'},前往${targetScene.name}。`;
|
||||
|
||||
return {
|
||||
nextState,
|
||||
actionText: `前往${targetScene.name}`,
|
||||
travelResultText,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user