feat(editor): 完善技能覆写编辑器功能,优化配置体验

1. 为OverrideEditor新增baseSkillUuid参数,支持对敌技能debuff配置校验
2. 优化proc_skill和timed_buff_id的编辑体验,自动补全关联配置项
3. 更新常量注释与英雄技能配置,修正overrides字段配置
4. 新增对敌技能buff配置警示提示,引导正确配置方式
This commit is contained in:
pan
2026-09-08 10:38:18 +08:00
parent 4d7e54f833
commit f5908a9adf
7 changed files with 119 additions and 14 deletions

View File

@@ -544,9 +544,9 @@ export const HeroInfo: Record<number, heroInfo> = {
5001: {
uuid: 5001, name: "叠甲骑士", path: "hk1", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: HERO_ROLE_BASE[HRole.Tank].hp + 20, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def + 10,
skills: {
6004: { uuid: 6004, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: {"ap":100,"timed_buff_id":7010,"buff_chance":50}, desc: "挥剑对{target}造成攻击力{ap}%伤害",
6004: { uuid: 6004, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: {"ap":100}, desc: "挥剑对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120}, desc: "普通攻击造成 120% 攻击伤害" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7012,"buff_value":30,"buff_chance":100,"buff_duration":5}, desc: "普通攻击造成 120% 攻击伤害" },
],
},
6301: { uuid: 6301, name: "魔法盾", lv: 1, cd: 8, ccd: 0, overrides: {"ap":30}, desc: "{target}获得生命最大值{ap}%的护盾",

View File

@@ -23,9 +23,7 @@
"cd": 1.2,
"cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd",
"overrides": {
"ap": 100,
"timed_buff_id": 7010,
"buff_chance": 50
"ap": 100
},
"desc": "挥剑对{target}造成攻击力{ap}%伤害",
"lv_entries": [
@@ -33,7 +31,10 @@
"lv": 2,
"s_lv": 2,
"overrides": {
"ap": 120
"timed_buff_id": 7012,
"buff_value": 30,
"buff_chance": 100,
"buff_duration": 5
},
"desc": "普通攻击造成 120% 攻击伤害"
}

View File

@@ -98,6 +98,54 @@
引用校验:`timed_buff_id` 必须 ∈ `BuffList`;`proc_skill.s_uuid` / 触发组 key / 召唤类引用必须 ∈ `SkillSet`。
### 6.1 timed_buff_id(计时 buff 注入)
**编辑器 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 默认值。
**配套键**(同一 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) | 按主效果推导:伤害→被命中者,辅助→友方 |
**生效路径(重要)**:
- ✅ 友方/辅助技能(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 写入底座即可起效;
- 同一 overrides 不要既配 `timed_buff_id` 又依赖永久 `buff_type` 加成:timed 路径生效后永久分支被跳过。
**语义陷阱**:`buff_target` 缺省为被命中者——若想"攻击回血/吸血",必须显式配 `"buff_target": 1`,否则 buff 挂到被打的敌人身上(如 7010 回血会奶敌人)。
### 6.2 proc_skill(命中概率触发主动技能)
**定位**:命中后概率触发一个**主动技能**(大招/触发技底座,如 6100+/6200+ 段伤害技能),以施法者身份完整释放(含伤害/弹道/特效)。它是技能触发器,不是挂 buff 的方式——debuff 注入请用 `timed_buff_id`(见 §6.1)。
**编辑器 UI**:overrides 添加 `proc_skill 命中概率触发主动技能` 行后展开结构化编辑:
1. `被触发技能 uuid`:下拉列出全部 SkillSet(`uuid 名称`);
2. `概率%`:0~100 数字输入,默认 100;
3. `被触发技能覆写`:内嵌 OverrideEditor(同套白名单),可递归覆写被触发技能的 `ap`/`TGroup`/`hit_count` 等。
选中被触发技能后自动在其内嵌 overrides 补 `TGroup` 行(取底座值,已存在不覆盖),便于直改释放对象并联动对敌断链警示。
选中 `s_uuid` 前该行不输出(防止半成品配置导出),编辑中状态会被保留。
**JSON 格式**:
```json
"proc_skill": { "s_uuid": 6201, "chance": 20, "overrides": { "ap": 150 } }
```
**运行时行为**:
- 触发链路:技能命中 → 掷 `chance` 骰(≥100 直接成功不掷骰;无 70% 封顶)→ 以施法者身份走 `forceCastTriggerSkill`(无视 CD、无施法前摇,触发类型记为 atking);
- `overrides` 与被触发技能自身 heroSet overrides 叠加,本次传入优先;
- DoT 伤害不触发 proc;被触发的技能不再累加普攻计数(防递归);
- 内嵌 `TGroup` 可改变被触发技能的释放对象(如触发一个友方增益)。
**选型指引**(对敌附带效果):
- 挂 debuff(冰冻/中毒/易伤等)→ `timed_buff_id`:写入 SkillSet 底座,或用友方技能 + `buff_target: 3` 分流挂敌;
- 命中后追发一个主动技能(陨石/火球等完整释放)→ `proc_skill`。
## 7. 新增英雄操作清单
1. 读 §0 数据源:选定技能/光环/buff 底座,确认 `HERO_ROLE_BASE` 模板值与攻速档位;
@@ -117,4 +165,6 @@
- 触发技/光环解锁写 lv6、大招档位写 lv9 → 现行惯例 lv4 / lv8+lv16;
- 普攻用数值 cd 而不给 `cdExpr` → 丢失攻速档位语义(编辑器攻速下拉失效);
- 档位 overrides 全量复制基础档 → 浅合并下冗余且后续改基础值不生效;
- desc 与 overrides 数值脱节 → desc 模板只写 `{ap}` 等占位符,不要硬编码数字。
- desc 与 overrides 数值脱节 → desc 模板只写 `{ap}` 等占位符,不要硬编码数字;
- 对敌技能 debuff 写在 overrides → 命中附带判定按 SkillSet 底座读取,应写入底座配置(详见 §6.1);
- 想做吸血但漏配 `buff_target:1` → 缺省挂被命中者,回血 buff 会奶敌人。

View File

@@ -36,6 +36,7 @@
size="small"
style="width: 220px"
filterable
@change="onProcSkillChange(item)"
>
<el-option v-for="s in skillOptions" :key="s.uuid" :label="`${s.uuid} ${s.name}`" :value="s.uuid" />
</el-select>
@@ -50,7 +51,7 @@
/>
<div class="proc-skill-overrides">
<span class="proc-skill-label">被触发技能覆写:</span>
<OverrideEditor v-model="item.value.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="item.value.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="item.value.s_uuid" />
</div>
</div>
</template>
@@ -74,12 +75,21 @@
<el-input-number v-else v-model="item.value" size="small" :controls="false" style="width: 120px" />
<el-button type="danger" size="small" text @click="remove(idx)">删除</el-button>
</div>
<el-alert
v-if="showEnemyBuffWarn"
type="info"
:closable="false"
show-icon
style="margin-bottom: 6px"
title="对敌技能 debuff 配置提示"
description="对敌技能的命中附带 debuff 以 SkillSet 底座配置为准,此处 overrides 注入建议用于友方/辅助技能(可用 buff_target 分流挂敌);吸血类需 buff_target=1(施法者自身)。proc_skill 用于命中后概率触发主动技能,不是挂 buff 的方式。"
/>
<el-button size="small" @click="add">+ 添加覆盖</el-button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ref, watch, computed } from 'vue'
import { OVERRIDE_KEYS, SKILL_ICON_OPTIONS } from '../constants'
const props = defineProps({
@@ -88,9 +98,22 @@ const props = defineProps({
skillOptions: { type: Array, default: () => [] },
// 全部 buff 候选(timed_buff_id 下拉用),由 App 从 meta.BuffList 计算后透传
buffOptions: { type: Array, default: () => [] },
// 宿主技能 uuid:用于判定对敌技能(TGroup=Enemy)注入 timed_buff_id 的断链警示
baseSkillUuid: { type: Number, default: null },
})
const emit = defineEmits(['update:modelValue'])
// 宿主技能是否走对敌路径:有效 TGroup = overrides.TGroup 覆写值 ?? 底座 TGroup(Enemy=3;meta 未加载时不判定)
const isEnemyHost = computed(() => {
if (props.baseSkillUuid == null) return false
const base = props.skillOptions.find((s) => s.uuid === props.baseSkillUuid)
const tgroupRow = rows.value.find((r) => r.key === 'TGroup')
const effective = tgroupRow?.value ?? base?.TGroup
return effective === 3 || effective === 'Enemy'
})
// 对敌技能且注入了 timed_buff_id → 运行时断链不生效,警示引导改用 proc_skill / 底座表
const showEnemyBuffWarn = computed(() => isEnemyHost.value && rows.value.some((r) => r.key === 'timed_buff_id'))
// 本地行数组,允许 key 为空
const rows = ref(Object.entries(props.modelValue || {}).map(([key, value]) => ({ key, value })))
@@ -155,11 +178,42 @@ function isBuffKey(key) {
return OVERRIDE_KEYS.find((k) => k.key === key)?.type === 'buff'
}
// 选中 proc_skill 被触发技能后,在其内嵌 overrides 自动补 TGroup 行(已存在不覆盖):
// 值取该技能底座的 TGroup,让内嵌编辑器能立刻按有效目标群体做对敌断链警示等判定,也便于直改释放对象
function onProcSkillChange(item) {
const overrides = item.value?.overrides
if (!overrides) return
if (overrides.TGroup !== undefined) return
const base = props.skillOptions.find((s) => s.uuid === item.value.s_uuid)
const name = TGROUP_NAMES[base?.TGroup]
if (name) overrides.TGroup = name
}
// 底座 TGroup 数值 → 字符串名(编辑器 TGroup 下拉 value 为字符串,导出时还原 TGroup.X 枚举表达式)
const TGROUP_NAMES = ['Self', 'Ally', 'Team', 'Enemy', 'All']
// allow-create 手输的 buff id 是字符串,归一化为数字,保证导出 TS 配置类型正确
function onBuffValueChange(item) {
if (typeof item.value === 'string' && item.value.trim() !== '' && !isNaN(Number(item.value))) {
item.value = Number(item.value)
}
ensureBuffCompanionRows(item.value)
}
// 挂载 timed_buff_id 后自动补齐配套三键(已存在的不重复添加):
// 数值预填 BuffList 默认值(modifiers[0].value / tick.damage_or_heal / duration),避免导出 0 值覆盖 buff 语义
function ensureBuffCompanionRows(buffId) {
const buff = typeof buffId === 'number' ? props.buffOptions.find((b) => b.id === buffId) : null
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('buff_chance')) {
rows.value.push({ key: 'buff_chance', value: 100 })
}
if (!has('buff_duration')) {
rows.value.push({ key: 'buff_duration', value: buff?.duration ?? 5 })
}
}
// 对象型覆写行选中时初始化默认结构,避免 v-model 绑定到 undefined 子字段

View File

@@ -25,7 +25,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="skill.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="skill.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="skill.uuid" />
</el-form-item>
<!-- 分档编辑:大招(is-ult)全档编辑;普攻(basic-lv2)仅 lv2 强化档(客户端独立技能图标消费) -->
@@ -47,7 +47,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="e.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="e.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="skill.uuid" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="e.desc" type="textarea" :rows="2" />

View File

@@ -23,7 +23,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="entry.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="entry.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="Number(sid)" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="entry.desc" type="textarea" :rows="2" />

View File

@@ -135,8 +135,8 @@ export const OVERRIDE_KEYS = [
{ key: 'call_hero', label: 'call_hero 召唤英雄id' },
{ key: 'revive_hp', label: 'revive_hp 复活回血比例(%)' },
{ key: 'revive_count', label: 'revive_count 复活次数上限' },
// 对象型覆写:命中后概率触发额外技能(技能级触发器),含 s_uuid/chance/overrides 子字段
{ key: 'proc_skill', label: 'proc_skill 命中概率触发技能', type: 'object' },
// 对象型覆写:命中后概率触发主动技能(大招/触发技),含 s_uuid/chance/overrides 子字段
{ key: 'proc_skill', label: 'proc_skill 命中概率触发主动技能', type: 'object' },
// 字符串型覆写:value 为图标ID(uicons 图集帧名),非数值
{ key: 'icon', label: 'icon 英雄语境图标(覆写 SkillSet.icon)', type: 'string' },
]