feat(skill-system): 新增计时debuff机制,支持技能命中敌方附加减益效果

本次提交完整实现了技能对敌附加debuff的完整流程:
1.  新增TimedDebuffSnapshot数据结构,用于透传debuff配置
2.  在SkillConfig和SkillOverrides中新增debuff相关配置项
3.  实现HeroAtkSystem中debuff的判定与施加逻辑
4.  完善后台配置、前端编辑器的debuff配置支持
5.  调整了buff目标判定逻辑,修复了原有默认目标的语义混淆问题
6.  优化了BuffSystem的调试日志,新增了debuff分类标识
7.  批量更新了现有英雄技能的debuff配置示例
This commit is contained in:
pan
2026-09-08 16:37:13 +08:00
parent f5908a9adf
commit be17d18ff3
16 changed files with 214 additions and 124 deletions

View File

@@ -31,10 +31,9 @@
"lv": 2,
"s_lv": 2,
"overrides": {
"timed_buff_id": 7012,
"buff_value": 30,
"buff_chance": 100,
"buff_duration": 5
"timed_debuff_id": 7007,
"debuff_value": -10,
"debuff_chance": 100
},
"desc": "普通攻击造成 120% 攻击伤害"
}
@@ -146,7 +145,7 @@
"cdExpr": null,
"overrides": {
"ap": 150,
"timed_buff_id": 7002,
"timed_debuff_id": 7002,
"icon": "Stat_AttackSpeed_02"
},
"desc": "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)",
@@ -435,11 +434,11 @@
"lv": 2,
"s_lv": 2,
"overrides": {
"timed_buff_id": 7007,
"buff_value": 30,
"buff_chance": 30
"timed_debuff_id": 7007,
"debuff_value": 30,
"debuff_chance": 30
},
"desc": "普通攻击有{buff_chance}%概率使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)"
"desc": "普通攻击有{debuff_chance}%概率使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)"
}
]
},
@@ -458,20 +457,20 @@
"s_lv": 2,
"overrides": {
"ap": 250,
"timed_buff_id": 7007,
"buff_value": 40
"timed_debuff_id": 7007,
"debuff_value": 40
},
"desc": "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)"
"desc": "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)"
},
{
"lv": 16,
"s_lv": 3,
"overrides": {
"ap": 350,
"timed_buff_id": 7007,
"buff_value": 60
"timed_debuff_id": 7007,
"debuff_value": 60
},
"desc": "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)"
"desc": "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)"
}
]
}
@@ -1479,11 +1478,11 @@
"lv": 2,
"s_lv": 2,
"overrides": {
"timed_buff_id": 7005,
"buff_value": 30,
"buff_chance": 30
"timed_debuff_id": 7005,
"debuff_value": 30,
"debuff_chance": 30
},
"desc": "普通攻击有{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)"
"desc": "普通攻击有{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)"
}
]
},
@@ -1494,12 +1493,12 @@
"cdExpr": null,
"overrides": {
"ap": 150,
"timed_buff_id": 7005,
"buff_value": 30,
"buff_chance": 50,
"timed_debuff_id": 7005,
"debuff_value": 30,
"debuff_chance": 50,
"icon": "4-Apply-Poison"
},
"desc": "毒箭如雨,对{target}造成攻击力{ap}%伤害,{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)",
"desc": "毒箭如雨,对{target}造成攻击力{ap}%伤害,{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)",
"lv_entries": [
{
"lv": 8,
@@ -1534,11 +1533,11 @@
"t_num": 3,
"overrides": {
"ap": 80,
"timed_buff_id": 7005,
"buff_value": 40,
"buff_chance": 100
"timed_debuff_id": 7005,
"debuff_value": 40,
"debuff_chance": 100
},
"desc": "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)"
"desc": "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)"
}
]
},
@@ -1726,10 +1725,10 @@
"lv": 2,
"s_lv": 2,
"overrides": {
"timed_buff_id": 7008,
"buff_chance": 30
"timed_debuff_id": 7008,
"debuff_chance": 30
},
"desc": "普通攻击有{buff_chance}%概率使目标破甲(防御降低,持续{dur}秒)"
"desc": "普通攻击有{debuff_chance}%概率使目标破甲(防御降低,持续{dur}秒)"
}
]
},
@@ -1740,7 +1739,7 @@
"cdExpr": null,
"overrides": {
"ap": 150,
"timed_buff_id": 7008
"timed_debuff_id": 7008
},
"desc": "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)",
"lv_entries": [
@@ -1775,7 +1774,7 @@
"t_num": 1,
"overrides": {
"ap": 150,
"timed_buff_id": 7008
"timed_debuff_id": 7008
},
"desc": "战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒)"
}

