147 lines
4.7 KiB
TypeScript
147 lines
4.7 KiB
TypeScript
import { type LonLatPoint } from '../../utils/projection'
|
|
import { type GameLaunchEnvelope } from '../../utils/gameLaunch'
|
|
import { type GameSessionState } from './gameSessionState'
|
|
|
|
export interface RecoveryTelemetrySnapshot {
|
|
distanceMeters: number
|
|
currentSpeedKmh: number | null
|
|
averageSpeedKmh: number | null
|
|
heartRateBpm: number | null
|
|
caloriesKcal: number | null
|
|
lastGpsPoint: LonLatPoint | null
|
|
lastGpsAt: number | null
|
|
lastGpsAccuracyMeters: number | null
|
|
}
|
|
|
|
export interface RecoveryViewportSnapshot {
|
|
zoom: number
|
|
centerTileX: number
|
|
centerTileY: number
|
|
rotationDeg: number
|
|
gpsLockEnabled: boolean
|
|
hasGpsCenteredOnce: boolean
|
|
}
|
|
|
|
export interface RecoveryRuntimeSnapshot {
|
|
gameState: GameSessionState
|
|
telemetry: RecoveryTelemetrySnapshot
|
|
viewport: RecoveryViewportSnapshot
|
|
currentGpsPoint: LonLatPoint | null
|
|
currentGpsAccuracyMeters: number | null
|
|
currentGpsInsideMap: boolean
|
|
bonusScore: number
|
|
quizCorrectCount: number
|
|
quizWrongCount: number
|
|
quizTimeoutCount: number
|
|
}
|
|
|
|
export interface SessionRecoverySnapshot {
|
|
schemaVersion: 1
|
|
savedAt: number
|
|
launchEnvelope: GameLaunchEnvelope
|
|
configAppId: string
|
|
configVersion: string
|
|
runtime: RecoveryRuntimeSnapshot
|
|
}
|
|
|
|
const SESSION_RECOVERY_STORAGE_KEY = 'cmr.sessionRecovery.v1'
|
|
|
|
function cloneLonLatPoint(point: LonLatPoint | null): LonLatPoint | null {
|
|
if (!point) {
|
|
return null
|
|
}
|
|
return {
|
|
lon: point.lon,
|
|
lat: point.lat,
|
|
}
|
|
}
|
|
|
|
function cloneGameSessionState(state: GameSessionState): GameSessionState {
|
|
return {
|
|
status: state.status,
|
|
endReason: state.endReason,
|
|
startedAt: state.startedAt,
|
|
endedAt: state.endedAt,
|
|
completedControlIds: state.completedControlIds.slice(),
|
|
skippedControlIds: state.skippedControlIds.slice(),
|
|
currentTargetControlId: state.currentTargetControlId,
|
|
inRangeControlId: state.inRangeControlId,
|
|
score: state.score,
|
|
guidanceState: state.guidanceState,
|
|
modeState: state.modeState
|
|
? JSON.parse(JSON.stringify(state.modeState)) as Record<string, unknown>
|
|
: null,
|
|
}
|
|
}
|
|
|
|
export function cloneSessionRecoverySnapshot(snapshot: SessionRecoverySnapshot): SessionRecoverySnapshot {
|
|
return {
|
|
schemaVersion: 1,
|
|
savedAt: snapshot.savedAt,
|
|
launchEnvelope: JSON.parse(JSON.stringify(snapshot.launchEnvelope)) as GameLaunchEnvelope,
|
|
configAppId: snapshot.configAppId,
|
|
configVersion: snapshot.configVersion,
|
|
runtime: {
|
|
gameState: cloneGameSessionState(snapshot.runtime.gameState),
|
|
telemetry: {
|
|
distanceMeters: snapshot.runtime.telemetry.distanceMeters,
|
|
currentSpeedKmh: snapshot.runtime.telemetry.currentSpeedKmh,
|
|
averageSpeedKmh: snapshot.runtime.telemetry.averageSpeedKmh,
|
|
heartRateBpm: snapshot.runtime.telemetry.heartRateBpm,
|
|
caloriesKcal: snapshot.runtime.telemetry.caloriesKcal,
|
|
lastGpsPoint: cloneLonLatPoint(snapshot.runtime.telemetry.lastGpsPoint),
|
|
lastGpsAt: snapshot.runtime.telemetry.lastGpsAt,
|
|
lastGpsAccuracyMeters: snapshot.runtime.telemetry.lastGpsAccuracyMeters,
|
|
},
|
|
viewport: {
|
|
zoom: snapshot.runtime.viewport.zoom,
|
|
centerTileX: snapshot.runtime.viewport.centerTileX,
|
|
centerTileY: snapshot.runtime.viewport.centerTileY,
|
|
rotationDeg: snapshot.runtime.viewport.rotationDeg,
|
|
gpsLockEnabled: snapshot.runtime.viewport.gpsLockEnabled,
|
|
hasGpsCenteredOnce: snapshot.runtime.viewport.hasGpsCenteredOnce,
|
|
},
|
|
currentGpsPoint: cloneLonLatPoint(snapshot.runtime.currentGpsPoint),
|
|
currentGpsAccuracyMeters: snapshot.runtime.currentGpsAccuracyMeters,
|
|
currentGpsInsideMap: snapshot.runtime.currentGpsInsideMap,
|
|
bonusScore: snapshot.runtime.bonusScore,
|
|
quizCorrectCount: snapshot.runtime.quizCorrectCount,
|
|
quizWrongCount: snapshot.runtime.quizWrongCount,
|
|
quizTimeoutCount: snapshot.runtime.quizTimeoutCount,
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeSessionRecoverySnapshot(raw: unknown): SessionRecoverySnapshot | null {
|
|
if (!raw || typeof raw !== 'object') {
|
|
return null
|
|
}
|
|
|
|
const candidate = raw as SessionRecoverySnapshot
|
|
if (candidate.schemaVersion !== 1 || !candidate.runtime || !candidate.runtime.gameState) {
|
|
return null
|
|
}
|
|
|
|
return cloneSessionRecoverySnapshot(candidate)
|
|
}
|
|
|
|
export function loadSessionRecoverySnapshot(): SessionRecoverySnapshot | null {
|
|
try {
|
|
return normalizeSessionRecoverySnapshot(wx.getStorageSync(SESSION_RECOVERY_STORAGE_KEY))
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function saveSessionRecoverySnapshot(snapshot: SessionRecoverySnapshot): void {
|
|
try {
|
|
wx.setStorageSync(SESSION_RECOVERY_STORAGE_KEY, cloneSessionRecoverySnapshot(snapshot))
|
|
} catch {}
|
|
}
|
|
|
|
export function clearSessionRecoverySnapshot(): void {
|
|
try {
|
|
wx.removeStorageSync(SESSION_RECOVERY_STORAGE_KEY)
|
|
} catch {}
|
|
}
|