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

140 lines
5.2 KiB

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