From f0f9cf7499b2fd65da3b0e8b52eac70e7fb5632e Mon Sep 17 00:00:00 2001 From: humingxing <930621236@qq.com> Date: Thu, 15 Oct 2026 11:32:38 +0800 Subject: [PATCH] =?UTF-8?q?buff=E5=92=8C=E6=8A=80=E8=83=BD=E8=A1=A8?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../battle/combat/BattleBuffDamageHook.ts | 36 +-- .../battle/combat/PassiveSkillRuntime.ts | 76 ++++- docs/方案-Battlebuff扩展105-112.md | 8 + preview-template/workbench/css/workbench.css | 12 + preview-template/workbench/js/agent-assets.js | 275 +++++++++++++----- .../workbench/js/panels/assets.js | 7 +- 6 files changed, 309 insertions(+), 105 deletions(-) diff --git a/assets/script/battle/combat/BattleBuffDamageHook.ts b/assets/script/battle/combat/BattleBuffDamageHook.ts index e831841..308f252 100644 --- a/assets/script/battle/combat/BattleBuffDamageHook.ts +++ b/assets/script/battle/combat/BattleBuffDamageHook.ts @@ -1,10 +1,9 @@ +import type { BattleSession } from 'db://assets/mx/module/battle'; import type { AbilityContext } from 'db://assets/mx/module/battle/combat/AbilityContext'; import { setDamagePreCalcHook, type DamagePreCalcState, } from 'db://assets/mx/module/battle/combat/DamagePipeline'; -import { AttributeComp } from 'db://assets/mx/module/battle/ecs/Components'; -import type { EntityId } from 'db://assets/mx/module/ecs'; import { getBattleBuffDigitStatHost } from './BattleBuffDigitStatHost'; import { getPassiveSkillHost } from './PassiveSkillRuntime'; @@ -32,26 +31,7 @@ function listHasSkill(ids: readonly number[], skillId: number): boolean { } /** - * 目标是否处于减速(move_speed_mul<1 或 debuff_3 / slow 标签)。 - * @param ctx 伤害上下文 - * @param target 目标 - */ -function targetIsSlowed(ctx: AbilityContext, target: EntityId): boolean { - const world = ctx.services.world; - const attr = world.getComponent(target, AttributeComp); - if (attr) { - for (let i = 0; i < attr.modifiers.length; i++) { - const m = attr.modifiers[i]; - if (m.attr === 'move_speed_mul' && m.op === 'Mul' && m.value < 1) return true; - } - } - if (ctx.services.buffs.hasTag(target, 'debuff_3')) return true; - if (ctx.services.buffs.hasTag(target, 'slow')) return true; - return false; -} - -/** - * 局内数字 buff 伤害预调:107/110/111/112。 + * 局内数字 buff + 被动 condition7/8 result21 伤害预调。 */ export class BattleBuffDamageHook { /** 注册到 DamagePipeline */ @@ -72,6 +52,12 @@ export class BattleBuffDamageHook { * @param state 可调状态 */ static apply(ctx: AbilityContext, params: Record, state: DamagePreCalcState): void { + const session = ctx.services as unknown as BattleSession; + const target = ctx.targets[0] ?? 0; + if (target > 0) { + getPassiveSkillHost().applyHitDamageMods(session, ctx.caster, target, state); + } + const flags = getBattleBuffDigitStatHost().getFlags(ctx.caster); if (!flags) return; const skillId = parseSkillIdFromAbilityId(ctx.abilityId); @@ -100,7 +86,6 @@ export class BattleBuffDamageHook { } } - const target = ctx.targets[0]; if (!(target > 0)) return; const mode = flags.condCrit; if (!mode) return; @@ -109,12 +94,13 @@ export class BattleBuffDamageHook { state.critRateToCritDmg = true; return; } + const host = getPassiveSkillHost(); if (mode === 'poison') { - if (getPassiveSkillHost().hasPoison(target)) state.forceCrit = true; + if (host.hasPoison(target)) state.forceCrit = true; return; } if (mode === 'slow') { - if (targetIsSlowed(ctx, target)) state.forceCrit = true; + if (host.isTargetSlowed(session, target)) state.forceCrit = true; } } } diff --git a/assets/script/battle/combat/PassiveSkillRuntime.ts b/assets/script/battle/combat/PassiveSkillRuntime.ts index ea54fc8..e44ee10 100644 --- a/assets/script/battle/combat/PassiveSkillRuntime.ts +++ b/assets/script/battle/combat/PassiveSkillRuntime.ts @@ -56,6 +56,10 @@ export enum PassiveConditionKind { OnKill = 5, /** 技能释放次数(含普攻 AbilityCast),参数:次数 */ SkillCastCount = 6, + /** 攻击中毒单位造成伤害(伤害预调判定) */ + OnPoisonedHit = 7, + /** 攻击减速单位造成伤害(伤害预调判定) */ + OnSlowedHit = 8, } /** 被动触发结果类型 */ @@ -100,6 +104,8 @@ export enum PassiveResultKind { BehindFan = 19, /** 击杀叠伤害:加成万分比,最大叠加次数 */ KillDmgStack = 20, + /** 该次伤害必定暴击,暴击率转化成暴击伤害 */ + ForceCritConvert = 21, /** 空 result:按行 atk_type 施放一次 */ CastAtkRow = 0, } @@ -646,6 +652,61 @@ export class PassiveSkillHost { return false; } + /** + * 目标是否减速(move_speed_mul<1 或 debuff_3/slow)。 + * @param session 会话 + * @param target 目标 + */ + isTargetSlowed(session: BattleSession, target: EntityId): boolean { + if (!(target > 0) || !session.world.isAlive(target)) return false; + const attr = session.world.getComponent(target, AttributeComp); + if (attr) { + for (let i = 0; i < attr.modifiers.length; i++) { + const m = attr.modifiers[i]; + if (m.attr === 'move_speed_mul' && m.op === 'Mul' && m.value < 1) return true; + } + } + if (session.buffs.hasTag(target, 'debuff_3')) return true; + if (session.buffs.hasTag(target, 'slow')) return true; + return false; + } + + /** + * 伤害预调:condition 7/8 + result 21(该次伤害必暴+暴击转爆伤)。 + * @param session 会话 + * @param caster 伤害来源 + * @param target 受击目标 + * @param state 伤害预调状态 + */ + applyHitDamageMods( + session: BattleSession, + caster: EntityId, + target: EntityId, + state: { forceCrit: boolean; critRateToCritDmg: boolean }, + ): void { + if (!(target > 0)) return; + const idx = this._indexOf(caster); + if (idx < 0) return; + const list = this._binds[idx].list; + for (let i = 0; i < list.length; i++) { + const b = list[i]; + if (b.resultKind !== PassiveResultKind.ForceCritConvert) continue; + if (b.conditionKind === PassiveConditionKind.OnPoisonedHit) { + if (this.hasPoison(target)) { + state.forceCrit = true; + state.critRateToCritDmg = true; + } + continue; + } + if (b.conditionKind === PassiveConditionKind.OnSlowedHit) { + if (this.isTargetSlowed(session, target)) { + state.forceCrit = true; + state.critRateToCritDmg = true; + } + } + } + } + /** * 查询实体已绑定被动。 * @param entityId 塔实体 @@ -796,9 +857,17 @@ export class PassiveSkillHost { } } - /** 命中判定 condition 1/4 */ + /** 命中判定 condition 1/4;7/8+result21 在伤害预调处理,此处跳过 */ private _tryOnHit(session: BattleSession, caster: EntityId, target: EntityId, bind: PassiveSkillBind, amount: number): boolean { const kind = bind.conditionKind; + if (kind === PassiveConditionKind.OnPoisonedHit || kind === PassiveConditionKind.OnSlowedHit) { + if (bind.resultKind === PassiveResultKind.ForceCritConvert) return false; + if (bind.cdLeft > 0) return false; + if (kind === PassiveConditionKind.OnPoisonedHit && !this.hasPoison(target)) return false; + if (kind === PassiveConditionKind.OnSlowedHit && !this.isTargetSlowed(session, target)) return false; + this._fire(session, caster, target, bind, amount); + return true; + } if (kind === PassiveConditionKind.OnDealDamage) { if (bind.cdLeft > 0) return false; if (!this._rollWan(session, bind.conditionParams[0], bind.skillId)) return false; @@ -978,6 +1047,9 @@ export class PassiveSkillHost { case PassiveResultKind.KillDmgStack: this._applyKillDmgStack(session, caster, bind, p); return; + case PassiveResultKind.ForceCritConvert: + // 该次伤害修正在 applyHitDamageMods / 伤害预调中处理 + return; case PassiveResultKind.CastAtkRow: this._applyCastAtkRow(session, caster, target, bind); return; @@ -2216,7 +2288,7 @@ export class PassiveSkillHost { const condParts = cond.split(',').map(Number); const resultParts = result ? result.split(',').map(Number) : [PassiveResultKind.CastAtkRow]; const conditionKind = condParts[0] || 0; - if (conditionKind < 1 || conditionKind > 6) { + if (conditionKind < 1 || conditionKind > 8) { console.log(`[PassiveSkill] 未知 condition skill=${skillId} kind=${conditionKind}`); } return { diff --git a/docs/方案-Battlebuff扩展105-112.md b/docs/方案-Battlebuff扩展105-112.md index beddf93..0d79605 100644 --- a/docs/方案-Battlebuff扩展105-112.md +++ b/docs/方案-Battlebuff扩展105-112.md @@ -30,11 +30,19 @@ | 18 | 寒潭减速 | `PassiveSkillHost` 地面区 | | 19 | 目标身后扇形 | 向量扇形圈伤 | | 20 | 击杀叠伤 | `dmg_mul` 叠层 | +| 21 | 该次伤害必暴+暴击转爆伤 | 伤害预调(配合 condition 7/8) | 空 `result` + 有 `atk_type`:`CastAtkRow` 注册 `ability_buffskill_*` 后 `tryCast`(冰球术/穿云击/群体加速)。 `condition=2` 且 `cd>0`:部署触发后按 CD 循环(远攻剑气)。 +| condition | 含义 | +|----------:|------| +| 7 | 攻击中毒单位造成伤害(预调判定) | +| 8 | 攻击减速单位造成伤害(预调判定) | + +> 2026-10-15 导表:`2062/3062` 由 ft=112 改为 ft=105 挂 `206201/306201`(cond 7/8 + result 21)。`4052 霸王之气` 仍为 ft=112。 + ## 3. 代码入口 - `BattleBuffDigitStatHost`:101–112 刷新、挂 Skill、战斗标记 diff --git a/preview-template/workbench/css/workbench.css b/preview-template/workbench/css/workbench.css index d89f859..b2abd85 100644 --- a/preview-template/workbench/css/workbench.css +++ b/preview-template/workbench/css/workbench.css @@ -1127,6 +1127,18 @@ body { font-variant-numeric: tabular-nums; } +.pw-assets-tag { + display: inline-block; + margin-left: 4px; + padding: 0 4px; + border-radius: 3px; + font-size: 9px; + line-height: 14px; + background: rgba(47, 111, 237, 0.18); + color: var(--pw-info); + vertical-align: middle; +} + .pw-assets-side { width: 168px; flex-shrink: 0; diff --git a/preview-template/workbench/js/agent-assets.js b/preview-template/workbench/js/agent-assets.js index 0c9e8a0..8103db7 100644 --- a/preview-template/workbench/js/agent-assets.js +++ b/preview-template/workbench/js/agent-assets.js @@ -85,10 +85,49 @@ } /** - * 解析资源所属 Bundle。 - * 顺序:asset 上的 bundle 字段 → nativeUrl 路径 → bundle.getAssetInfo → cache.has + * 按构建「依赖打入分包」扩展归属: + * 包内资源引用到的、且尚未被其它包显式声明的资源,打入同一包。 + * 使用 assetManager.dependUtil.getDependsRecursively。 */ - function resolveBundleName(uuid, asset, bundleObjs, uuidMap) { + function expandOwnershipByDeps(cc, bi) { + var depUtil = cc.assetManager && cc.assetManager.dependUtil; + if (!depUtil || typeof depUtil.getDependsRecursively !== 'function') return; + + var uuidMap = bi.uuidMap || {}; + var depPacked = bi.depPacked || (bi.depPacked = {}); + var roots = []; + for (var u in uuidMap) { + if (Object.prototype.hasOwnProperty.call(uuidMap, u)) roots.push(u); + } + + for (var i = 0; i < roots.length; i++) { + var root = roots[i]; + var bname = uuidMap[root]; + if (!bname) continue; + var deps = null; + try { + deps = depUtil.getDependsRecursively(root); + } catch (e) { + try { + deps = depUtil.getDeps(root); + } catch (e2) { + deps = null; + } + } + if (!deps || !deps.length) continue; + for (var j = 0; j < deps.length; j++) { + var d = String(deps[j]); + if (!d || d === root) continue; + // 已被其它包显式占有则不抢(跨包引用) + if (uuidMap[d] && uuidMap[d] !== bname) continue; + if (!uuidMap[d]) { + uuidMap[d] = bname; + depPacked[d] = true; + } + } + } + } + function resolveBundleName(uuid, asset, bi) { try { if (asset) { if (asset.bundle && asset.bundle.name) return asset.bundle.name; @@ -96,54 +135,52 @@ if (asset.__bundleName) return asset.__bundleName; } } catch (e) { /* */ } - - if (uuidMap && uuid && uuidMap[uuid]) return uuidMap[uuid]; + if (bi && bi.uuidMap && uuid && bi.uuidMap[uuid]) return bi.uuidMap[uuid]; var url = (asset && (asset.nativeUrl || asset._nativeUrl)) || ''; var m = String(url).match(/\/([A-Za-z0-9_\-]+)\/(?:import|native|js|ohm|chunks)\//i); if (m && m[1]) return m[1]; - if (bundleObjs && bundleObjs.length) { - for (var i = 0; i < bundleObjs.length; i++) { - var b = bundleObjs[i]; - if (!b) continue; - var bname = b.__pwName || b.name || ''; - try { - if (typeof b.getAssetInfo === 'function' && uuid) { - var info = b.getAssetInfo(uuid); - if (info && (info.uuid || info.redirect || info.path != null || info.ext)) { - return bname; - } - } - } catch (e2) { /* */ } - try { - if (b.cache && typeof b.cache.has === 'function' && uuid && b.cache.has(uuid)) { - return bname; - } - } catch (e3) { /* */ } - try { - if (b.cache && b.cache.get && uuid && b.cache.get(uuid)) { - return bname; - } - } catch (e4) { /* */ } - } + var objs = (bi && bi.bundleObjs) || []; + for (var i = 0; i < objs.length; i++) { + var b = objs[i]; + if (!b) continue; + var bname = b.__pwName || b.name || ''; + try { + if (typeof b.getAssetInfo === 'function' && uuid) { + var info = b.getAssetInfo(uuid); + if (info && (info.uuid || info.redirect || info.path != null || info.ext)) return bname; + } + } catch (e2) { /* */ } + try { + if (b.cache && b.cache.has && uuid && b.cache.has(uuid)) return bname; + } catch (e3) { /* */ } } return ''; } - /** 遍历已加载 bundles,收集对象与 cache uuid 索引 */ + /** + * 构建索引:uuid→bundle、uuid→path、pack(自动图集)成员。 + * 数据来自 bundle._config(paths / packs)与 cache。 + */ function indexBundles(cc) { var list = []; var uuidMap = {}; + var pathMap = {}; + var packOf = {}; // uuid -> packId + var packMembers = {}; // packId -> [uuid] var bundleObjs = []; + + var addU = function (u, bname) { + if (u != null && bname) uuidMap[String(u)] = bname; + }; + try { cc.assetManager.bundles.forEach(function (bundle, name) { var n = name || (bundle && bundle.name) || ''; - if (!n) return; - if (bundle) { - bundle.__pwName = n; - bundleObjs.push(bundle); - } + if (!n || !bundle) return; + bundle.__pwName = n; + bundleObjs.push(bundle); list.push({ name: n, assetCount: 0, @@ -152,46 +189,108 @@ audioMB: 0, otherMB: 0, status: 'loaded', + packCount: 0, }); - var add = function (u) { - if (u) uuidMap[String(u)] = n; - }; + + var cfg = bundle._config; + + // 1) paths: 路径 → [uuid, type, ...] 或 {uuid} + try { + var paths = cfg && (cfg.paths || cfg._paths); + if (paths && typeof paths === 'object') { + for (var p in paths) { + if (!Object.prototype.hasOwnProperty.call(paths, p)) continue; + var val = paths[p]; + if (Array.isArray(val)) { + for (var i = 0; i < val.length; i++) { + var u1 = val[i]; + if (typeof u1 === 'string' && /^[0-9a-fA-F-]{8,}/.test(u1)) { + addU(u1, n); + if (!pathMap[u1]) pathMap[u1] = p; + } else if (u1 && u1.uuid) { + addU(u1.uuid, n); + if (!pathMap[u1.uuid]) pathMap[u1.uuid] = p; + } + } + } else if (val && val.uuid) { + addU(val.uuid, n); + if (!pathMap[val.uuid]) pathMap[val.uuid] = p; + } + } + } + } catch (e) { /* */ } + + // 2) packs: 自动图集 / 合包 + try { + var packs = cfg && (cfg.packs || cfg._packs); + if (packs && typeof packs === 'object') { + var packCount = 0; + for (var packId in packs) { + if (!Object.prototype.hasOwnProperty.call(packs, packId)) continue; + var members = packs[packId]; + if (!members) continue; + packCount += 1; + packMembers[packId] = packMembers[packId] || []; + var arr = Array.isArray(members) ? members : [members]; + for (var j = 0; j < arr.length; j++) { + var um = arr[j]; + if (um && typeof um === 'object') um = um.uuid; + if (!um) continue; + addU(um, n); + packOf[String(um)] = packId; + packMembers[packId].push(String(um)); + if (!pathMap[String(um)]) pathMap[String(um)] = packId; + } + } + for (var q = 0; q < list.length; q++) { + if (list[q].name === n) list[q].packCount = packCount; + } + } + } catch (e2) { /* */ } + + // 3) cache try { var cache = bundle.cache || bundle._cache; if (cache && typeof cache.forEach === 'function') { cache.forEach(function (asset, uuid) { - add(uuid); - if (asset) add(asset._uuid); + addU(uuid, n); + if (asset) addU(asset._uuid, n); }); } if (cache && cache._map) { for (var k in cache._map) { - if (Object.prototype.hasOwnProperty.call(cache._map, k)) add(k); + if (Object.prototype.hasOwnProperty.call(cache._map, k)) addU(k, n); } } - } catch (e) { /* */ } + } catch (e3) { /* */ } - // config.uuids / paths + // 4) getAssetInfo 扫一遍 config 里的 uuid try { - var cfg = bundle._config; - if (cfg) { - var lists = [cfg.uuids, cfg.uuidList, cfg._uuidList]; - for (var li = 0; li < lists.length; li++) { - var arr = lists[li]; - if (!arr) continue; - if (Array.isArray(arr)) { - for (var i = 0; i < arr.length; i++) add(arr[i]); - } else if (typeof arr === 'object') { - for (var kk in arr) { - if (Object.prototype.hasOwnProperty.call(arr, kk)) add(kk); + if (typeof bundle.getAssetInfo === 'function') { + var keys = Object.keys(uuidMap); + for (var s = 0; s < keys.length; s++) { + if (uuidMap[keys[s]] === n) continue; + try { + var inf = bundle.getAssetInfo(keys[s]); + if (inf && (inf.path != null || inf.redirect)) { + addU(keys[s], n); + if (!pathMap[keys[s]] && inf.path) pathMap[keys[s]] = inf.path; } - } + } catch (e4) { /* */ } } } - } catch (e2) { /* */ } + } catch (e5) { /* */ } }); - } catch (e3) { /* */ } - return { list: list, uuidMap: uuidMap, bundleObjs: bundleObjs }; + } catch (e6) { /* */ } + + return { + list: list, + uuidMap: uuidMap, + pathMap: pathMap, + packOf: packOf, + packMembers: packMembers, + bundleObjs: bundleObjs, + }; } /** @@ -220,11 +319,8 @@ var type = fmtType(asset); var mem = assetMem(asset, type, seenTex); - var bname = resolveBundleName(id, asset, bi.bundleObjs, bi.uuidMap); - if (!bname) { - var isDefault = /default_|splash|empty|builtin/i.test(String(asset.name || '')); - bname = isDefault ? 'internal' : 'internal'; - } + var bname = resolveBundleName(id, asset, bi); + if (!bname) bname = 'internal'; var agg = bundleByName[bname]; if (!agg) { agg = { @@ -235,6 +331,7 @@ audioMB: 0, otherMB: 0, status: 'loaded', + packCount: 0, }; bundleByName[bname] = agg; bi.list.push(agg); @@ -255,22 +352,38 @@ h = asset.height | 0; } + // 自动图集 / 合包:packId;SpriteFrame 挂到纹理 uuid + var packId = bi.packOf[id] || ''; + var texUuid = ''; + if (type === 'SpriteFrame') { + var tex = asset.texture || (asset.getTexture && asset.getTexture()); + texUuid = String((tex && (tex._uuid || tex.uuid)) || ''); + } else if (type === 'Texture2D' || type === 'Texture') { + texUuid = id; + } + var display = asset.name || ''; + var pathStr = bi.pathMap[id] || ''; if (!display || /^[0-9a-f-]{8,}$/i.test(display)) { - // 可读名:优先 getAssetInfo.path 最后一段 - try { - for (var bi2 = 0; bi2 < bi.bundleObjs.length; bi2++) { - var bb = bi2.bundleObjs[bi2]; - if (bb && typeof bb.getAssetInfo === 'function') { - var inf = bb.getAssetInfo(id); - if (inf && inf.path) { - var segs = String(inf.path).split('/'); - display = segs[segs.length - 1] || inf.path; - break; + if (pathStr) { + var segs = String(pathStr).split('/'); + display = segs[segs.length - 1] || pathStr; + } + if (!display) { + try { + for (var bi2 = 0; bi2 < bi.bundleObjs.length; bi2++) { + var bb = bi.bundleObjs[bi2]; + if (bb && typeof bb.getAssetInfo === 'function') { + var inf = bb.getAssetInfo(id); + if (inf && inf.path) { + var ps = String(inf.path).split('/'); + display = ps[ps.length - 1] || inf.path; + break; + } } } - } - } catch (eName) { /* */ } + } catch (eName) { /* */ } + } if (!display) display = id.slice(0, 8) + '…'; } @@ -284,6 +397,9 @@ w: w, h: h, kind: kind, + pack: packId, + texUuid: texUuid, + path: pathStr, }); agg.assetCount += 1; @@ -322,7 +438,14 @@ } totals.assetCount = rows.length; totals.bundleCount = bundles.length; - return { bundles: bundles, rows: rows, totals: totals }; + totals.packCount = Object.keys(bi.packMembers || {}).length; + return { + bundles: bundles, + rows: rows, + totals: totals, + packs: bi.packMembers || {}, + packOf: bi.packOf || {}, + }; } function buildPreview(asset, type) { diff --git a/preview-template/workbench/js/panels/assets.js b/preview-template/workbench/js/panels/assets.js index cac7896..a1f304d 100644 --- a/preview-template/workbench/js/panels/assets.js +++ b/preview-template/workbench/js/panels/assets.js @@ -200,9 +200,12 @@ } else if (r.type === 'ImageAsset') { memText = (r.w && r.h) ? (r.w + '×' + r.h) : '源像素'; } + var tag = ''; + if (r.pack) tag += ' 图集'; + if (r.type === 'Texture2D' && r.pack) tag += ' 合包'; out.push( - '
' + - '' + escapeHtml(r.name) + '' + + '
' + + '' + escapeHtml(r.name) + tag + '' + '' + escapeHtml(r.type) + '' + '' + escapeHtml(memText) + '' + '
'