@@ -7,10 +7,7 @@ import {
|
||||
resolveHydratedSnapshotState,
|
||||
} from './runtimeSnapshot';
|
||||
|
||||
function createStory(
|
||||
text: string,
|
||||
streaming = false,
|
||||
): StoryMoment {
|
||||
function createStory(text: string, streaming = false): StoryMoment {
|
||||
return {
|
||||
text,
|
||||
options: [],
|
||||
@@ -63,6 +60,13 @@ function createHydratedBattleSnapshot(
|
||||
hp: 18,
|
||||
maxHp: 32,
|
||||
description: '拦路的刀客',
|
||||
levelProfile: {
|
||||
level: 4,
|
||||
referenceStrength: 202,
|
||||
progressionRole: 'rival',
|
||||
source: 'manual',
|
||||
},
|
||||
experienceReward: 20,
|
||||
},
|
||||
],
|
||||
playerX: 0,
|
||||
@@ -160,6 +164,14 @@ describe('runtimeSnapshot', () => {
|
||||
armor: null,
|
||||
relic: null,
|
||||
});
|
||||
expect(hydrated.gameState.playerProgression).toEqual({
|
||||
level: 1,
|
||||
currentLevelXp: 0,
|
||||
totalXp: 0,
|
||||
xpToNextLevel: 60,
|
||||
pendingLevelUps: 0,
|
||||
lastGrantedSource: null,
|
||||
});
|
||||
expect(hydrated.gameState.playerMaxHp).toBe(12);
|
||||
expect(hydrated.gameState.playerHp).toBe(12);
|
||||
expect(hydrated.gameState.playerMaxMana).toBe(12);
|
||||
@@ -180,6 +192,13 @@ describe('runtimeSnapshot', () => {
|
||||
description: '拦路的刀客',
|
||||
hp: 18,
|
||||
maxHp: 32,
|
||||
levelProfile: {
|
||||
level: 4,
|
||||
referenceStrength: 202,
|
||||
progressionRole: 'rival',
|
||||
source: 'manual',
|
||||
},
|
||||
experienceReward: 20,
|
||||
attackRange: expect.any(Number),
|
||||
speed: expect.any(Number),
|
||||
animation: 'idle',
|
||||
@@ -210,6 +229,13 @@ describe('runtimeSnapshot', () => {
|
||||
speed: 7,
|
||||
hp: 18,
|
||||
maxHp: 32,
|
||||
levelProfile: {
|
||||
level: 4,
|
||||
referenceStrength: 202,
|
||||
progressionRole: 'rival',
|
||||
source: 'manual',
|
||||
},
|
||||
experienceReward: 20,
|
||||
renderKind: 'npc',
|
||||
encounter: {
|
||||
kind: 'npc',
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
buildInitialNpcState,
|
||||
createNpcBattleMonster,
|
||||
} from '../data/npcInteractions';
|
||||
import { normalizePlayerProgressionState } from '../data/playerProgression';
|
||||
import type {
|
||||
Encounter,
|
||||
GameState,
|
||||
@@ -18,9 +19,7 @@ import type {
|
||||
SnapshotState,
|
||||
} from './runtimeSnapshotTypes';
|
||||
|
||||
function normalizeBottomTab(
|
||||
bottomTab: string | null | undefined,
|
||||
): BottomTab {
|
||||
function normalizeBottomTab(bottomTab: string | null | undefined): BottomTab {
|
||||
return bottomTab === 'character' || bottomTab === 'inventory'
|
||||
? bottomTab
|
||||
: 'adventure';
|
||||
@@ -106,6 +105,8 @@ function normalizeRuntimeBattleEncounter(
|
||||
typeof encounter.npcAvatar === 'string' ? encounter.npcAvatar : '',
|
||||
context: typeof encounter.context === 'string' ? encounter.context : '',
|
||||
hostile: true,
|
||||
levelProfile: encounter.levelProfile,
|
||||
experienceReward: encounter.experienceReward,
|
||||
} satisfies Encounter;
|
||||
}
|
||||
|
||||
@@ -126,9 +127,7 @@ function resolveRuntimeNpcBattleState(
|
||||
}
|
||||
|
||||
const npcStateKey =
|
||||
gameState.currentBattleNpcId ??
|
||||
encounter.id ??
|
||||
encounter.npcName;
|
||||
gameState.currentBattleNpcId ?? encounter.id ?? encounter.npcName;
|
||||
const npcState =
|
||||
gameState.npcStates[npcStateKey] ??
|
||||
buildInitialNpcState(
|
||||
@@ -161,9 +160,13 @@ function hydrateRuntimeNpcBattleMonster(params: {
|
||||
);
|
||||
const candidate = params.hostileNpc as Partial<SceneHostileNpc>;
|
||||
const xMeters =
|
||||
typeof candidate.xMeters === 'number' ? candidate.xMeters : template.xMeters;
|
||||
typeof candidate.xMeters === 'number'
|
||||
? candidate.xMeters
|
||||
: template.xMeters;
|
||||
const yOffset =
|
||||
typeof candidate.yOffset === 'number' ? candidate.yOffset : template.yOffset;
|
||||
typeof candidate.yOffset === 'number'
|
||||
? candidate.yOffset
|
||||
: template.yOffset;
|
||||
|
||||
return {
|
||||
...template,
|
||||
@@ -198,6 +201,11 @@ function hydrateRuntimeNpcBattleMonster(params: {
|
||||
: template.attackRange,
|
||||
speed:
|
||||
typeof candidate.speed === 'number' ? candidate.speed : template.speed,
|
||||
levelProfile: candidate.levelProfile ?? template.levelProfile,
|
||||
experienceReward:
|
||||
typeof candidate.experienceReward === 'number'
|
||||
? candidate.experienceReward
|
||||
: template.experienceReward,
|
||||
encounter: {
|
||||
...template.encounter,
|
||||
xMeters,
|
||||
@@ -263,6 +271,9 @@ export function normalizeSavedGameState(gameState: GameState) {
|
||||
|
||||
return hydrateRuntimeNpcBattleGameState({
|
||||
...hydratableState,
|
||||
playerProgression: normalizePlayerProgressionState(
|
||||
hydratableState.playerProgression ?? null,
|
||||
),
|
||||
playerMaxHp,
|
||||
playerHp: Math.min(hydratableState.playerHp, playerMaxHp),
|
||||
playerMaxMana,
|
||||
@@ -305,14 +316,19 @@ export function isHydratedSnapshotState(
|
||||
(gameState.runtimeSessionId === null ||
|
||||
typeof gameState.runtimeSessionId === 'string') &&
|
||||
(!gameState.playerCharacter ||
|
||||
Boolean(gameState.playerEquipment && typeof gameState.playerEquipment === 'object')),
|
||||
Boolean(
|
||||
gameState.playerEquipment &&
|
||||
typeof gameState.playerEquipment === 'object',
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
export function rehydrateSavedSnapshot<T extends HydratedSnapshotState>(
|
||||
snapshot: T,
|
||||
): T {
|
||||
const hydratedGameState = hydrateRuntimeNpcBattleGameState(snapshot.gameState);
|
||||
const hydratedGameState = hydrateRuntimeNpcBattleGameState(
|
||||
snapshot.gameState,
|
||||
);
|
||||
|
||||
if (hydratedGameState === snapshot.gameState) {
|
||||
return snapshot;
|
||||
|
||||
Reference in New Issue
Block a user