feat(editor): add proc_skill override support for skills

1. 新增proc_skill覆写配置项,支持配置概率触发额外技能
2. 重构OverrideEditor,支持对象类型覆写的结构化编辑
3. 给SkillEditor和TriggerGroupEditor透传技能选项用于uuid选择
4. 更新英雄技能数据示例,添加触发普攻的proc_skill配置
5. 修复覆写编辑器同步逻辑,保留编辑中未完成的配置项
This commit is contained in:
panFD
2026-09-05 13:38:30 +08:00
parent 63423c5378
commit c4b49e4df6
8 changed files with 89 additions and 28 deletions

View File

@@ -548,7 +548,7 @@ 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}, desc: "挥剑对{target}造成攻击力{ap}%伤害",
6004: { uuid: 6004, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: {"ap":100,"proc_skill":{"s_uuid":6001,"chance":100,"overrides":{"crt":100}}}, desc: "挥剑对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120} },
],

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "dae7fb4d-25c3-4c7a-8630-c70e2bac9b3a",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -23,7 +23,14 @@
"cd": 1.2,
"cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd",
"overrides": {
"ap": 100
"ap": 100,
"proc_skill": {
"s_uuid": 6001,
"chance": 100,
"overrides": {
"crt": 100
}
}
},
"desc": "挥剑对{target}造成攻击力{ap}%伤害",
"lv_entries": [

View File

@@ -114,14 +114,14 @@
<!-- 普攻 -->
<div class="panel">
<h3>普攻 skills[0]</h3>
<SkillEditor v-if="basicSkill" :skill="basicSkill" :options="basicSkillOptions" :basic-lv2="true" />
<SkillEditor v-if="basicSkill" :skill="basicSkill" :options="basicSkillOptions" :skill-options="allSkills" :basic-lv2="true" />
<el-button v-else size="small" type="primary" @click="addBasicSkill">+ 添加普攻</el-button>
</div>
<!-- 大招 -->
<div class="panel">
<h3>大招 skills[1]</h3>
<SkillEditor v-if="ultSkill" :skill="ultSkill" :options="ultSkillOptions" :is-ult="true" />
<SkillEditor v-if="ultSkill" :skill="ultSkill" :options="ultSkillOptions" :skill-options="allSkills" :is-ult="true" />
<el-button v-else size="small" type="primary" @click="addUltSkill">+ 添加大招</el-button>
</div>

View File

@@ -1,7 +1,7 @@
<template>
<div class="override-editor">
<div v-for="(item, idx) in rows" :key="idx" class="override-row">
<el-select v-model="item.key" placeholder="字段" size="small" style="width: 220px" filterable>
<el-select v-model="item.key" placeholder="字段" size="small" style="width: 220px" filterable @change="onKeyChange(item)">
<el-option v-for="k in availableKeys(idx)" :key="k.key" :label="k.label" :value="k.key" />
</el-select>
<!-- 字符串型覆写(icon):输入帧名,提供常用候选可筛选 -->
@@ -17,6 +17,33 @@
>
<el-option v-for="opt in SKILL_ICON_OPTIONS" :key="opt" :label="opt" :value="opt" />
</el-select>
<!-- 对象型覆写(proc_skill):命中后概率触发额外技能,结构化编辑 s_uuid/chance/overrides -->
<template v-else-if="isObjectKey(item.key)">
<div class="proc-skill-editor">
<el-select
v-model="item.value.s_uuid"
placeholder="被触发技能 uuid"
size="small"
style="width: 220px"
filterable
>
<el-option v-for="s in skillOptions" :key="s.uuid" :label="`${s.uuid} ${s.name}`" :value="s.uuid" />
</el-select>
<el-input-number
v-model="item.value.chance"
:min="0"
:max="100"
:controls="false"
size="small"
placeholder="概率%"
style="width: 90px"
/>
<div class="proc-skill-overrides">
<span class="proc-skill-label">被触发技能覆写:</span>
<OverrideEditor v-model="item.value.overrides" :skill-options="skillOptions" />
</div>
</div>
</template>
<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>
@@ -28,25 +55,43 @@
import { ref, watch } from 'vue'
import { OVERRIDE_KEYS, SKILL_ICON_OPTIONS } from '../constants'
const props = defineProps({ modelValue: { type: Object, default: () => ({}) } })
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
// 全部技能候选(proc_skill.s_uuid 下拉用),由 App 经 SkillEditor 透传
skillOptions: { type: Array, default: () => [] },
})
const emit = defineEmits(['update:modelValue'])
// 本地行数组,允许 key 为空
const rows = ref(Object.entries(props.modelValue || {}).map(([key, value]) => ({ key, value })))
// 外部 modelValue 变化时同步(如切换英雄)
// 该行是否满足输出条件(proc_skill 需有合法 s_uuid,其余仅需有 key)
function isRowOutput(it) {
if (!it.key) return false
if (it.key === 'proc_skill') return !!it.value?.s_uuid
return true
}
// 外部 modelValue 变化时同步(如切换英雄):
// 仅当外部值与"当前可输出的行"不一致时才覆盖;proc_skill 编辑中(s_uuid 未填)的行不输出但需保留,
// 否则会因 modelValue 缺少 proc_skill 而把用户正在编辑的行整个清掉(表现为"添加无效")
watch(() => props.modelValue, (v) => {
const next = Object.entries(v || {}).map(([key, value]) => ({ key, value }))
// 简单比较,避免输入时循环
if (JSON.stringify(next) !== JSON.stringify(rows.value.filter(r => r.key))) {
rows.value = next
const outputRows = rows.value.filter(isRowOutput)
if (JSON.stringify(next) !== JSON.stringify(outputRows)) {
// 保留编辑中(key 已选但未达输出条件)的行,追加在外部同步结果之后
const editing = rows.value.filter((r) => r.key && !isRowOutput(r))
rows.value = [...next, ...editing]
}
}, { deep: true })
// 每次行变化都尝试同步(过滤空 key)
// 每次行变化都尝试同步(过滤空 key;proc_skill 需有合法 s_uuid 才输出)
watch(rows, (arr) => {
const obj = {}
for (const it of arr) if (it.key) obj[it.key] = it.value
for (const it of arr) {
if (!isRowOutput(it)) continue
obj[it.key] = it.value
}
emit('update:modelValue', obj)
}, { deep: true })
@@ -61,10 +106,20 @@ function isStringKey(key) {
return OVERRIDE_KEYS.find((k) => k.key === key)?.type === 'string'
}
// 该覆写字段为枚举型时返回候选列表(如 buff_target),否则返回 null 走数值输入
function enumOptions(key) {
const def = OVERRIDE_KEYS.find((k) => k.key === key)
return def?.type === 'enum' ? def.options : null
// 该覆写字段是否为对象型(proc_skill:{s_uuid, chance, overrides})
function isObjectKey(key) {
return OVERRIDE_KEYS.find((k) => k.key === key)?.type === 'object'
}
// 对象型覆写行选中时初始化默认结构,避免 v-model 绑定到 undefined 子字段
function onKeyChange(item) {
if (isObjectKey(item.key)) {
item.value = { s_uuid: undefined, chance: 100, overrides: {} }
} else if (isStringKey(item.key)) {
item.value = ''
} else {
item.value = 0
}
}
function add() {
@@ -77,4 +132,8 @@ function remove(idx) {
<style scoped>
.override-row { display: flex; gap: 8px; margin-bottom: 6px; align-items: center; }
/* proc_skill 结构化编辑:占满整行,子字段横排,内嵌覆写编辑器缩进展示 */
.proc-skill-editor { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; flex: 1; }
.proc-skill-overrides { width: 100%; padding-left: 12px; border-left: 2px solid var(--el-border-color); }
.proc-skill-label { font-size: 12px; color: var(--el-text-color-secondary); }
</style>

View File

@@ -25,7 +25,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="skill.overrides" />
<OverrideEditor v-model="skill.overrides" :skill-options="skillOptions" />
</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" />
<OverrideEditor v-model="e.overrides" :skill-options="skillOptions" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="e.desc" type="textarea" :rows="2" />
@@ -70,6 +70,8 @@ import OverrideEditor from './OverrideEditor.vue'
const props = defineProps({
skill: { type: Object, required: true },
options: { type: Array, default: () => [] },
// 全部技能候选(proc_skill.s_uuid 下拉用),由 App 透传
skillOptions: { type: Array, default: () => [] },
isUlt: { type: Boolean, default: false },
// 普攻 lv2 强化档模式:仅允许配置单个 lv2 档(lv=2 / s_lv=2 锁定,禁用增删)
basicLv2: { type: Boolean, default: false },

View File

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

View File

@@ -124,6 +124,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' },
// 字符串型覆写:value 为图标ID(uicons 图集帧名),非数值
{ key: 'icon', label: 'icon 英雄语境图标(覆写 SkillSet.icon)', type: 'string' },
]