You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.9 KiB
191 lines
6.9 KiB
|
3 weeks ago
|
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}`;
|
||
|
|
}
|
||
|
|
}
|