export type PvpContentType = 'dungeon' | 'raid' export type CpuDifficulty = 1 | 2 | 3 | 4 | 5 export type CpuPvpLeaderboardEntry = { characterName: string className: string contentType: PvpContentType encountersCleared: number cpuDifficulty: CpuDifficulty result: 'victory' | 'defeat' completedAt: string } const cpuLeaderboardKey = 'chronicle.pvpCpuLeaderboard.v1' const checkpointKey = 'chronicle.pvpRoguelikeCheckpoint.v1' export function randomCpuDifficulty(): CpuDifficulty { return (Math.floor(Math.random() * 5) + 1) as CpuDifficulty } export function loadCpuPvpLeaderboard(contentType?: PvpContentType): CpuPvpLeaderboardEntry[] { try { const raw = JSON.parse(localStorage.getItem(cpuLeaderboardKey) ?? '[]') as CpuPvpLeaderboardEntry[] return raw .filter((entry) => !contentType || entry.contentType === contentType) .sort((left, right) => right.encountersCleared - left.encountersCleared || right.cpuDifficulty - left.cpuDifficulty || right.completedAt.localeCompare(left.completedAt), ) .slice(0, 12) } catch { return [] } } export function recordCpuPvpLeaderboard(entry: CpuPvpLeaderboardEntry) { const current = loadCpuPvpLeaderboard() const next = [...current, entry] .sort((left, right) => right.encountersCleared - left.encountersCleared || right.cpuDifficulty - left.cpuDifficulty || right.completedAt.localeCompare(left.completedAt), ) .slice(0, 30) localStorage.setItem(cpuLeaderboardKey, JSON.stringify(next)) } function checkpointStorageKey(characterId: number, contentType: PvpContentType) { return `${checkpointKey}:${characterId}:${contentType}` } export function loadPvpRoguelikeCheckpoint(characterId: number, contentType: PvpContentType) { const value = Number(localStorage.getItem(checkpointStorageKey(characterId, contentType)) ?? 1) return Number.isInteger(value) && value >= 5 ? value : 1 } export function recordPvpRoguelikeCheckpoint( characterId: number, contentType: PvpContentType, stage: number, ) { if (stage < 5 || stage % 5 !== 0) return loadPvpRoguelikeCheckpoint(characterId, contentType) const current = loadPvpRoguelikeCheckpoint(characterId, contentType) const next = Math.max(current, stage) localStorage.setItem(checkpointStorageKey(characterId, contentType), String(next)) return next }