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.
AdLike2048/assets/script/manager/MatchWinStats.ts

222 lines
8.2 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;
}
3 days ago
/**
* 开战成功:写入未结算标记(杀进程后下次启动按失败扣星)。
* @param opponentRankId 对手段位 id;无效时记 -1
*/
static markBattleStarted(opponentRankId: number): void {
const id = Math.floor(Number(opponentRankId) || 0);
gg.data.doc.pendingBattleOppRankId = id > 0 ? id : -1;
gg.data.saveLocal();
console.log('[MatchWinStats] markBattleStarted oppRank=', gg.data.doc.pendingBattleOppRankId);
}
/** 清除未结算标记(不单独存档,由调用方 save) */
static clearPendingBattle(): void {
if ((gg.data.doc?.pendingBattleOppRankId ?? 0) === 0) return;
gg.data.doc.pendingBattleOppRankId = 0;
}
/**
* 读档后:若有未结算对局则静默按失败结算(扣星/记负场,无奖励 UI)。
* @returns 是否执行了补结算
*/
static settlePendingAsLoseIfNeeded(): boolean {
const raw = Math.floor(Number(gg.data.doc?.pendingBattleOppRankId) || 0);
if (raw === 0) return false;
const opp = raw > 0 ? raw : 0;
console.log('[MatchWinStats] 未结算对局按失败处理 oppRank=', opp);
this.record(false, opp);
return true;
}
3 weeks ago
/**
* 结算一场(平局不记)。
* 玩家胜:玩家胜场 +1,该对手负场 +1;反之亦然。
* @param playerWin 玩家是否获胜
* @param opponentRankId 对手段位 id
*/
static record(playerWin: boolean, opponentRankId: number, opts?: { save?: boolean }): void {
3 days ago
this.clearPendingBattle();
3 weeks ago
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}`;
}
}