1
This commit is contained in:
112
src/persistence/runtimeSnapshot.ts
Normal file
112
src/persistence/runtimeSnapshot.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user