View File

@@ -87,7 +87,7 @@
## 6. overrides 白名单与特殊存储格式
可用键(对照 `SkillSet.ts` 的 `SkillOverrides` 接口;编辑器 UI 白名单 `OVERRIDE_KEYS` 是其子集,手动改 JSON 可用全集):
`ap, hit_count, hitcd, crt, frz, stun, para, bck, bck_chance, TGroup, buff_type, timed_buff_id, buff_value, buff_duration, buff_chance, buff_target, call_hero, summon_count, is_accel, num, revive_hp, revive_count, proc_skill, icon`
`ap, hit_count, hitcd, crt, frz, stun, para, bck, bck_chance, TGroup, buff_type, timed_buff_id, buff_value, buff_duration, buff_chance, buff_target, timed_debuff_id, debuff_value, debuff_duration, debuff_chance, call_hero, summon_count, is_accel, num, revive_hp, revive_count, proc_skill, icon`
特殊存储格式(导出时由 server 还原为 TS 枚举表达式):
- `TGroup`:JSON 存字符串(如 `"Team"`),导出还原 `TGroup.Team`;`null` 表示不覆写;
@@ -96,27 +96,35 @@
- `icon`:字符串图标帧名(uicons 图集),非数值;
- 其余一律直存数值。
引用校验:`timed_buff_id` 必须 ∈ `BuffList`;`proc_skill.s_uuid` / 触发组 key / 召唤类引用必须 ∈ `SkillSet`。
引用校验:`timed_buff_id` / `timed_debuff_id` 必须 ∈ `BuffList`;`proc_skill.s_uuid` / 触发组 key / 召唤类引用必须 ∈ `SkillSet`。
### 6.1 timed_buff_id(计时 buff 注入)
### 6.1 timed_buff_id / timed_debuff_id(计时 buff / debuff 注入)
**编辑器 UI**:overrides 添加 `timed_buff_id 计时buff` 行后,值栏为 BuffList 下拉(`id 名称` + 右侧 buff 效果摘要,数据来自 `/api/meta` 的 `BuffList`);meta 未加载时可手输数字 id(allow-create,保存时自动归一化为数字)。选定 buff id 后编辑器自动补齐 `buff_value` / `buff_chance` / `buff_duration` 三行(已存在不重复),数值预填 BuffList 默认值。
**语义划分(新规则)**:
- `timed_buff_id`:**增益语义**。释放/命中时给友方或自身挂 buff;伤害技能缺省挂**施法者自身**(如吸血、攻击后强化),辅助技能缺省挂**友方目标**。可用 `buff_target` 覆写目标。
- `timed_debuff_id`:**减益语义**。命中敌人后给**被命中者**挂 debuff(目标固定,不可覆写);仅伤害路径(HeroAtkSystem)结算,DoT 不触发。
**编辑器 UI**:overrides 添加 `timed_buff_id 计时buff` 或 `timed_debuff_id 计时debuff` 行后,值栏为 BuffList 下拉(`id 名称` + 右侧 buff 效果摘要,数据来自 `/api/meta` 的 `BuffList`);meta 未加载时可手输数字 id(allow-create,保存时自动归一化为数字)。选定 id 后编辑器自动补齐配套三行(buff 补 `buff_value`/`buff_chance`/`buff_duration`,debuff 补 `debuff_value`/`debuff_chance`/`debuff_duration`,已存在不重复),数值预填 BuffList 默认值。
**配套键**(同一 overrides 内按需添加):
| 键 | 说明 | 缺省 |
|---|---|---|
| `buff_value` | 覆写修饰数值(modifiers.value / tick.damage_or_heal,单位同 BuffList) | 回退 BuffList 默认 |
| `buff_duration` | 覆写持续时间(秒) | 回退 BuffList.duration |
| `buff_chance` | 触发概率 %(命中时掷骰) | 100(必触发) |
| `buff_target` | 施加目标:0 被命中者(Hit) / 1 施法者(Self) / 2 全体友方(Ally) / 3 全体敌方(Enemy) | 按主效果推导:伤害→被命中者,辅助→友方 |
| `buff_value` | 覆写 buff 修饰数值(modifiers.value / tick.damage_or_heal,单位同 BuffList) | 回退 BuffList 默认 |
| `buff_duration` | 覆写 buff 持续时间(秒) | 回退 BuffList.duration |
| `buff_chance` | buff 触发概率 %(命中时掷骰) | 100(必触发) |
| `buff_target` | buff 施加目标:0 被命中者(Hit) / 1 施法者(Self) / 2 全体友方(Ally) / 3 全体敌方(Enemy) | 按主效果推导:伤害→施法者自身,辅助→友方目标 |
| `debuff_value` | 覆写 debuff 修饰数值 | 回退 BuffList 默认 |
| `debuff_duration` | 覆写 debuff 持续时间(秒) | 回退 BuffList.duration |
| `debuff_chance` | debuff 触发概率 %(命中时掷骰) | 100(必触发) |
**生效路径(重要)**:
- ✅ 友方/辅助技能(TGroup 为 Self/Team/Ally,或 6300/6500 段辅助底座):走 SCastSystem merged config,overrides 注入完整生效;`buff_target: 3`(Enemy)可把 buff 分流挂到敌方网格,实现对敌 debuff;
- ✅ 对敌技能的命中附带 debuff:以 SkillSet **底座配置**为准(HeroAtkSystem 按 `SkillSet[s_uuid]` 读取 `timed_buff_id`/`buff_chance` 等),对敌 debuff 写入底座即可起效;
- ✅ 友方/辅助技能(TGroup 为 Self/Team/Ally,或 6300/6500 段辅助底座):走 SCastSystem merged config,`timed_buff_id` 注入完整生效;`buff_target: 3`(Enemy)可把 buff 分流挂到敌方网格(如群体减甲);
- ✅ 对敌伤害技能的命中附带 debuff:用 `timed_debuff_id`(可写在 SkillSet 底座或 heroSet overrides),HeroAtkSystem 命中结算时挂被命中者;
- ✅ 对敌伤害技能的命中附带增益(吸血/自强化):用 `timed_buff_id`,缺省挂施法者自身;
- 同一 overrides 不要既配 `timed_buff_id` 又依赖永久 `buff_type` 加成:timed 路径生效后永久分支被跳过。
**语义陷阱**:`buff_target` 缺省为被命中者——若想"攻击回血/吸血",必须显式配 `"buff_target": 1`,否则 buff 挂到被打的敌人身上(如 7010 回血会奶敌人)。
**语义陷阱**:`timed_buff_id` 在伤害技能上缺省挂**施法者自身**——若想"命中给敌人挂减益",必须用 `timed_debuff_id`,否则会把减益挂到自己身上。
### 6.2 proc_skill(命中概率触发主动技能)
@@ -166,5 +174,5 @@
- 普攻用数值 cd 而不给 `cdExpr` → 丢失攻速档位语义(编辑器攻速下拉失效);
- 档位 overrides 全量复制基础档 → 浅合并下冗余且后续改基础值不生效;
- desc 与 overrides 数值脱节 → desc 模板只写 `{ap}` 等占位符,不要硬编码数字;
- 对敌技能 debuff 写在 overrides → 命中附带判定按 SkillSet 底座读取,应写入底座配置(详见 §6.1);
- 想做吸血但漏配 `buff_target:1` → 缺省挂被命中者,回血 buff 会奶敌人。
- 对敌技能 debuff 误用 `timed_buff_id` → buff 缺省挂施法者自身,会把减益挂到自己身上;应改用 `timed_debuff_id`(详见 §6.1);
- 想做吸血但把 buff 写到 `timed_debuff_id` → debuff 目标固定为被命中者,会奶敌人;吸血/自强化请用 `timed_buff_id`(缺省即挂自身)。

