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/battle/debug/BagTextDragDebugLog.ts

315 lines
11 KiB

import { Node, sp, UIOpacity } from 'cc';
import { TextGrid } from '../view/TextGrid';
const TAG = '[BagTextDrag]';
/** 池污染偷字专项:控制台过滤此标签即可复制整段证据 */
const STEAL = '[BagTextSteal]';
/** 默认关;控制台 `setBagTextDragLogVerbose(true)` 开启 */
let _verbose = false;
/**
* 开关 BagTextDrag / BagTextSteal 打印。
* @param enabled 是否开启
*/
export function setBagTextDragLogVerbose(enabled: boolean): void {
_verbose = !!enabled;
}
/** 当前是否开启拖动诊断打印 */
export function isBagTextDragLogVerbose(): boolean {
return _verbose;
}
try {
(globalThis as { setBagTextDragLogVerbose?: typeof setBagTextDragLogVerbose }).setBagTextDragLogVerbose = setBagTextDragLogVerbose;
} catch {
/* ignore */
}
/** Spine 组件上可读的缓存/皮肤字段(引擎 protected,仅诊断用) */
type SpineDebugFields = sp.Skeleton & {
_cacheMode?: number;
_skinName?: string;
_animationName?: string;
};
/** 字块显示/皮肤快照(拖动诊断) */
export interface BagTextDragVisualSnap {
/** 节点是否有效 */
valid: boolean;
/** 世界坐标 */
world: string;
/** 本地坐标 */
local: string;
/** 根节点 active */
nodeActive: boolean;
/** 根节点缩放 */
scale: string;
/** UIOpacity(无组件为 null) */
opacity: number | null;
/** skin 节点 active(无节点为 null) */
skinActive: boolean | null;
/** skin 本地坐标 */
skinLocal: string | null;
/** Skeleton.enabled */
skEnabled: boolean | null;
/** Spine cacheMode 数值 */
cacheMode: number | null;
/** cacheMode 可读名 */
cacheModeName: string;
/** 是否动画缓存模式 */
animCached: boolean | null;
/** 当前皮肤名 */
spineSkin: string | null;
/** 当前动画名 */
anim: string | null;
/** 业务字 id */
weapon2Id: number;
/** 位置描述 */
location: string;
/** 是否疑似「可点不可见」 */
suspectHidden: boolean;
}
/**
* 背包/地图字块拖动显示诊断。
* 排查「拖动后文字偶现消失但仍可操作 / 槽下无 textGrid」。
*/
export class BagTextDragDebugLog {
private static _seq = 0;
/** 当前进行中的拖动序号(未开始为 0) */
private static _activeSeq = 0;
/** 新一次拖动开始,返回序号 */
static begin(grid: TextGrid | null, stage = 'start'): number {
this._seq += 1;
this._activeSeq = this._seq;
this.snap(this._seq, stage, grid);
this.steal('DRAG_BEGIN', `seq=#${this._seq} path=${this.parentPath(grid)} w2=${grid?.weapon2Id ?? 0}`);
return this._seq;
}
/** 当前拖动序号(无进行中返回 0) */
static activeSeq(): number {
return this._activeSeq;
}
/**
* 池污染/偷字专项日志(默认关;开启后便于整段复制)。
* @param event 事件码
* @param detail 详情
*/
static steal(event: string, detail: string): void {
if (!_verbose) return;
const seq = this._activeSeq > 0 ? this._activeSeq : this._seq;
console.log(`${STEAL} #${seq} ${event} | ${detail}`);
}
/**
* 打印槽位子节点名(佐证 textGrid 是否还在 uigrid 下)。
* @param slotIndex 槽位
* @param slotNode 槽节点
* @param stage 阶段
*/
static slotChildren(slotIndex: number, slotNode: Node | null | undefined, stage: string): void {
if (!slotNode?.isValid) {
this.steal('SLOT_CHILDREN', `${stage} slot=${slotIndex} slotNode=invalid`);
return;
}
const names: string[] = [];
let hasTextGrid = false;
for (let i = 0; i < slotNode.children.length; i++) {
const c = slotNode.children[i];
if (!c?.isValid) continue;
names.push(c.name);
if (c.name === 'textGrid' || !!c.getComponent(TextGrid)) hasTextGrid = true;
}
this.steal(
hasTextGrid ? 'SLOT_OK' : 'SLOT_MISSING_TEXTGRID',
`${stage} slot=${slotIndex} name=${slotNode.name} children=[${names.join(',')}] hasTextGrid=${hasTextGrid}`,
);
}
/**
* getNode 取到仍在场景/索引中的在用节点。
* @param reason 原因
* @param detail 详情
*/
static poolHitLive(reason: string, detail: string): void {
this.steal(`POOL_HIT_LIVE:${reason}`, detail);
}
/**
* 源字已离槽。
* @param slotIndex 槽位
* @param grid 字块
* @param repaired 是否已挂回
*/
static sourceLeftSlot(slotIndex: number, grid: TextGrid | null | undefined, repaired: boolean): void {
this.steal(
repaired ? 'SOURCE_LEFT_SLOT_REPAIRED' : 'SOURCE_LEFT_SLOT',
`slot=${slotIndex} w2=${grid?.weapon2Id ?? 0} path=${this.parentPath(grid)} repaired=${repaired}`,
);
}
/**
* 补齐空槽。
* @param slotIndex 槽位
* @param weapon2Id 字 id
* @param ok 是否成功
*/
static fillMissing(slotIndex: number, weapon2Id: number, ok: boolean): void {
this.steal(
ok ? 'FILL_MISSING_OK' : 'FILL_MISSING_FAIL',
`slot=${slotIndex} w2=${weapon2Id}`,
);
}
/**
* 从池数组摘掉仍在场景的节点。
* @param removed 摘掉个数
* @param detail 详情
*/
static detachFromPool(removed: number, detail: string): void {
if (removed <= 0) return;
this.steal('DETACH_FROM_POOL', `removed=${removed} ${detail}`);
}
/**
* 打印字块显示快照。
* @param seq 拖动序号
* @param stage 阶段名
* @param grid 字块
* @param extra 附加说明
*/
static snap(seq: number, stage: string, grid: TextGrid | null | undefined, extra?: string): void {
if (!_verbose) return;
const s = this.capture(grid);
const flag = s.suspectHidden ? ' SUSPECT_HIDDEN' : '';
const more = extra ? ` ${extra}` : '';
console.log(
`${TAG} #${seq} ${stage}${flag}${more}`
+ ` w2=${s.weapon2Id} loc=${s.location}`
+ ` world=${s.world} local=${s.local}`
+ ` nodeActive=${s.nodeActive} scale=${s.scale} opacity=${s.opacity}`
+ ` skinActive=${s.skinActive} skinLocal=${s.skinLocal}`
+ ` skEnabled=${s.skEnabled} cache=${s.cacheModeName}(${s.cacheMode}) cached=${s.animCached}`
+ ` spineSkin=${s.spineSkin} anim=${s.anim}`
+ ` path=${this.parentPath(grid)}`,
);
if (s.suspectHidden) {
console.log(`${TAG} #${seq} ${stage} 疑似隐藏但仍可能可点`);
}
}
/** 松手/取消结束 */
static end(seq: number, grid: TextGrid | null | undefined, outcome: string, extra?: string): void {
this.snap(seq, `end:${outcome}`, grid, extra);
this.steal('DRAG_END', `outcome=${outcome}${extra ? ` ${extra}` : ''} path=${this.parentPath(grid)}`);
if (seq === this._activeSeq) this._activeSeq = 0;
}
/** 采集显示/皮肤状态 */
static capture(grid: TextGrid | null | undefined): BagTextDragVisualSnap {
if (!grid?.isValid || !grid.node?.isValid) {
return {
valid: false,
world: 'n/a',
local: 'n/a',
nodeActive: false,
scale: 'n/a',
opacity: null,
skinActive: null,
skinLocal: null,
skEnabled: null,
cacheMode: null,
cacheModeName: 'n/a',
animCached: null,
spineSkin: null,
anim: null,
weapon2Id: 0,
location: 'invalid',
suspectHidden: true,
};
}
const node = grid.node;
const wp = node.worldPosition;
const lp = node.position;
const uo = node.getComponent(UIOpacity);
const skin = grid.getSkinNode();
const sk = skin?.isValid ? skin.getComponent(sp.Skeleton) : null;
const skDbg = sk?.isValid ? (sk as SpineDebugFields) : null;
const cacheMode = skDbg != null ? Number(skDbg._cacheMode) : null;
const loc = grid.location;
const location = loc?.kind === 'bag'
? `bag[${loc.slotIndex}]`
: loc?.kind === 'map'
? `map[${loc.gridIndex}]`
: 'none';
const nodeActive = !!node.active;
const opacity = uo?.isValid ? uo.opacity : null;
const scaleNearZero = Math.abs(node.scale.x) < 0.05 || Math.abs(node.scale.y) < 0.05;
const skinActive = skin?.isValid ? skin.active : null;
const skEnabled = sk?.isValid ? sk.enabled : null;
const suspectHidden = nodeActive
&& (
(opacity != null && opacity < 16)
|| scaleNearZero
|| skinActive === false
|| skEnabled === false
);
return {
valid: true,
world: this._fmtXY(wp.x, wp.y),
local: this._fmtXY(lp.x, lp.y),
nodeActive,
scale: `${node.scale.x.toFixed(2)},${node.scale.y.toFixed(2)}`,
opacity,
skinActive,
skinLocal: skin?.isValid ? this._fmtXY(skin.position.x, skin.position.y) : null,
skEnabled,
cacheMode,
cacheModeName: this._cacheModeName(cacheMode),
animCached: sk?.isValid ? sk.isAnimationCached() : null,
spineSkin: skDbg?._skinName ?? null,
anim: skDbg?._animationName ?? null,
weapon2Id: grid.weapon2Id,
location,
suspectHidden,
};
}
/** 父节点路径(诊断「跑哪去了」) */
static parentPath(grid: TextGrid | null | undefined): string {
if (!grid?.node?.isValid) return 'n/a';
return this.nodeParentPath(grid.node);
}
/** 任意节点父路径 */
static nodeParentPath(node: Node | null | undefined): string {
if (!node?.isValid) return 'n/a';
const parts: string[] = [];
let n: Node | null = node;
for (let i = 0; i < 8 && n?.isValid; i++) {
parts.push(n.name || '?');
n = n.parent;
}
return parts.join(' ← ');
}
private static _fmtXY(x: number, y: number): string {
return `(${x.toFixed(1)},${y.toFixed(1)})`;
}
private static _cacheModeName(mode: number | null): string {
if (mode == null || Number.isNaN(mode)) return 'n/a';
if (mode === sp.SpineAnimationCacheMode.REALTIME) return 'REALTIME';
if (mode === sp.SpineAnimationCacheMode.SHARED_CACHE) return 'SHARED';
if (mode === sp.SpineAnimationCacheMode.PRIVATE_CACHE) return 'PRIVATE';
return `mode${mode}`;
}
}