/** 战令配置计算,不处理金币、登录或其他模块的同步。 */ export const PASS_PERIOD = 30 * 24 * 60 * 60 * 1000; export const PASS_PRODUCTS = { battlepass: 1800, battlepass_30: 3000, battlepass_12: 1200 }; const OLD_TOKENS = [1, 2, 2, 2, 2, 2, 2, 2, 2, ...Array(21).fill(4)]; export function passTier(stage: any): number { return stage && stage.tier === 30 ? 30 : stage && stage.activate ? 18 : 0; } export function legacyExperience(stage: any): number { const level = Math.max(1, Math.min(30, Number(stage.progressLevel) || 1)); return OLD_TOKENS.slice(0, level - 1).reduce((n, cost) => n + cost, 0) + Math.min(OLD_TOKENS[level - 1], Number(stage.progress) || 0); } export function normalizePass(stage: any, rows: any[]): any { if (!stage || !Number(stage.time)) return stage; stage = { ...stage, free: [...(stage.free || [])], passCheck: [...(stage.passCheck || [])] }; if (!Number.isFinite(stage.experience)) { const level = Math.max(1, Math.min(30, Number(stage.progressLevel) || 1)); stage.experience = rows.slice(0, level - 1).reduce((n, row) => n + Number(row.token), 0) + Math.min(Number(rows[level - 1].token), Number(stage.progress) || 0); } let remaining = Math.max(0, stage.experience), unlocked = 0; for (const row of rows) { if (remaining < Number(row.token)) break; remaining -= Number(row.token); unlocked++; } stage.unlocked = unlocked; // 保持旧界面“当前档 + 当前档经验”的表示,完成一档时显示满格。 const complete = remaining === 0 || unlocked === rows.length; stage.progressLevel = Math.max(1, Math.min(rows.length, unlocked + (complete ? 0 : 1))); stage.progress = complete ? Number(rows[stage.progressLevel - 1].token) : remaining; for (const lane of ['free', 'passCheck']) for (let i = 0; i < unlocked; i++) { if (stage[lane][i] !== 0) stage[lane][i] = 1; } return stage; } export function mergePassInfo(raw: any, extra: any, rows: any[], oldRows: any[] = rows): any { if (!raw) return raw; for (const key of [1, 2]) { const old = raw[key]; if (!old || !Number(old.time)) continue; const saved = extra && extra[String(old.time)]; if (saved) { raw[key] = { ...saved, activate: !!old.activate || !!saved.activate, experience: saved.experience + Math.max(0, legacyExperience(old) - (saved.legacyExperience || 0)) }; for (const lane of ['free', 'passCheck']) { raw[key][lane] = [...(saved[lane] || [])]; for (let i = 0; i < Math.min(30, (old[lane] || []).length); i++) if (old[lane][i] === 0) raw[key][lane][i] = 0; } } raw[key] = normalizePass(raw[key], key === 1 ? oldRows : rows); } return raw; } export function legacyPassInfo(pass: any, rows: any[], oldRows: any[] = rows): any { const legacy: any = {}; for (const key of [1, 2]) { const stage = pass[key]; if (!stage || !Number(stage.time)) { legacy[key] = stage; continue; } const level = Math.max(1, Math.min(30, stage.progressLevel)); const row = (key === 1 ? oldRows : rows)[stage.progressLevel - 1]; const progress = stage.unlocked >= level ? OLD_TOKENS[level - 1] : Math.min(OLD_TOKENS[level - 1], Math.floor(stage.progress * OLD_TOKENS[level - 1] / Math.max(1, Number(row.token)))); legacy[key] = { time: stage.time, activate: passTier(stage) > 0, progressLevel: level, progress, free: (stage.free || []).slice(0, 30), passCheck: (stage.passCheck || []).slice(0, 30) }; } return legacy; } export function passBounds(stage: any, count: number) { const tier = passTier(stage); const generated = Math.min(count, tier === 30 ? Math.max(40, (stage.unlocked || 0) + 5) : 40); // 正常滚动至可解锁范围的下一档,后续奖励供上拉预览。 const content = Math.min(generated, tier === 30 ? Math.max(36, (stage.unlocked || 0) + 1) : 36); return { generated, content }; } export function canClaimPass(stage: any, index: number, left: boolean): boolean { // 1 表示已解锁待领取;换期重算的 unlocked 不能撤销存档中已有的领取资格。 return !!stage && index >= 0 && (stage[left ? 'free' : 'passCheck'] || [])[index] === 1 && (index < 35 ? left || passTier(stage) >= 18 : passTier(stage) === 30); } export function hasPassReward(stage: any): boolean { return !!stage && (stage.free || []).some((_, i) => canClaimPass(stage, i, true) || canClaimPass(stage, i, false)); }