View File

@@ -257,10 +257,17 @@ function overrideShort(ov, { includeAp = true } = {}) {
const buffName = cache?.BuffList?.[ov.timed_buff_id]?.name || ''
const val = ov.buff_value != null ? `${ov.buff_value}` : ''
const chance = ov.buff_chance != null ? `buff_chance${ov.buff_chance}` : ''
// buff_target 非默认(被命中者)时标注目标,便于识别"挂自己/挂敌方"等跨阵营 buff
const tgtLabel = ov.buff_target != null && ov.buff_target !== 0 ? `→${BUFF_TARGET_NAMES[ov.buff_target]}` : ''
// buff_target 非默认(施法者自身)时标注目标,便于识别"挂敌方/挂队友"等跨阵营 buff
const tgtLabel = ov.buff_target != null && ov.buff_target !== 1 ? `→${BUFF_TARGET_NAMES[ov.buff_target]}` : ''
parts.push(`附加${val}${buffName}${ov.timed_buff_id}${chance}${tgtLabel}`)
}
// timed_debuff_id:命中被攻击者后施加(目标固定,无需标注)
if (ov.timed_debuff_id != null) {
const debuffName = cache?.BuffList?.[ov.timed_debuff_id]?.name || ''
const val = ov.debuff_value != null ? `${ov.debuff_value}` : ''
const chance = ov.debuff_chance != null ? `debuff_chance${ov.debuff_chance}` : ''
parts.push(`命中附加${val}${debuffName}${ov.timed_debuff_id}${chance}`)
}
// proc_skill:命中后概率触发额外技能,标注被触发技能名/概率/覆写 ap(若有)
if (ov.proc_skill?.s_uuid != null) {
const procName = cache?.SkillSet?.[ov.proc_skill.s_uuid]?.name || ''

View File

@@ -280,6 +280,9 @@ const OV_LABELS = {
timed_buff_id: 'buff',
buff_value: 'val',
buff_chance: 'chance',
timed_debuff_id: 'debuff',
debuff_value: 'dval',
debuff_chance: 'dchance',
summon_count: '数',
thorns_dmg: 'thorns',
thorns_rate: 'thorns',

View File

@@ -55,7 +55,7 @@
</div>
</div>
</template>
<!-- buff 型覆写(timed_buff_id):BuffList 下拉,副文本展示 buff info;allow-create 兼容 meta 未加载时手输 id -->
<!-- buff/debuff 型覆写(timed_buff_id/timed_debuff_id):BuffList 下拉,副文本展示 buff info;allow-create 兼容 meta 未加载时手输 id -->
<el-select
v-else-if="isBuffKey(item.key)"
v-model="item.value"
@@ -81,8 +81,8 @@
:closable="false"
show-icon
style="margin-bottom: 6px"
title="对敌技能 debuff 配置提示"
description="对敌技能的命中附带 debuff 以 SkillSet 底座配置为准,此处 overrides 注入建议用于友方/辅助技能(可用 buff_target 分流挂敌);吸血类需 buff_target=1(施法者自身)。proc_skill 用于命中后概率触发主动技能,不是挂 buff 的方式。"
title="buff/debuff 配置提示"
description="buff 为增益语义:缺省挂施法者自身(如吸血/攻击后强化),可用 buff_target 改为全体友方;debuff 为对敌减益语义:缺省挂被命中者(目标不可覆写),对敌技能请用 timed_debuff_id。proc_skill 用于命中后概率触发主动技能,不是挂 buff 的方式。"
/>
<el-button size="small" @click="add">+ 添加覆盖</el-button>
</div>
@@ -197,22 +197,24 @@ function onBuffValueChange(item) {
if (typeof item.value === 'string' && item.value.trim() !== '' && !isNaN(Number(item.value))) {
item.value = Number(item.value)
}
ensureBuffCompanionRows(item.value)
ensureBuffCompanionRows(item.value, item.key === 'timed_debuff_id')
}
// 挂载 timed_buff_id 后自动补齐配套三键(已存在的不重复添加):
// 挂载 timed_buff_id / timed_debuff_id 后自动补齐配套三键(已存在的不重复添加):
// 数值预填 BuffList 默认值(modifiers[0].value / tick.damage_or_heal / duration),避免导出 0 值覆盖 buff 语义
function ensureBuffCompanionRows(buffId) {
// isDebuff=true 时补 debuff_* 键,否则补 buff_* 键
function ensureBuffCompanionRows(buffId, isDebuff = false) {
const buff = typeof buffId === 'number' ? props.buffOptions.find((b) => b.id === buffId) : null
const prefix = isDebuff ? 'debuff' : 'buff'
const has = (key) => rows.value.some((r) => r.key === key)
if (!has('buff_value')) {
rows.value.push({ key: 'buff_value', value: buff?.modifiers?.[0]?.value ?? buff?.tick?.damage_or_heal ?? 0 })
if (!has(`${prefix}_value`)) {
rows.value.push({ key: `${prefix}_value`, value: buff?.modifiers?.[0]?.value ?? buff?.tick?.damage_or_heal ?? 0 })
}
if (!has('buff_chance')) {
rows.value.push({ key: 'buff_chance', value: 100 })
if (!has(`${prefix}_chance`)) {
rows.value.push({ key: `${prefix}_chance`, value: 100 })
}
if (!has('buff_duration')) {
rows.value.push({ key: 'buff_duration', value: buff?.duration ?? 5 })
if (!has(`${prefix}_duration`)) {
rows.value.push({ key: `${prefix}_duration`, value: buff?.duration ?? 5 })
}
}

View File

@@ -109,19 +109,25 @@ export const OVERRIDE_KEYS = [
{ key: 'bck', label: 'bck 击退' },
{ key: 'bck_chance', label: 'bck_chance 击退概率' },
// buff 型覆写:值为 BuffList 配置 id,前端用 meta.BuffList 渲染下拉(支持 allow-create 手输兜底)
// buff 语义为增益,缺省挂施法者自身;debuff 语义为对敌减益,缺省挂被命中者(目标不可覆写)
{ key: 'timed_buff_id', label: 'timed_buff_id 计时buff', type: 'buff' },
{ key: 'buff_value', label: 'buff_value 修饰数值' },
{ key: 'buff_duration', label: 'buff_duration 持续时间' },
{ key: 'buff_chance', label: 'buff_chance 附加概率' },
{ key: 'thorns_dmg', label: 'thorns_dmg 反伤固定值' },
{ key: 'thorns_rate', label: 'thorns_rate 反伤比例(%)' },
// 枚举型覆写:计时 buff 施加目标(缺省跟随技能主效果目标,见 SkillSet.BuffTarget)
// 枚举型覆写:计时 buff 施加目标(缺省按技能主效果推导:伤害→施法者自身,辅助→友方目标)
{ key: 'buff_target', label: 'buff_target buff施加目标', type: 'enum', options: [
{ value: 0, label: '0 被命中者(Hit/默认)' },
{ value: 1, label: '1 施法者自身(Self)' },
{ value: 0, label: '0 被命中者(Hit)' },
{ value: 1, label: '1 施法者自身(Self/默认)' },
{ value: 2, label: '2 全体友方(Ally)' },
{ value: 3, label: '3 全体敌方(Enemy)' },
]},
// debuff 型覆写:值为 BuffList 配置 id,命中被攻击者后施加;目标固定为被命中者,不可覆写
{ key: 'timed_debuff_id', label: 'timed_debuff_id 计时debuff', type: 'buff' },
{ key: 'debuff_value', label: 'debuff_value 修饰数值' },
{ key: 'debuff_duration', label: 'debuff_duration 持续时间' },
{ key: 'debuff_chance', label: 'debuff_chance 附加概率' },
// 枚举型覆写:技能目标群体(JSON 存字符串,导出还原 TGroup.X;proc_skill.overrides 可借此改变被触发技能的释放对象)
// 注:不提供 All —— SCastSystem 未对 TGroup.All 做友/敌分流(会落到敌方分支),避免误配
{ key: 'TGroup', label: 'TGroup 目标群体', type: 'enum', options: [