消消方块阵换皮表情
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

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';
}
}