1. 将触发技能默认生成等级从6改为4,默认激活等级同步更新 2. 修复复活技能成长档位描述错误,从lv1/lv9/lv16改为lv1/lv8/lv16 3. 为普攻技能新增默认的lv2强化档位配置
277 lines
11 KiB
Vue
277 lines
11 KiB
Vue
<template>
|
||
<div class="hero-app">
|
||
<header class="hero-header">
|
||
<h1>英雄配置编辑器</h1>
|
||
<div class="actions">
|
||
<el-button size="small" @click="refreshMeta" :loading="loadingMeta">刷新配置数据</el-button>
|
||
<el-button size="small" type="warning" @click="doExport" :disabled="!heroes.length">导出 hexport.ts</el-button>
|
||
</div>
|
||
</header>
|
||
<div class="hero-main">
|
||
<aside class="hero-sidebar">
|
||
<div class="side-head">
|
||
<span>英雄列表 ({{ heroes.length }})</span>
|
||
<el-button size="small" type="primary" text @click="addHero">+ 新增</el-button>
|
||
</div>
|
||
<div class="hero-list">
|
||
<div
|
||
v-for="(h, i) in heroes"
|
||
:key="i"
|
||
class="hero-item"
|
||
:class="{ active: i === activeIdx }"
|
||
@click="activeIdx = i"
|
||
>
|
||
<div>
|
||
<div>{{ h.uuid }} {{ h.name || '(未命名)' }}</div>
|
||
<div class="meta">{{ roleLabel(h.roleKey) }} · {{ typeLabel(h.typeKey) }} · ★{{ h.rarity }}</div>
|
||
</div>
|
||
<el-button type="danger" size="small" text @click.stop="removeHero(i)">删</el-button>
|
||
</div>
|
||
<div v-if="!heroes.length" class="empty">点击右上角"新增"创建英雄</div>
|
||
</div>
|
||
</aside>
|
||
<section class="hero-editor" v-if="current">
|
||
<!-- 基础属性 -->
|
||
<div class="panel">
|
||
<h3>基础属性</h3>
|
||
<div class="form-grid">
|
||
<el-form-item label="uuid">
|
||
<el-input-number v-model="current.uuid" :min="5001" :controls="false" style="width: 100%" />
|
||
</el-form-item>
|
||
<el-form-item label="名称">
|
||
<el-input v-model="current.name" />
|
||
</el-form-item>
|
||
<el-form-item label="资源路径 path">
|
||
<el-input v-model="current.path" placeholder="hm1 / hk2 ..." />
|
||
</el-form-item>
|
||
<el-form-item label="品质 rarity">
|
||
<el-select v-model="current.rarity">
|
||
<el-option v-for="n in 5" :key="n" :label="`${n} 星`" :value="n" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="职业 role">
|
||
<el-select v-model="current.roleKey">
|
||
<el-option v-for="r in HERO_ROLES" :key="r.key" :label="r.label" :value="r.key" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="成长 grow_type">
|
||
<el-select v-model="current.growKey">
|
||
<el-option v-for="g in GROW_TYPES" :key="g.key" :label="g.label" :value="g.key" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="形态 type">
|
||
<el-select v-model="current.typeKey">
|
||
<el-option v-for="t in H_TYPES" :key="t.key" :label="t.label" :value="t.key" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="攻击距离 dis(可选)">
|
||
<el-input-number v-model="current.dis" :min="0" :controls="false" style="width: 100%" placeholder="默认按形态" />
|
||
</el-form-item>
|
||
<el-form-item label="hp 偏移">
|
||
<el-input-number v-model="current.statBase.hp" :controls="false" style="width: 100%" />
|
||
</el-form-item>
|
||
<el-form-item label="ap 偏移">
|
||
<el-input-number v-model="current.statBase.ap" :controls="false" style="width: 100%" />
|
||
</el-form-item>
|
||
<el-form-item label="def 偏移">
|
||
<el-input-number v-model="current.statBase.def" :controls="false" style="width: 100%" />
|
||
</el-form-item>
|
||
<el-form-item label="注释">
|
||
<el-input v-model="current.comment" placeholder="导出文件中显示在条目上方" />
|
||
</el-form-item>
|
||
</div>
|
||
<div class="muted">基础值 = HERO_ROLE_BASE[{{ current.roleKey }}] → hp {{ roleBase?.hp }} / ap {{ roleBase?.ap }} / def {{ roleBase?.def }}</div>
|
||
</div>
|
||
|
||
<!-- 普攻 -->
|
||
<div class="panel">
|
||
<h3>普攻 skills[0]</h3>
|
||
<SkillEditor v-if="basicSkill" :skill="basicSkill" :options="basicSkillOptions" />
|
||
<el-button v-else size="small" type="primary" @click="addBasicSkill">+ 添加普攻</el-button>
|
||
</div>
|
||
|
||
<!-- 大招 -->
|
||
<div class="panel">
|
||
<h3>大招 skills[1](与 revive 互斥)</h3>
|
||
<template v-if="current.revive.length === 0">
|
||
<SkillEditor v-if="ultSkill" :skill="ultSkill" :options="ultSkillOptions" :is-ult="true" />
|
||
<el-button v-else size="small" type="primary" @click="addUltSkill">+ 添加大招</el-button>
|
||
</template>
|
||
<el-alert v-else type="info" :closable="false">已配置 revive,大招不可用</el-alert>
|
||
</div>
|
||
|
||
<!-- 触发技能 -->
|
||
<div class="panel">
|
||
<h3>触发技能(lv4 激活)</h3>
|
||
<el-tabs>
|
||
<el-tab-pane label="atking 攻击后"><TriggerGroupEditor :model="current.atking" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
|
||
<el-tab-pane label="atked 受击后"><TriggerGroupEditor :model="current.atked" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
|
||
<el-tab-pane label="fstart 战斗开始"><TriggerGroupEditor :model="current.fstart" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
|
||
<el-tab-pane label="dead 死亡后"><TriggerGroupEditor :model="current.dead" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
|
||
<!-- 光环 -->
|
||
<div class="panel">
|
||
<h3>驻场光环 field</h3>
|
||
<FieldEditor :model="current.field" :field-options="fieldOptions" />
|
||
</div>
|
||
|
||
<!-- 复活 -->
|
||
<div class="panel">
|
||
<h3>复活 revive(与大招互斥)</h3>
|
||
<ReviveEditor :model="current.revive" :revive-options="reviveOptions" />
|
||
</div>
|
||
|
||
<!-- 额外加成 -->
|
||
<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>
|
||
</div>
|
||
|
||
<!-- 描述 -->
|
||
<div class="panel">
|
||
<h3>英雄描述 info</h3>
|
||
<el-input v-model="current.info" type="textarea" :rows="3" />
|
||
</div>
|
||
</section>
|
||
<section v-else class="hero-editor"><div class="empty">请先选择或新增英雄</div></section>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, h } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { api } from './api'
|
||
import {
|
||
HERO_ROLES, GROW_TYPES, H_TYPES,
|
||
ROLE_ATK_SKILL, ROLE_ULT_SKILL, SUPPORT_ULT,
|
||
defaultHero, defaultSkill,
|
||
} from './constants'
|
||
import SkillEditor from './components/SkillEditor.vue'
|
||
import TriggerGroupEditor from './components/TriggerGroupEditor.vue'
|
||
import FieldEditor from './components/FieldEditor.vue'
|
||
import ReviveEditor from './components/ReviveEditor.vue'
|
||
import BonusEditor from './components/BonusEditor.vue'
|
||
|
||
const heroes = ref([])
|
||
const activeIdx = ref(-1)
|
||
const meta = ref(null)
|
||
const loadingMeta = ref(false)
|
||
|
||
const current = computed(() => heroes.value[activeIdx.value])
|
||
|
||
const skillMap = computed(() => meta.value?.SkillSet || {})
|
||
const allSkills = computed(() => Object.values(skillMap.value).sort((a, b) => a.uuid - b.uuid))
|
||
const fieldOptions = computed(() => Object.values(meta.value?.FieldSkillSet || {}).sort((a, b) => a.uuid - b.uuid))
|
||
const reviveOptions = computed(() => allSkills.value.filter((s) => s.uuid >= 6500 && s.uuid < 6600))
|
||
|
||
const roleBase = computed(() => {
|
||
if (!current.value || !meta.value?.HERO_ROLE_BASE) return null
|
||
const roleEnum = HERO_ROLES.find((r) => r.key === current.value.roleKey)?.value
|
||
return meta.value.HERO_ROLE_BASE[roleEnum]
|
||
})
|
||
|
||
const basicSkill = computed(() => current.value?.skills?.[0])
|
||
const ultSkill = computed(() => current.value?.skills?.[1])
|
||
|
||
const basicSkillOptions = computed(() => {
|
||
if (!current.value) return []
|
||
const allowed = ROLE_ATK_SKILL[current.value.roleKey] || []
|
||
return allSkills.value.filter((s) => allowed.includes(s.uuid))
|
||
})
|
||
|
||
const ultSkillOptions = computed(() => {
|
||
if (!current.value) return []
|
||
const allowed = [...(ROLE_ULT_SKILL[current.value.roleKey] || []), ...SUPPORT_ULT]
|
||
return allSkills.value.filter((s) => allowed.includes(s.uuid))
|
||
})
|
||
|
||
function roleLabel(key) { return HERO_ROLES.find((r) => r.key === key)?.label || key }
|
||
function typeLabel(key) { return H_TYPES.find((t) => t.key === key)?.label || key }
|
||
|
||
async function loadMeta() {
|
||
loadingMeta.value = true
|
||
try {
|
||
meta.value = await api.meta()
|
||
} catch (e) {
|
||
ElMessage.error('加载配置数据失败: ' + (e?.response?.data?.error || e.message))
|
||
} finally {
|
||
loadingMeta.value = false
|
||
}
|
||
}
|
||
async function refreshMeta() {
|
||
try {
|
||
await api.refresh()
|
||
await loadMeta()
|
||
ElMessage.success('已刷新')
|
||
} catch (e) {
|
||
ElMessage.error('刷新失败')
|
||
}
|
||
}
|
||
|
||
function addHero() {
|
||
const nh = defaultHero()
|
||
const used = new Set(heroes.value.map((h) => h.uuid))
|
||
let uuid = 5001
|
||
while (used.has(uuid)) uuid++
|
||
nh.uuid = uuid
|
||
heroes.value.push(nh)
|
||
activeIdx.value = heroes.value.length - 1
|
||
}
|
||
function removeHero(i) {
|
||
heroes.value.splice(i, 1)
|
||
if (activeIdx.value >= heroes.value.length) activeIdx.value = heroes.value.length - 1
|
||
}
|
||
|
||
function addBasicSkill() {
|
||
if (!current.value) return
|
||
const first = basicSkillOptions.value[0]
|
||
if (!first) return ElMessage.warning('当前职业无可用普攻底座')
|
||
const sk = defaultSkill(first.uuid)
|
||
// 普攻默认提供 lv2 强化档位
|
||
sk.lv_entries = [
|
||
{ lv: 2, s_lv: 2, overrides: { ap: 120 }, desc: '' },
|
||
]
|
||
current.value.skills[0] = sk
|
||
}
|
||
function addUltSkill() {
|
||
if (!current.value) return
|
||
const first = ultSkillOptions.value[0]
|
||
if (!first) return ElMessage.warning('当前职业无可用大招底座')
|
||
const sk = defaultSkill(first.uuid)
|
||
// 大招默认 summon_count=1,并提供 lv9/lv16 档位
|
||
sk.cd = 8
|
||
sk.cdExpr = null
|
||
sk.overrides = { summon_count: 1 }
|
||
sk.lv_entries = [
|
||
{ lv: 9, s_lv: 2, overrides: { summon_count: 1 }, desc: '' },
|
||
{ lv: 16, s_lv: 3, overrides: { summon_count: 2 }, desc: '' },
|
||
]
|
||
current.value.skills[1] = sk
|
||
}
|
||
|
||
async function doExport() {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`将写入 ${heroes.value.length} 个英雄到 assets/script/game/common/config/hexport.ts`,
|
||
'导出确认',
|
||
{ confirmButtonText: '写入', cancelButtonText: '取消', type: 'warning' }
|
||
)
|
||
} catch { return }
|
||
try {
|
||
const r = await api.exportHeroes(heroes.value)
|
||
ElMessage.success(`已写入 ${r.bytes} 字节 → ${r.path}`)
|
||
} catch (e) {
|
||
ElMessage.error('导出失败: ' + (e?.response?.data?.error || e.message))
|
||
}
|
||
}
|
||
|
||
onMounted(loadMeta)
|
||
</script>
|