refactor(hero): 重构额外属性加成系统,统一为bonus字段
1. 将原ap_bonus/hp_bonus/def_bonus三个字段合并为通用的bonus数组结构 2. 新增多属性同时配置能力,支持hp/ap/def百分比加成与crt/frz/stun/para固定百分点加成 3. 更新所有前端编辑器、服务端渲染与游戏运行时逻辑适配新配置格式 4. 添加数据兼容迁移逻辑,自动处理旧版英雄配置数据
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
| `skills` | array | **恰好 2 个**:`[普攻, 大招]`,结构见 §3 |
|
||||
| `atking`/`atked`/`fstart`/`dead` | object | 触发组,结构见 §4 |
|
||||
| `field` | array | 驻场光环档位,结构见 §5 |
|
||||
| `ap_bonus`/`hp_bonus`/`def_bonus` | array | 一次性属性奖励 `[{lv,value}]`,运行时累加 ≤英雄等级的档;常规英雄留空 |
|
||||
| `bonus` | array | 额外属性加成 `[{lv,attrs:{...}}]`,运行时累加所有 lv ≤ 英雄等级 的档位;`hp`/`ap`/`def` 为百分比(基数取"基础×品质+成长"快照,多档加法叠加),`crt`/`frz`/`stun`/`para` 为固定百分点;常规英雄留空 |
|
||||
| `info` | string | 描述文案,可空 |
|
||||
| `revive` | — | **已废弃**:复活改在 `dead` 组 overrides 里配 `revive_hp`/`revive_count`,不要写独立字段 |
|
||||
|
||||
|
||||
@@ -202,11 +202,13 @@ function renderSingleHero(h) {
|
||||
// 复活已纳入 dead 触发组,通过 dead 编辑器的 overrides 配置 revive_hp / revive_count
|
||||
// 不再导出独立的 revive 字段
|
||||
|
||||
// bonuses
|
||||
for (const bk of ['ap_bonus', 'hp_bonus', 'def_bonus']) {
|
||||
if (!h[bk]?.length) continue
|
||||
push(` ${bk}: [`)
|
||||
for (const b of h[bk]) push(` { lv: ${b.lv}, value: ${b.value} },`)
|
||||
// bonus(统一额外属性加成:hp/ap/def 百分比 + crt/frz/stun/para 百分点;跳过空 attrs 档位)
|
||||
const bonusList = (h.bonus || []).filter((b) => b.attrs && Object.keys(b.attrs).length)
|
||||
if (bonusList.length) {
|
||||
push(` bonus: [`)
|
||||
for (const b of bonusList) {
|
||||
push(` { lv: ${b.lv}, attrs: ${JSON.stringify(b.attrs)} },`)
|
||||
}
|
||||
push(` ],`)
|
||||
}
|
||||
|
||||
@@ -325,6 +327,18 @@ function csvStatLine(h, key) {
|
||||
return `${role}${base > 0 ? '+' : ''}${base}`
|
||||
}
|
||||
|
||||
/** bonus 描述行:lv9 ap+20% hp+15%;lv12 crt+10 ap+30% */
|
||||
function csvBonus(bonus) {
|
||||
if (!bonus?.length) return '无'
|
||||
return bonus
|
||||
.filter((b) => b.attrs && Object.keys(b.attrs).length)
|
||||
.map((b) => {
|
||||
const parts = Object.entries(b.attrs).map(([k, v]) => (k === 'hp' || k === 'ap' || k === 'def') ? `${k}+${v}%` : `${k}+${v}`)
|
||||
return `lv${b.lv} ${parts.join(' ')}`
|
||||
})
|
||||
.join(';') || '无'
|
||||
}
|
||||
|
||||
/** 生成英雄设计表 CSV 内容 */
|
||||
function renderHeroCsv(heroes) {
|
||||
const sorted = [...heroes].sort((a, b) => a.uuid - b.uuid)
|
||||
@@ -350,6 +364,7 @@ function renderHeroCsv(heroes) {
|
||||
rows.push(['fstart[0]_lv4', ...sorted.map((h) => csvTrigger(h.fstart))])
|
||||
rows.push(['dead[0]_lv4', ...sorted.map((h) => csvTrigger(h.dead))])
|
||||
rows.push(['field[0]_lv4', ...sorted.map((h) => csvField(h.field))])
|
||||
rows.push(['bonus', ...sorted.map((h) => csvBonus(h.bonus))])
|
||||
return rows.map((r) => r.map(csvCell).join(',')).join('\n') + '\n'
|
||||
}
|
||||
|
||||
|
||||
@@ -173,12 +173,8 @@
|
||||
|
||||
<!-- 额外加成 -->
|
||||
<div class="panel">
|
||||
<h3>额外属性加成</h3>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="ap_bonus"><BonusEditor :model="current.ap_bonus" /></el-tab-pane>
|
||||
<el-tab-pane label="hp_bonus"><BonusEditor :model="current.hp_bonus" /></el-tab-pane>
|
||||
<el-tab-pane label="def_bonus"><BonusEditor :model="current.def_bonus" /></el-tab-pane>
|
||||
</el-tabs>
|
||||
<h3>额外属性加成(hp/ap/def 百分比,crt/frz/stun/para 百分点)</h3>
|
||||
<BonusEditor :model="current.bonus" />
|
||||
</div>
|
||||
|
||||
<!-- 描述 -->
|
||||
@@ -363,9 +359,9 @@ function fieldSummary(h) {
|
||||
return `lv${f.lv} · ${auras}`
|
||||
})
|
||||
}
|
||||
// 额外加成条目总数(ap/hp/def_bonus 之和)
|
||||
// 额外加成档位总数(bonus 数组长度)
|
||||
function bonusCount(h) {
|
||||
return (h.ap_bonus?.length || 0) + (h.hp_bonus?.length || 0) + (h.def_bonus?.length || 0)
|
||||
return h.bonus?.length || 0
|
||||
}
|
||||
|
||||
async function loadMeta() {
|
||||
@@ -439,6 +435,11 @@ async function loadHeroes() {
|
||||
try {
|
||||
const r = await api.fetchHeroes()
|
||||
if (Array.isArray(r.heroes) && r.heroes.length) {
|
||||
// 兼容旧数据:ap_bonus/hp_bonus/def_bonus 已废弃,统一迁移为空 bonus
|
||||
for (const h of r.heroes) {
|
||||
if (!Array.isArray(h.bonus)) h.bonus = []
|
||||
delete h.ap_bonus; delete h.hp_bonus; delete h.def_bonus
|
||||
}
|
||||
heroes.value = r.heroes
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,14 +1,61 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(b, idx) in model" :key="idx" style="display: flex; gap: 8px; margin-bottom: 6px">
|
||||
<el-input-number v-model="b.lv" :min="1" :max="20" :controls="false" style="width: 100px" placeholder="lv" />
|
||||
<el-input-number v-model="b.value" :controls="false" style="width: 140px" placeholder="value" />
|
||||
<el-button type="danger" size="small" text @click="model.splice(idx, 1)">删除</el-button>
|
||||
<div v-for="(b, idx) in model" :key="idx" class="skill-block">
|
||||
<div class="head">
|
||||
<span>加成档位 lv {{ b.lv }}</span>
|
||||
<el-button type="danger" size="small" text @click="model.splice(idx, 1)">删除档位</el-button>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="等级 lv">
|
||||
<el-input-number v-model="b.lv" :min="1" :max="20" :controls="false" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="属性加成">
|
||||
<div style="width: 100%">
|
||||
<div v-for="(a, i) in attrList(b)" :key="i" style="display: flex; gap: 8px; margin-bottom: 6px">
|
||||
<el-select :model-value="a.key" placeholder="属性" style="width: 220px" @update:model-value="(k) => renameKey(b, a.key, k)">
|
||||
<el-option v-for="opt in BONUS_ATTR_KEYS" :key="opt.key" :label="opt.label" :value="opt.key" :disabled="hasKey(b, opt.key, a.key)" />
|
||||
</el-select>
|
||||
<el-input-number v-model="b.attrs[a.key]" :controls="false" style="width: 140px" placeholder="value" />
|
||||
<el-button type="danger" size="small" text @click="removeKey(b, a.key)">删除</el-button>
|
||||
</div>
|
||||
<el-button size="small" @click="addKey(b)">+ 添加属性</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-button size="small" @click="model.push({ lv: 12, value: 0 })">+ 添加</el-button>
|
||||
<el-button size="small" type="primary" @click="model.push({ lv: 9, attrs: {} })">+ 添加加成档位</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { BONUS_ATTR_KEYS } from '../constants'
|
||||
|
||||
defineProps({ model: { type: Array, required: true } })
|
||||
|
||||
// attrs 对象为键值对,转列表渲染(保持键序稳定)
|
||||
function attrList(b) {
|
||||
return Object.keys(b.attrs).map((key) => ({ key }))
|
||||
}
|
||||
function hasKey(b, key, except) {
|
||||
return key !== except && b.attrs[key] !== undefined
|
||||
}
|
||||
function addKey(b) {
|
||||
const free = BONUS_ATTR_KEYS.find((o) => b.attrs[o.key] === undefined)
|
||||
if (free) b.attrs[free.key] = 0
|
||||
}
|
||||
function removeKey(b, key) {
|
||||
delete b.attrs[key]
|
||||
}
|
||||
// 换键:删旧键、写新键(保留原值)
|
||||
function renameKey(b, oldKey, newKey) {
|
||||
const v = b.attrs[oldKey]
|
||||
delete b.attrs[oldKey]
|
||||
b.attrs[newKey] = v
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.skill-block { border: 1px solid var(--el-border-color); border-radius: 4px; padding: 10px; margin-bottom: 10px; }
|
||||
.head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.form-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 16px; }
|
||||
</style>
|
||||
|
||||
@@ -76,9 +76,7 @@ export function defaultHero() {
|
||||
dead: {},
|
||||
field: [], // [{lv,uuids:[{uuid,value,name}],desc}]
|
||||
// revive 已纳入 dead 触发组,通过 overrides.revive_hp / revive_count 配置
|
||||
ap_bonus: [],
|
||||
hp_bonus: [],
|
||||
def_bonus: [],
|
||||
bonus: [], // [{lv, attrs:{hp?,ap?,def?,crt?,frz?,stun?,para?}}] hp/ap/def 为百分比,概率类为固定百分点
|
||||
info: '',
|
||||
}
|
||||
}
|
||||
@@ -143,6 +141,17 @@ export const OVERRIDE_KEYS = [
|
||||
{ key: 'icon', label: 'icon 英雄语境图标(覆写 SkillSet.icon)', type: 'string' },
|
||||
]
|
||||
|
||||
// bonus attrs 可用键:hp/ap/def 按"基础+成长"快照百分比加成;crt/frz/stun/para 为固定百分点累加
|
||||
export const BONUS_ATTR_KEYS = [
|
||||
{ key: 'hp', label: 'hp 生命%' },
|
||||
{ key: 'ap', label: 'ap 攻击%' },
|
||||
{ key: 'def', label: 'def 防御%' },
|
||||
{ key: 'crt', label: 'crt 暴击率(百分点)' },
|
||||
{ key: 'frz', label: 'frz 冰冻(百分点)' },
|
||||
{ key: 'stun', label: 'stun 击晕(百分点)' },
|
||||
{ key: 'para', label: 'para 麻痹(百分点)' },
|
||||
]
|
||||
|
||||
// icon 覆写常用候选(SkillSet 各底座图标 + 通用图标),可在输入框直接键入其他帧名
|
||||
export const SKILL_ICON_OPTIONS = [
|
||||
'Stat_Attack_01', 'Stat_RangedAttack', 'Stat_Tripleshot',
|
||||
|
||||
Reference in New Issue
Block a user