import type { BattleSession } from 'db://assets/mx/module/battle'; import { AttrId, TeamComp, BattleTeamId } from 'db://assets/mx/module/battle'; import type { Player } from '../../game/Player'; import { getTowerLoadoutService } from '../combat/TowerLoadoutService'; import { isGlobalSkillEntry, sumSelectedBaseAttrWan, wanToWeightMul, } from '../skill/GlobalSkillEffects'; import { TableNames } from '../../game/ConfigTableData'; const TAG = '[GlobalSkillDebug]'; const STYLE = 'color:#67C23A;font-weight:bold'; /** 默认关闭;需要验数时 setGlobalSkillDebugEnabled(true) */ let _enabled = false; export function setGlobalSkillDebugEnabled(enabled: boolean): void { _enabled = enabled; console.log(`%c${TAG} ${enabled ? '已开启' : '已关闭'}`, STYLE); } export function isGlobalSkillDebugEnabled(): boolean { return _enabled; } function _log(message: string, ...args: unknown[]): void { if (!_enabled) return; let extra = ''; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a !== null && typeof a === 'object') continue; extra += ` ${String(a)}`; } console.log(`%c${TAG} ${message}${extra}`, STYLE); } export interface GlobalSkillSnapshot { skillIds: number[]; skillBrief: string[]; /** base_attr 各类型万分比合计 */ attrWan: Record; /** 已部署塔战斗参数 */ towers: Array<{ entityId: number; weapon1Id: number; attack: number; critRate: number; critDamage: number; baseCd: number; finalCd: number; }>; /** type8 减速后移速乘数 */ monsterSlowMul: number; /** type9 紫权重乘数 */ purpleWeightMul: number; /** type10 英雄刷新权重乘数 */ heroWeightMul: number; /** type11 格子刷新权重乘数 */ gridWeightMul: number; } const ATTR_LABELS: Record = { 1: '全局伤害', 3: '全局暴击', 4: '全局暴伤', 5: '全局冷却', 8: '怪物减速', 9: '技能概率(紫)', 10: '英雄概率', 11: '格子概率', }; function _attrWanMap(skills: readonly ITableSkill[]): Record { const out: Record = {}; for (const type of [1, 3, 4, 5, 8, 9, 10, 11]) { const wan = sumSelectedBaseAttrWan(skills, type); if (wan > 0) { out[`${type}:${ATTR_LABELS[type] ?? type}`] = wan; } } return out; } function _towerRows(session: BattleSession, playerId: number): GlobalSkillSnapshot['towers'] { const world = session.world; const attrs = session.attributes; const loadoutSvc = getTowerLoadoutService(); const rows: GlobalSkillSnapshot['towers'] = []; world.forEachAlive((id) => { const team = world.getComponent(id, TeamComp); if (!team || team.ownerId !== playerId || BattleTeamId.isEnemyTeam(team.team)) return; const loadout = loadoutSvc.getLoadout(id); if (!loadout) return; const w1 = gg.data.table.getTableData(TableNames.Hero, loadout.weapon1Id); const baseCd = 0; rows.push({ entityId: id as number, weapon1Id: loadout.weapon1Id, attack: attrs.getAttr(id, AttrId.Attack), critRate: attrs.getAttr(id, AttrId.CritRate), critDamage: attrs.getAttr(id, AttrId.CritDamage), baseCd, finalCd: baseCd > 0 ? attrs.getFinalCooldown(id, baseCd) : 0, }); }); return rows; } /** 采集当前已选词条 + 塔属性 + 全局倍率快照 */ export function captureGlobalSkillSnapshot( session: BattleSession, player: Player, ): GlobalSkillSnapshot { const skills = player.selectedSkills; const slowWan = sumSelectedBaseAttrWan(skills, 8); return { skillIds: skills.map((s) => s.id), skillBrief: skills.map((s) => `${s.id}:${s.name}${isGlobalSkillEntry(s) ? '(全局)' : ''}`), attrWan: _attrWanMap(skills), towers: _towerRows(session, player.playerId), monsterSlowMul: Math.max(0, 1 - slowWan / 10000), purpleWeightMul: wanToWeightMul(sumSelectedBaseAttrWan(skills, 9)), heroWeightMul: wanToWeightMul(sumSelectedBaseAttrWan(skills, 10)), gridWeightMul: wanToWeightMul(sumSelectedBaseAttrWan(skills, 11)), }; } function _fmtTower(t: GlobalSkillSnapshot['towers'][0]): string { return `塔#${t.entityId} W1=${t.weapon1Id} atk=${t.attack.toFixed(1)} cri=${(t.critRate * 100).toFixed(1)}% criD=${(t.critDamage * 100).toFixed(1)}% cd=${t.baseCd.toFixed(2)}→${t.finalCd.toFixed(2)}`; } function _diffTowers( before: GlobalSkillSnapshot['towers'], after: GlobalSkillSnapshot['towers'], ): void { const beforeMap = new Map(before.map((t) => [t.entityId, t])); for (const a of after) { const b = beforeMap.get(a.entityId); if (!b) { _log(` [新塔] ${_fmtTower(a)}`); continue; } const changed = Math.abs(a.attack - b.attack) > 0.01 || Math.abs(a.critRate - b.critRate) > 1e-6 || Math.abs(a.critDamage - b.critDamage) > 1e-6 || Math.abs(a.finalCd - b.finalCd) > 1e-4; if (!changed) continue; _log( ` 塔#${a.entityId} W1=${a.weapon1Id}`, `\n atk ${b.attack.toFixed(1)} → ${a.attack.toFixed(1)}`, `\n cri ${(b.critRate * 100).toFixed(1)}% → ${(a.critRate * 100).toFixed(1)}%`, `\n criD ${(b.critDamage * 100).toFixed(1)}% → ${(a.critDamage * 100).toFixed(1)}%`, `\n cd ${b.finalCd.toFixed(2)} → ${a.finalCd.toFixed(2)} (base ${a.baseCd.toFixed(2)})`, ); } } /** * 打印选技能前后对比。 * 开关:setGlobalSkillDebugEnabled(true) */ export function logGlobalSkillPickCompare( before: GlobalSkillSnapshot, after: GlobalSkillSnapshot, picked: readonly ITableSkill[], ): void { if (!_enabled) return; _log('──────── 选技能前后对比 ────────'); _log('本次选择:', picked.map((s) => `${s.id}:${s.name} base_attr=${s.base_attr || '-'}`).join(' | ')); _log('已选列表 前:', before.skillBrief.join(', ') || '(空)'); _log('已选列表 后:', after.skillBrief.join(', ') || '(空)'); _log(`base_attr万分比 前: ${JSON.stringify(before.attrWan)}`); _log(`base_attr万分比 后: ${JSON.stringify(after.attrWan)}`); _log( '全局倍率', `\n 怪物移速 ${before.monsterSlowMul.toFixed(4)} → ${after.monsterSlowMul.toFixed(4)}`, `\n 紫权重× ${before.purpleWeightMul.toFixed(4)} → ${after.purpleWeightMul.toFixed(4)}`, `\n 英雄权重× ${before.heroWeightMul.toFixed(4)} → ${after.heroWeightMul.toFixed(4)}`, `\n 格子权重× ${before.gridWeightMul.toFixed(4)} → ${after.gridWeightMul.toFixed(4)}`, ); if (before.towers.length === 0 && after.towers.length === 0) { _log('场上无已部署塔(全局属性将在部署后物化)'); } else { _log('塔属性变化:'); _diffTowers(before.towers, after.towers); } _log('────────────────────────────'); } // 控制台:setGlobalSkillDebugEnabled(true) / false try { (globalThis as { setGlobalSkillDebugEnabled?: typeof setGlobalSkillDebugEnabled }) .setGlobalSkillDebugEnabled = setGlobalSkillDebugEnabled; } catch { /* ignore */ }