import { OtherDataType } from '../game/GameDataEnums'; import { RankDataManager } from './RankDataManager'; import { DailyWinStats } from './DailyWinStats'; import { RankBoardManager } from './RankBoardManager'; import { AiBattleBridge } from '../battle/ai/bridge/AiBattleBridge'; import { MatchStreakStats } from './MatchStreakStats'; import { HeadIconManager } from './HeadIconManager'; import { EmolumentDataManager } from './EmolumentDataManager'; /** 单方胜负场次 */ export type MatchWinRecord = { /** 胜场 */ win: number; /** 负场 */ lose: number; }; /** 匹配页抽出、进战后由开战消耗的对手快照 */ let _pendingOpponentRankId = 0; /** 匹配抽出的对手总星(与 rankId 同次抽取) */ let _pendingOpponentStars = 0; /** * 匹配胜率:玩家战绩进 doc;各对手段位进 onlyLocal。 * 每日跨天清零;无对局记 0%。 */ export class MatchWinStats { /** 读档 / 打开匹配页 / 结算前确保仍为当日统计 */ static ensureToday(): void { const today = this._todayKey(); const saved = String(gg.data.doc?.matchWinStatsDate ?? '').trim(); if (saved === today) return; gg.data.doc.matchWinCount = 0; gg.data.doc.matchLoseCount = 0; gg.data.doc.matchWinStatsDate = today; gg.data.setkeyKeyValueDayClear(OtherDataType.MatchAiWinStats, true); gg.data.saveLocal(); console.log('[MatchWinStats] 跨天重置胜率', today); } /** 跨天回调(GameData.onFirstLoginToday) */ static onCrossDay(): void { this.ensureToday(); } /** 按当前段位抽出本局对手,供匹配页展示、进战复用 */ static ensurePendingOpponentRank(): number { if (_pendingOpponentRankId > 0) return _pendingOpponentRankId; const profile = AiBattleBridge.inst.buildMatchProfile(); if (profile && profile.opponentRankId > 0) { _pendingOpponentRankId = profile.opponentRankId; _pendingOpponentStars = RankDataManager.clampRankStars(profile.opponentStars); return _pendingOpponentRankId; } const match = RankDataManager.resolveOpponentStarMatch(); if (match && match.opponentRankId > 0) { _pendingOpponentRankId = match.opponentRankId; _pendingOpponentStars = RankDataManager.clampRankStars(match.opponentStars); return _pendingOpponentRankId; } const id = RankDataManager.pickOpponentRankId(); _pendingOpponentRankId = id > 0 ? id : 0; _pendingOpponentStars = _pendingOpponentRankId > 0 ? RankDataManager.getPlayerRankStars() : 0; if (_pendingOpponentRankId > 0) { console.log('[MatchWinStats] 对手星快照缺失,回退玩家总星', _pendingOpponentRankId, _pendingOpponentStars); } return _pendingOpponentRankId; } /** 查看 pending 对手段位,不消耗 */ static peekPendingOpponentRankId(): number { return _pendingOpponentRankId; } /** 查看 pending 对手总星,不消耗 */ static peekPendingOpponentStars(): number { return _pendingOpponentStars; } /** 开局消耗;无 pending 时返回 0 */ static takePendingOpponentRankId(): number { const id = _pendingOpponentRankId; _pendingOpponentRankId = 0; _pendingOpponentStars = 0; return id; } /** 玩家胜负场次 */ static getPlayerRecord(): MatchWinRecord { this.ensureToday(); const win = Math.max(0, Math.floor(Number(gg.data.doc?.matchWinCount) || 0)); const lose = Math.max(0, Math.floor(Number(gg.data.doc?.matchLoseCount) || 0)); return { win, lose }; } /** 玩家胜率 0~100 */ static getPlayerWinRate(): number { return this.toRate(this.getPlayerRecord()); } /** * 某对手段位的胜负场次 * @param opponentRankId Rank 表 id */ static getAiRecord(opponentRankId: number): MatchWinRecord { this.ensureToday(); const id = Math.floor(Number(opponentRankId) || 0); if (id <= 0) return { win: 0, lose: 0 }; const raw = String(gg.data.getkeyKeyValue(OtherDataType.MatchAiWinStats, id, true) ?? ''); const parts = raw.split('&'); const win = Math.max(0, Math.floor(Number(parts[0]) || 0)); const lose = Math.max(0, Math.floor(Number(parts[1]) || 0)); return { win, lose }; } /** * 某对手段位胜率 0~100 * @param opponentRankId Rank 表 id */ static getAiWinRate(opponentRankId: number): number { return this.toRate(this.getAiRecord(opponentRankId)); } /** * 胜率百分比 * @param rec 胜负场次 */ static toRate(rec: MatchWinRecord): number { const total = rec.win + rec.lose; if (total <= 0) return 0; return (rec.win / total) * 100; } /** * 结算一场(平局不记)。 * 玩家胜:玩家胜场 +1,该对手负场 +1;反之亦然。 * @param playerWin 玩家是否获胜 * @param opponentRankId 对手段位 id */ static record(playerWin: boolean, opponentRankId: number, opts?: { save?: boolean }): void { const p = this.getPlayerRecord(); if (playerWin) p.win += 1; else p.lose += 1; gg.data.doc.matchWinCount = p.win; gg.data.doc.matchLoseCount = p.lose; MatchStreakStats.applyBattleResult(playerWin, { save: false }); if (playerWin) { DailyWinStats.recordWin({ save: false }); void RankBoardManager.syncSelfDailyWins(); } const starsChanged = RankDataManager.applyBattleResult(playerWin, { save: false }); if (starsChanged) { HeadIconManager.onRankStarsChanged(); void RankBoardManager.syncSelfScore(); } EmolumentDataManager.syncAfterBattle(playerWin, starsChanged); const id = Math.floor(Number(opponentRankId) || 0); let aiRec: MatchWinRecord | null = null; if (id > 0) { aiRec = this.getAiRecord(id); if (playerWin) aiRec.lose += 1; else aiRec.win += 1; gg.data.setkeyKeyValue(OtherDataType.MatchAiWinStats, id, `${aiRec.win}&${aiRec.lose}`, true); } if (opts?.save !== false) gg.data.saveLocal(); const aiPart = aiRec ? ` ai=${aiRec.win}/${aiRec.win + aiRec.lose}` : ''; console.log( `[MatchWinStats] record playerWin=${playerWin ? 1 : 0} oppRank=${id}` + ` player=${p.win}/${p.win + p.lose}` + ` streakW=${MatchStreakStats.getWinStreak()} streakL=${MatchStreakStats.getLoseStreak()}${aiPart}`, ); } /** 今日日期键 yyyyMMdd */ private static _todayKey(): string { const d = new Date(gg.data.getCurentTime2()); const y = d.getFullYear(); const m = `${d.getMonth() + 1}`.padStart(2, '0'); const day = `${d.getDate()}`.padStart(2, '0'); return `${y}${m}${day}`; } }