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.
141 lines
5.2 KiB
141 lines
5.2 KiB
|
1 week ago
|
import { Node } from 'cc';
|
||
|
|
import { DEV } from 'cc/env';
|
||
|
|
import { tetriFloorNode } from '../tetriCard/tetriFloorNode';
|
||
|
|
import { tetriNode } from '../tetriCard/tetriNode';
|
||
|
|
import { PfDiagnostics } from '../diagnostics/PfDiagnostics';
|
||
|
|
|
||
|
|
type BlockCounts = { weapon: number; floor: number; weaponHidden: number };
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 叠塔异常诊断(问题2/3):
|
||
|
|
* - BLOCK_STACK:有 floor 无 weapon(幽灵架 / 方块不见了只剩无重力底)
|
||
|
|
* - BLOCK_HIDDEN:武器块还在场景但不可见
|
||
|
|
* - PIN_COLLAPSE:钉 floor 后武器块大量消失(崩开)
|
||
|
|
*/
|
||
|
|
export class TetriStackDiagnostics {
|
||
|
|
private static _lastPinMs = 0;
|
||
|
|
private static _sessionBattle: object | null = null;
|
||
|
|
private static _ghostShelfReported = false;
|
||
|
|
private static _pinWeaponBaseline = 0;
|
||
|
|
private static _pinFloorName = '';
|
||
|
|
|
||
|
|
private static readonly LOG_COOLDOWN_MS = 120000;
|
||
|
|
private static readonly PIN_COLLAPSE_COOLDOWN_MS = 30000;
|
||
|
|
private static readonly LAUNCH_ZERO_COOLDOWN_MS = 60000;
|
||
|
|
private static readonly PIN_WINDOW_MS = 8000;
|
||
|
|
|
||
|
|
/** 离开战斗 / 新局初始化时清空,避免上一局 pin/幽灵架状态污染 */
|
||
|
|
static resetBattle(): void {
|
||
|
|
this._lastPinMs = 0;
|
||
|
|
this._sessionBattle = null;
|
||
|
|
this._ghostShelfReported = false;
|
||
|
|
this._pinWeaponBaseline = 0;
|
||
|
|
this._pinFloorName = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
static countBlocks(): BlockCounts {
|
||
|
|
const map = gg.game?.CurentBattle?.TetraMap;
|
||
|
|
if (!map?._tetrisTable?.isValid) return { weapon: 0, floor: 0, weaponHidden: 0 };
|
||
|
|
let weapon = 0;
|
||
|
|
let floor = 0;
|
||
|
|
let weaponHidden = 0;
|
||
|
|
const walk = (n: Node) => {
|
||
|
|
const children = n.children;
|
||
|
|
for (let i = 0; i < children.length; i++) {
|
||
|
|
const c = children[i];
|
||
|
|
if (c.getComponent(tetriNode) && !c.getComponent(tetriFloorNode)) {
|
||
|
|
weapon++;
|
||
|
|
if (c.isValid && !c.activeInHierarchy) weaponHidden++;
|
||
|
|
}
|
||
|
|
if (c.getComponent(tetriFloorNode)) floor++;
|
||
|
|
walk(c);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
walk(map._tetrisTable);
|
||
|
|
return { weapon, floor, weaponHidden };
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _syncBattleSession(): void {
|
||
|
|
const b = gg.game?.CurentBattle ?? null;
|
||
|
|
if (b !== this._sessionBattle) {
|
||
|
|
this._sessionBattle = b;
|
||
|
|
this._ghostShelfReported = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static _log(tag: string, detail: string, cooldownMs = this.LOG_COOLDOWN_MS, level: 'error' | 'warn' | 'info' = 'warn'): void {
|
||
|
|
PfDiagnostics.write('STACK', tag, detail, cooldownMs, level);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** pinFloor 开始时记时间戳与武器基线,用于 pin 后塌塔判断 */
|
||
|
|
static onPinFloor(floorNodeName: string): void {
|
||
|
|
this._lastPinMs = Date.now();
|
||
|
|
this._pinFloorName = floorNodeName ?? '';
|
||
|
|
this._pinWeaponBaseline = this.countBlocks().weapon;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* pin 序列跑完后:塌塔 / 隐藏块 / 幽灵架。
|
||
|
|
*/
|
||
|
|
static afterPinFloorSequence(): void {
|
||
|
|
const after = this.countBlocks();
|
||
|
|
const before = this._pinWeaponBaseline;
|
||
|
|
|
||
|
|
if (after.weaponHidden >= 2) {
|
||
|
|
if (DEV) {
|
||
|
|
console.warn('[StackDiag] BLOCK_HIDDEN', `hid=${after.weaponHidden} w=${after.weapon} f=${after.floor}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (before >= 2) {
|
||
|
|
const lost = before - after.weapon;
|
||
|
|
if (lost >= 2) {
|
||
|
|
this._log(
|
||
|
|
'PIN_COLLAPSE',
|
||
|
|
`w${before}->${after.weapon} f=${after.floor} lost=${lost} floor=${this._pinFloorName}`,
|
||
|
|
this.PIN_COLLAPSE_COOLDOWN_MS,
|
||
|
|
'error',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (after.floor >= 1 && after.weapon === 0 && before >= 2) {
|
||
|
|
const afterPin = this._lastPinMs > 0 && Date.now() - this._lastPinMs < this.PIN_WINDOW_MS;
|
||
|
|
this._log(
|
||
|
|
'BLOCK_STACK',
|
||
|
|
`w${before}->0 f=${after.floor} afterPin=${afterPin ? 1 : 0} floor=${this._pinFloorName}`,
|
||
|
|
this.LOG_COOLDOWN_MS,
|
||
|
|
'error',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 发射点落到世界原点兜底(仅 DEV console,不上传) */
|
||
|
|
static onLaunchOriginFallback(weaponId: number, carrierCount: number): void {
|
||
|
|
if (!DEV) return;
|
||
|
|
const c = this.countBlocks();
|
||
|
|
console.warn('[StackDiag] LAUNCH_ZERO', `wid=${weaponId} carriers=${carrierCount} w=${c.weapon} f=${c.floor}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 武器块已从场景移除之后调用 */
|
||
|
|
static afterWeaponRemoved(source: string, weaponBefore: number, nodeName: string): void {
|
||
|
|
if (source === 'hammer') return;
|
||
|
|
|
||
|
|
this._syncBattleSession();
|
||
|
|
if (this._ghostShelfReported) return;
|
||
|
|
|
||
|
|
const after = this.countBlocks();
|
||
|
|
if (after.floor < 1 || after.weapon > 0) return;
|
||
|
|
if (weaponBefore < 2) return;
|
||
|
|
|
||
|
|
this._ghostShelfReported = true;
|
||
|
|
const afterPin = this._lastPinMs > 0 && Date.now() - this._lastPinMs < this.PIN_WINDOW_MS;
|
||
|
|
this._log(
|
||
|
|
'BLOCK_STACK',
|
||
|
|
`w${weaponBefore}->0 f=${after.floor} src=${source} node=${nodeName} afterPin=${afterPin ? 1 : 0}`,
|
||
|
|
this.LOG_COOLDOWN_MS,
|
||
|
|
'error',
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|