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.
217 lines
7.7 KiB
217 lines
7.7 KiB
import { DEV } from 'cc/env';
|
|
import { UIID } from '../game/ConfigRes';
|
|
|
|
import { PfDiagnostics } from './PfDiagnostics';
|
|
|
|
/**
|
|
* 战斗诊断:仅上报高置信度真 bug(胜利未结算等)。
|
|
* 预警波次卡住类日志仅 DEV 本地,不上传平台。
|
|
*/
|
|
export class BattleDiagnostics {
|
|
private static _pauseSinceMs = 0;
|
|
private static _pauseReason = '';
|
|
private static _lastStuckLogMs = 0;
|
|
private static _warnTrackSinceMs = 0;
|
|
private static _lastWarnStuckLogMs = 0;
|
|
|
|
/** 真卡死判定:pause 持续超过此时间才考虑上报 */
|
|
private static readonly STUCK_PAUSE_MS = 45000;
|
|
/** 同局同 tag 上传间隔 */
|
|
private static readonly STUCK_LOG_COOLDOWN_MS = 600000;
|
|
|
|
/** 结算流程中其它 pause 不报;waveWin|panels=none 本身就是要抓的 bug */
|
|
private static readonly _SETTLEMENT_PAUSE = new Set([
|
|
'BattleFail', 'BattleRevive',
|
|
]);
|
|
|
|
private static readonly _INIT_PAUSE = new Set([
|
|
'battleInit', 'unknown',
|
|
]);
|
|
|
|
private static readonly _INTENTIONAL_PAUSE = new Set([
|
|
'TetriDoc', 'adRecoverHp', 'useShovel', 'mechanismEdit',
|
|
'SkillSelect', 'TurnTable', 'BattlePause', 'BattleRevive', 'BattleFail',
|
|
'newMechanism', 'videoAd',
|
|
]);
|
|
|
|
/** 仅这些 reason + panels=none 才上传 STUCK_PAUSE(已有 orphan heal 的不报) */
|
|
private static readonly _STUCK_UPLOAD_REASONS = new Set([
|
|
'waveWarn', 'waveWin',
|
|
]);
|
|
|
|
static resetBattle(): void {
|
|
this._pauseSinceMs = 0;
|
|
this._pauseReason = '';
|
|
this._warnTrackSinceMs = 0;
|
|
this._lastStuckLogMs = 0;
|
|
this._lastWarnStuckLogMs = 0;
|
|
}
|
|
|
|
static onPause(reason = 'unknown'): void {
|
|
this._pauseSinceMs = Date.now();
|
|
this._pauseReason = reason;
|
|
}
|
|
|
|
static getPauseReason(): string {
|
|
return this._pauseReason;
|
|
}
|
|
|
|
static onTetriDocPauseHeal(): void {}
|
|
static onWaveWarnPauseHeal(): void {}
|
|
static onSkillSelectPauseHeal(): void {}
|
|
static onTurnTablePauseHeal(): void {}
|
|
|
|
static onResumeSuccess(): void {
|
|
this._pauseSinceMs = 0;
|
|
this._pauseReason = '';
|
|
}
|
|
|
|
static onResumeBlocked(_blocker: string): void {}
|
|
|
|
static onWarnStart(_wave: number, _fixLeft: number): void {
|
|
this._warnTrackSinceMs = Date.now();
|
|
}
|
|
|
|
static onWarnFinished(_wave: number): void {
|
|
this._warnTrackSinceMs = 0;
|
|
}
|
|
|
|
static onWarnForceFinish(_wave: number, _warnMs: number): void {
|
|
this._warnTrackSinceMs = 0;
|
|
}
|
|
|
|
static tick(battle: {
|
|
IsGamePause?: boolean;
|
|
IsGameOver?: boolean;
|
|
IsWarnTimeActive?: boolean;
|
|
IsEdit?: boolean;
|
|
CurentWaveNum?: number;
|
|
tetriWarnTimeFix?: number;
|
|
} | null | undefined): void {
|
|
if (!battle || battle !== gg.game?.CurentBattle || battle.IsGameOver) return;
|
|
|
|
const now = Date.now();
|
|
this._tickWaveWarn(battle, now);
|
|
|
|
if (battle.IsGamePause && this._pauseSinceMs > 0 && !battle.IsWarnTimeActive) {
|
|
const pausedMs = now - this._pauseSinceMs;
|
|
if (
|
|
pausedMs >= this.STUCK_PAUSE_MS
|
|
&& now - this._lastStuckLogMs >= this.STUCK_LOG_COOLDOWN_MS
|
|
&& this._shouldReportStuckPause(this._pauseReason)
|
|
) {
|
|
this._lastStuckLogMs = now;
|
|
PfDiagnostics.write(
|
|
'BATTLE',
|
|
'STUCK_PAUSE',
|
|
`ms=${pausedMs}|reason=${this._pauseReason}|${this._panelFlags()}`,
|
|
this.STUCK_LOG_COOLDOWN_MS,
|
|
'error',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static _tickWaveWarn(
|
|
battle: {
|
|
IsWarnTimeActive?: boolean;
|
|
IsGamePause?: boolean;
|
|
CurentWaveNum?: number;
|
|
tetriWarnTimeFix?: number;
|
|
},
|
|
now: number,
|
|
): void {
|
|
if (!battle.IsWarnTimeActive) {
|
|
this._warnTrackSinceMs = 0;
|
|
return;
|
|
}
|
|
|
|
if (this._warnTrackSinceMs <= 0) {
|
|
this._warnTrackSinceMs = now;
|
|
}
|
|
|
|
const warnMs = now - this._warnTrackSinceMs;
|
|
const hasUiModal = this._hasUiModal();
|
|
const fixLeft = battle.tetriWarnTimeFix ?? 0;
|
|
const wave = battle.CurentWaveNum ?? 0;
|
|
|
|
// 预警倒计时结束后未开波:仅本地记录,不上传平台(避免 panels=warn 类日志刷屏)
|
|
if (
|
|
fixLeft <= 0
|
|
&& !hasUiModal
|
|
&& warnMs >= 8000
|
|
&& now - this._lastWarnStuckLogMs >= this.STUCK_LOG_COOLDOWN_MS
|
|
) {
|
|
this._lastWarnStuckLogMs = now;
|
|
if (DEV) {
|
|
console.warn(
|
|
'[BattleDiag][local]',
|
|
`WAVE_WARN_STUCK wave=${wave}|fix=${fixLeft}|pause=${battle.IsGamePause ? 1 : 0}|ms=${warnMs}|${this._panelFlags()}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static _shouldReportStuckPause(reason: string): boolean {
|
|
if (!this._STUCK_UPLOAD_REASONS.has(reason)) return false;
|
|
if (this._hasUiModal()) return false;
|
|
const b = gg.game?.CurentBattle;
|
|
if (b?.IsEdit) return false;
|
|
if (this._SETTLEMENT_PAUSE.has(reason)) return false;
|
|
if (this._INIT_PAUSE.has(reason)) return false;
|
|
if (this._INTENTIONAL_PAUSE.has(reason)) return false;
|
|
return true;
|
|
}
|
|
|
|
static getResumeBlocker(): string | null {
|
|
if (gg.ui.panelIsShow(UIID.newMechanism)) return 'newMechanism';
|
|
if (gg.ui.panelIsShow(UIID.BattlePause)) return 'BattlePause';
|
|
if (gg.ui.panelIsShow(UIID.BattleFail)) return 'BattleFail';
|
|
if (gg.ui.panelIsShow(UIID.SkillSelect)) return 'SkillSelect';
|
|
if (gg.ui.panelIsOpening(UIID.SkillSelect)) return 'SkillSelect';
|
|
if (gg.ui.panelIsShow(UIID.TurnTable)) return 'TurnTable';
|
|
if (gg.ui.panelIsOpening(UIID.TurnTable)) return 'TurnTable';
|
|
if (gg.ui.panelIsShow(UIID.BattleRevive)) return 'BattleRevive';
|
|
if (gg.ui.panelIsShow(UIID.TetriDoc)) return 'TetriDoc';
|
|
const b = gg.game?.CurentBattle;
|
|
if (b?.IsGachaModeChoujiang) return 'GachaModeChoujiang';
|
|
if (b?.IsEdit) return 'IsEdit';
|
|
if (b?.IsWarnTimeActive && b?.IsGamePause) return 'waveWarn';
|
|
return null;
|
|
}
|
|
|
|
private static _hasUiModal(): boolean {
|
|
if (gg.ui.panelIsShow(UIID.newMechanism)) return true;
|
|
if (gg.ui.panelIsShow(UIID.BattlePause)) return true;
|
|
if (gg.ui.panelIsShow(UIID.BattleFail)) return true;
|
|
if (gg.ui.panelIsShow(UIID.SkillSelect)) return true;
|
|
if (gg.ui.panelIsOpening(UIID.SkillSelect)) return true;
|
|
if (gg.ui.panelIsShow(UIID.TurnTable)) return true;
|
|
if (gg.ui.panelIsOpening(UIID.TurnTable)) return true;
|
|
if (gg.ui.panelIsShow(UIID.BattleRevive)) return true;
|
|
if (gg.ui.panelIsShow(UIID.TetriDoc)) return true;
|
|
if (gg.ui.panelIsOpening(UIID.TetriDoc)) return true;
|
|
const b = gg.game?.CurentBattle;
|
|
if (b?.IsGachaModeChoujiang) return true;
|
|
if (b?.IsEdit) return true;
|
|
return false;
|
|
}
|
|
|
|
private static _panelFlags(): string {
|
|
const ids: Array<[string, number]> = [
|
|
['SS', UIID.SkillSelect],
|
|
['TT', UIID.TurnTable],
|
|
['BP', UIID.BattlePause],
|
|
['BF', UIID.BattleFail],
|
|
['BR', UIID.BattleRevive],
|
|
['TD', UIID.TetriDoc],
|
|
];
|
|
const on = ids.filter(([, id]) => gg.ui.panelIsShow(id)).map(([k]) => k);
|
|
const b = gg.game?.CurentBattle;
|
|
const extra: string[] = [];
|
|
if (b?.IsWarnTimeActive) extra.push('warn');
|
|
if (b?.IsGachaModeChoujiang) extra.push('gacha');
|
|
const parts = [...on, ...extra];
|
|
return parts.length ? `panels=${parts.join(',')}` : 'panels=none';
|
|
}
|
|
}
|
|
|