This commit is contained in:
102
src/data/runtimeStats.ts
Normal file
102
src/data/runtimeStats.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { GameRuntimeStats, GameState } from '../types';
|
||||
|
||||
function clampNonNegativeInteger(value: unknown) {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) return 0;
|
||||
return Math.max(0, Math.floor(value));
|
||||
}
|
||||
|
||||
function getIsoTimestamp(now: number) {
|
||||
return new Date(now).toISOString();
|
||||
}
|
||||
|
||||
export function createInitialGameRuntimeStats(
|
||||
options: {
|
||||
isActiveRun?: boolean;
|
||||
now?: number;
|
||||
} = {},
|
||||
): GameRuntimeStats {
|
||||
const now = options.now ?? Date.now();
|
||||
|
||||
return {
|
||||
playTimeMs: 0,
|
||||
lastPlayTickAt: options.isActiveRun ? getIsoTimestamp(now) : null,
|
||||
hostileNpcsDefeated: 0,
|
||||
questsAccepted: 0,
|
||||
itemsUsed: 0,
|
||||
scenesTraveled: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeGameRuntimeStats(
|
||||
stats: Partial<GameRuntimeStats> | null | undefined,
|
||||
options: {
|
||||
isActiveRun?: boolean;
|
||||
now?: number;
|
||||
} = {},
|
||||
): GameRuntimeStats {
|
||||
const now = options.now ?? Date.now();
|
||||
|
||||
return {
|
||||
playTimeMs: typeof stats?.playTimeMs === 'number' && Number.isFinite(stats.playTimeMs)
|
||||
? Math.max(0, stats.playTimeMs)
|
||||
: 0,
|
||||
lastPlayTickAt: options.isActiveRun ? getIsoTimestamp(now) : null,
|
||||
hostileNpcsDefeated: clampNonNegativeInteger(stats?.hostileNpcsDefeated),
|
||||
questsAccepted: clampNonNegativeInteger(stats?.questsAccepted),
|
||||
itemsUsed: clampNonNegativeInteger(stats?.itemsUsed),
|
||||
scenesTraveled: clampNonNegativeInteger(stats?.scenesTraveled),
|
||||
};
|
||||
}
|
||||
|
||||
export function incrementGameRuntimeStats(
|
||||
stats: GameRuntimeStats,
|
||||
increments: Partial<Pick<GameRuntimeStats, 'hostileNpcsDefeated' | 'questsAccepted' | 'itemsUsed' | 'scenesTraveled'>>,
|
||||
): GameRuntimeStats {
|
||||
return {
|
||||
...stats,
|
||||
hostileNpcsDefeated: stats.hostileNpcsDefeated + clampNonNegativeInteger(increments.hostileNpcsDefeated),
|
||||
questsAccepted: stats.questsAccepted + clampNonNegativeInteger(increments.questsAccepted),
|
||||
itemsUsed: stats.itemsUsed + clampNonNegativeInteger(increments.itemsUsed),
|
||||
scenesTraveled: stats.scenesTraveled + clampNonNegativeInteger(increments.scenesTraveled),
|
||||
};
|
||||
}
|
||||
|
||||
export function syncGameRuntimePlayTime(stats: GameRuntimeStats, now = Date.now()): GameRuntimeStats {
|
||||
if (!stats.lastPlayTickAt) {
|
||||
return {
|
||||
...stats,
|
||||
lastPlayTickAt: getIsoTimestamp(now),
|
||||
};
|
||||
}
|
||||
|
||||
const lastTickMs = Date.parse(stats.lastPlayTickAt);
|
||||
if (Number.isNaN(lastTickMs)) {
|
||||
return {
|
||||
...stats,
|
||||
lastPlayTickAt: getIsoTimestamp(now),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...stats,
|
||||
playTimeMs: stats.playTimeMs + Math.max(0, now - lastTickMs),
|
||||
lastPlayTickAt: getIsoTimestamp(now),
|
||||
};
|
||||
}
|
||||
|
||||
export function getLiveGamePlayTimeMs(stats: GameRuntimeStats, now = Date.now()) {
|
||||
if (!stats.lastPlayTickAt) return stats.playTimeMs;
|
||||
|
||||
const lastTickMs = Date.parse(stats.lastPlayTickAt);
|
||||
if (Number.isNaN(lastTickMs)) return stats.playTimeMs;
|
||||
|
||||
return stats.playTimeMs + Math.max(0, now - lastTickMs);
|
||||
}
|
||||
|
||||
export function syncGameStatePlayTime(state: GameState, now = Date.now()): GameState {
|
||||
return {
|
||||
...state,
|
||||
runtimeStats: syncGameRuntimePlayTime(state.runtimeStats, now),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user