This commit is contained in:
2026-04-10 15:37:02 +08:00
parent 161cd32277
commit f19e482c8f
233 changed files with 43987 additions and 5127 deletions

View File

@@ -0,0 +1,112 @@
import type { GameState, StoryMoment } from '../types';
import type { BottomTab } from '../types/navigation';
import type {
HydratableGameState,
HydratedGameState,
HydratedSnapshotState,
SnapshotState,
} from './runtimeSnapshotTypes';
function normalizeBottomTab(
bottomTab: string | null | undefined,
): BottomTab {
return bottomTab === 'character' || bottomTab === 'inventory'
? bottomTab
: 'adventure';
}
function normalizeEquipmentLoadout(
playerEquipment: HydratableGameState['playerEquipment'],
) {
if (!playerEquipment || typeof playerEquipment !== 'object') {
return null;
}
return {
weapon: playerEquipment.weapon ?? null,
armor: playerEquipment.armor ?? null,
relic: playerEquipment.relic ?? null,
} satisfies GameState['playerEquipment'];
}
function createEmptyEquipmentLoadout() {
return {
weapon: null,
armor: null,
relic: null,
} satisfies GameState['playerEquipment'];
}
export function normalizeSavedStory(story: StoryMoment | null) {
if (!story) {
return null;
}
return {
...story,
streaming: false,
} satisfies StoryMoment;
}
export function normalizeSavedGameState(gameState: GameState) {
const hydratableState = gameState as HydratableGameState;
const resolvedEquipment = normalizeEquipmentLoadout(
hydratableState.playerEquipment,
);
const playerMaxHp = Math.max(1, hydratableState.playerMaxHp);
const playerMaxMana = Math.max(1, hydratableState.playerMaxMana);
return {
...hydratableState,
playerMaxHp,
playerHp: Math.min(hydratableState.playerHp, playerMaxHp),
playerMaxMana,
playerMana: Math.min(hydratableState.playerMana, playerMaxMana),
playerEquipment: resolvedEquipment ?? createEmptyEquipmentLoadout(),
runtimeActionVersion:
typeof hydratableState.runtimeActionVersion === 'number'
? hydratableState.runtimeActionVersion
: 0,
runtimeSessionId:
typeof hydratableState.runtimeSessionId === 'string'
? hydratableState.runtimeSessionId
: null,
} satisfies HydratedGameState;
}
export function hydrateSnapshotState(snapshot: {
gameState: GameState;
currentStory: StoryMoment | null;
bottomTab: string;
}): HydratedSnapshotState {
return {
gameState: normalizeSavedGameState(snapshot.gameState),
currentStory: normalizeSavedStory(snapshot.currentStory),
bottomTab: normalizeBottomTab(snapshot.bottomTab),
};
}
export function isHydratedSnapshotState(
snapshot: SnapshotState,
): snapshot is HydratedSnapshotState {
const { gameState, currentStory, bottomTab } = snapshot;
return Boolean(
(bottomTab === 'adventure' ||
bottomTab === 'character' ||
bottomTab === 'inventory') &&
(!currentStory || currentStory.streaming !== true) &&
typeof gameState.runtimeActionVersion === 'number' &&
(gameState.runtimeSessionId === null ||
typeof gameState.runtimeSessionId === 'string') &&
(!gameState.playerCharacter ||
Boolean(gameState.playerEquipment && typeof gameState.playerEquipment === 'object')),
);
}
export function resolveHydratedSnapshotState(snapshot: SnapshotState) {
return isHydratedSnapshotState(snapshot)
? snapshot
: hydrateSnapshotState(snapshot);
}