refactor(skillDesc): 重构技能描述系统,统一技能命名规则
1. 移除旧的TriggerSkillNameSet,改为从HeroInfo配置读取技能展示名 2. 新增buildSkillTierDescRich方法,实现技能等级分段展示 3. 废弃heroInfo.infos字段,使用动态生成的技能描述替代 4. 新增技能名配置字段,统一主技能和触发技能的展示逻辑
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
* - SkillDescSet:结构化描述模板
|
||||
* - FieldSkillSet(驻场技能)、BuffList(计时 buff 默认值兜底)、Attrs(属性枚举)
|
||||
*/
|
||||
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, getTriggerSkillName, HGrowType, calcGrowBonus, HSkillInfo } from "./heroSet";
|
||||
import { SkillTriggerName, SkillTriggerType, TriggerGrouped, LvTriggerEntry, LvFieldEntry, LvReviveEntry, LvBonusEntry, FieldEntry, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, getGroupedTriggerName, getFieldSkillName, getReviveSkillName, HGrowType, calcGrowBonus, HSkillInfo } from "./heroSet";
|
||||
import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
|
||||
import { FieldDescSet, FieldValueFmt, SkillDescSet } from "./SkillDescSet";
|
||||
import { BuffList } from "./BuffSet";
|
||||
@@ -383,7 +383,7 @@ function buildSkillLines(source: ISkillDescSource, showName: boolean = true, sho
|
||||
const sLv = hSkill.lv_entries?.length
|
||||
? (hSkill.lv_entries.filter(e => e.lv <= lv).pop()?.s_lv ?? hSkill.lv)
|
||||
: hSkill.lv;
|
||||
const nameSeg = showName ? `「${base.name}」 ` : "";
|
||||
const nameSeg = showName ? `「${hSkill.name ?? base.name}」 ` : "";
|
||||
lines.push(buildEffectLine(`Lv${sLv}: ${nameSeg}`, skill));
|
||||
}
|
||||
}
|
||||
@@ -404,8 +404,8 @@ function buildSkillLines(source: ISkillDescSource, showName: boolean = true, sho
|
||||
if (!base) continue;
|
||||
const skill = mergeSkillParams(base, item.overrides);
|
||||
const trigger = needCount ? `${name}×${item.t_num}` : name;
|
||||
// 触发语境专属名称(如 atked.6301=不屈壁垒),未配置回退通用名
|
||||
const nameSeg = showName ? `「${getTriggerSkillName(key, item.s_uuid)}」 ` : "";
|
||||
// 触发技能名走 HeroInfo 语境配置(如 atked.6301=不屈壁垒),未配置回退 SkillSet 内部名
|
||||
const nameSeg = showName ? `「${getGroupedTriggerName(grouped, item.s_uuid)}」 ` : "";
|
||||
lines.push(buildEffectLine(`${trigger}:${nameSeg}`, skill));
|
||||
}
|
||||
continue;
|
||||
@@ -418,7 +418,7 @@ function buildSkillLines(source: ISkillDescSource, showName: boolean = true, sho
|
||||
const base = SkillSet[Number(sKey)];
|
||||
if (!base) continue;
|
||||
const sorted = [...entries].sort((a, b) => a.lv - b.lv);
|
||||
const nameSeg = showName ? `「${getTriggerSkillName(key, Number(sKey))}」 ` : "";
|
||||
const nameSeg = showName ? `「${getGroupedTriggerName(grouped, Number(sKey))}」 ` : "";
|
||||
const headOf = (e: LvTriggerEntry) => `Lv${e.lv}: ${needCount ? `${name}×${e.t_num}` : name} ${nameSeg}`;
|
||||
|
||||
// 取当前等级生效档(≤lv 的最大档)
|
||||
@@ -602,7 +602,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boole
|
||||
const sLv = hSkill.lv_entries?.length
|
||||
? (hSkill.lv_entries.filter(e => e.lv <= lv).pop()?.s_lv ?? hSkill.lv)
|
||||
: hSkill.lv;
|
||||
const head = showName ? `「${base.name}」 Lv.${sLv}: ` : `Lv.${sLv}: `;
|
||||
const head = showName ? `「${hSkill.name ?? base.name}」 Lv.${sLv}: ` : `Lv.${sLv}: `;
|
||||
lines.push(effectRich(buildEffectLine(head, skill)));
|
||||
}
|
||||
}
|
||||
@@ -620,8 +620,8 @@ export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boole
|
||||
const base = SkillSet[sUuid];
|
||||
if (!base) continue;
|
||||
const sorted = [...entries].sort((a, b) => a.lv - b.lv);
|
||||
// 技能名走触发语境专属命名(如 atked.6301=不屈壁垒)
|
||||
const skillName = getTriggerSkillName(key, sUuid);
|
||||
// 技能名走 HeroInfo 触发语境配置(如 atked.6301=不屈壁垒)
|
||||
const skillName = getGroupedTriggerName(grouped, sUuid);
|
||||
|
||||
// 当前生效档(≤lv 最大档),未激活则用最低档
|
||||
let activeIdx = -1;
|
||||
@@ -645,7 +645,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boole
|
||||
const fieldEntries = source[SkillTriggerType.Field] as LvFieldEntry[] | undefined;
|
||||
if (fieldEntries?.length) {
|
||||
for (const entry of resolveFieldByLv(fieldEntries, lv)) {
|
||||
const baseName = getTriggerSkillName(SkillTriggerType.Field, entry.uuid);
|
||||
const baseName = getFieldSkillName(entry.uuid);
|
||||
const line = buildFieldLine("", entry, showName, baseName);
|
||||
if (line) lines.push(effectRich(line));
|
||||
}
|
||||
@@ -655,7 +655,7 @@ export function buildSkillDescMoreRich(source: ISkillDescSource, showName: boole
|
||||
// 血量百分比来自 SkillSet[s_uuid].ap(运行时口径),upr 是每级追加次数,不在此展示
|
||||
const revive = resolveReviveByLv(source[SkillTriggerType.Revive] as LvReviveEntry[] | undefined, lv);
|
||||
if (revive) {
|
||||
const reviveName = getTriggerSkillName(SkillTriggerType.Revive, revive.s_uuid);
|
||||
const reviveName = getReviveSkillName();
|
||||
const reviveHpPct = SkillSet[revive.s_uuid]?.ap ?? 50;
|
||||
lines.push(showName
|
||||
? `${skillNameRich(reviveName)}: 每回合拥有${valueRich(revive.r_num)}次重生机会, 死亡后以${valueRich(`${Math.round(reviveHpPct)}%`)}血量原地重生`
|
||||
@@ -757,3 +757,164 @@ export function buildCardDescRich(card: CardConfig): string {
|
||||
|
||||
return card.info || "";
|
||||
}
|
||||
|
||||
/* =====================================================================================
|
||||
* 按技能等级分段说明(升级引导 / 图鉴逐档预览)
|
||||
*
|
||||
* 与 buildSkillDesc(当前等级)/ buildSkillDescRich(全档位合并单行)并列的第三种模式:
|
||||
* 每个技能按等级档位逐段输出 —— 技能 Lv1 给完整能力+数值,Lv2 起只给相对上一级的
|
||||
* 「数值漂移 + 新功能」,让玩家一眼看清每一级"多得到了什么"。
|
||||
* ===================================================================================*/
|
||||
|
||||
/** 参与"新功能/数值漂移"对比的展示字段(排除动画/弹道/特效等玩家无感字段) */
|
||||
const DIFF_DISPLAY_KEYS: (keyof SkillOverrides)[] = [
|
||||
"ap", "hit_count", "num", "summon_count", "crt", "frz", "stun", "para",
|
||||
"bck", "bck_chance", "timed_buff_id", "buff_value", "buff_duration",
|
||||
];
|
||||
|
||||
/** 概率/次数类展示字段的单位与称谓(新功能标记 + 数值漂移描述共用) */
|
||||
const DIFF_FIELD_META: Partial<Record<keyof SkillOverrides, { label: string; unit: string }>> = {
|
||||
ap: { label: "伤害", unit: "%" },
|
||||
hit_count: { label: "命中", unit: "次" },
|
||||
num: { label: "双发", unit: "" },
|
||||
summon_count: { label: "召唤", unit: "" },
|
||||
crt: { label: "暴击", unit: "%" },
|
||||
frz: { label: "冰冻", unit: "%" },
|
||||
stun: { label: "击晕", unit: "%" },
|
||||
para: { label: "麻痹", unit: "%" },
|
||||
bck: { label: "击退", unit: "" },
|
||||
bck_chance: { label: "击退概率", unit: "%" },
|
||||
buff_duration: { label: "持续", unit: "秒" },
|
||||
};
|
||||
|
||||
/** 一个技能的等级档位(统一普攻 lv_entries 与触发技能 LvTriggerEntry 两种结构) */
|
||||
interface ISkillTier {
|
||||
/** 技能等级(展示用,取 s_lv,缺省按档位序号) */
|
||||
sLv: number;
|
||||
/** 该档生效的英雄等级(解锁提示) */
|
||||
heroLv: number;
|
||||
/** 合并底座后的完整技能配置 */
|
||||
skill: SkillConfig;
|
||||
}
|
||||
|
||||
/** 收集普攻/主技能的全部等级档位(按 sLv 升序)。Lv1=基础档(底+基础 overrides),Lv2+=lv_entries 各档 */
|
||||
function collectTiers(base: SkillConfig, baseOverrides: SkillOverrides | undefined,
|
||||
entries: { lv: number; s_lv?: number; overrides?: SkillOverrides }[]): ISkillTier[] {
|
||||
const tiers: ISkillTier[] = [];
|
||||
// Lv1 = 基础档(底 + 基础 overrides)
|
||||
const lv1Entry = entries.find(e => (e.s_lv ?? 0) === 1);
|
||||
tiers.push({
|
||||
sLv: lv1Entry?.s_lv ?? 1,
|
||||
heroLv: lv1Entry?.lv ?? 1,
|
||||
skill: mergeSkillParams(base, baseOverrides),
|
||||
});
|
||||
// Lv2+ = lv_entries 中 s_lv≥2 的档(与基础档浅合并,档位优先)
|
||||
for (const e of entries) {
|
||||
const sLv = e.s_lv ?? 0;
|
||||
if (sLv < 2) continue;
|
||||
tiers.push({ sLv, heroLv: e.lv, skill: mergeSkillParams(mergeSkillParams(base, baseOverrides), e.overrides) });
|
||||
}
|
||||
return tiers.sort((a, b) => a.sLv - b.sLv);
|
||||
}
|
||||
|
||||
/** 相邻两档的 diff:数值漂移(ap 等差值)+ 新功能(上级没有的 timed_buff/暴击/冰冻等) */
|
||||
function buildTierDiff(prev: SkillConfig, cur: SkillConfig): string[] {
|
||||
const parts: string[] = [];
|
||||
for (const key of DIFF_DISPLAY_KEYS) {
|
||||
const pv = (prev[key as keyof SkillConfig] as number | undefined) ?? 0;
|
||||
const cv = (cur[key as keyof SkillConfig] as number | undefined) ?? 0;
|
||||
if (cv === pv) continue;
|
||||
// 新功能:上级为 0/无,本档新增(含 timed_buff_id 新注入)
|
||||
if (key === "timed_buff_id") {
|
||||
if (cv > 0 && pv === 0) parts.push(`新增${valueRich("计时增益")}`);
|
||||
continue;
|
||||
}
|
||||
const meta = DIFF_FIELD_META[key];
|
||||
if (!meta) continue;
|
||||
if (pv === 0 && cv > 0) {
|
||||
parts.push(`新增${meta.label}${valueRich(`${cv}${meta.unit}`)}`);
|
||||
} else {
|
||||
const diff = cv - pv;
|
||||
const sign = diff > 0 ? "+" : "";
|
||||
parts.push(`${meta.label}${valueRich(`${sign}${diff}${meta.unit}`)}`);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
/** 单个技能的分段说明行(Lv1 完整 / Lv2+ diff),返回富文本行数组 */
|
||||
function buildTierLines(name: string, tiers: ISkillTier[], headPrefix: string): string[] {
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < tiers.length; i++) {
|
||||
const t = tiers[i];
|
||||
const head = `${skillNameRich(name)} ${valueRich(`Lv.${t.sLv}`)}${headPrefix}`;
|
||||
if (i === 0) {
|
||||
// 技能 1 级:完整能力 + 数值
|
||||
const line = buildEffectLine("", t.skill);
|
||||
const effect = `${line.prefix}${line.value ? valueRich(line.value) : ""}${line.suffix}`;
|
||||
lines.push(`${head}: ${highlightDuration(effect)}`);
|
||||
} else {
|
||||
// 技能 2 级起:相对上一级的数值漂移 + 新功能
|
||||
const diff = buildTierDiff(tiers[i - 1].skill, t.skill);
|
||||
const unlock = t.heroLv > 1 ? `(英雄${valueRich(t.heroLv)}级解锁)` : "";
|
||||
lines.push(diff.length > 0
|
||||
? `${head}${unlock}: ${diff.join(",")}`
|
||||
: `${head}${unlock}: 无变化`);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按技能等级分段输出说明(富文本)。
|
||||
*
|
||||
* 两种对外说明模式的第二种(第一种为 buildSkillDesc/buildSkillDescMoreRich 的"当前等级"模式):
|
||||
* 每个技能逐档输出 —— Lv1 给完整能力与数值,Lv2 起给相对上一级的提升(数值漂移 + 新功能),
|
||||
* 并标注解锁所需英雄等级。适合升级引导、图鉴逐档预览。
|
||||
*
|
||||
* @param source 英雄数据源(heroInfo 静态配置 或 HeroAttrsComp 运行时配置)
|
||||
* @returns 多行富文本串,\n 分隔;按技能分组、组内按技能等级升序
|
||||
*/
|
||||
export function buildSkillTierDescRich(source: ISkillDescSource): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
// ---- 普攻/主技能(skills):lv_entries 分档 ----
|
||||
if (source.skills) {
|
||||
for (const key in source.skills) {
|
||||
const hSkill = source.skills[key];
|
||||
if (!hSkill) continue;
|
||||
const base = SkillSet[hSkill.uuid];
|
||||
if (!base) continue;
|
||||
const entries = hSkill.lv_entries ?? [];
|
||||
// 无分档的单档技能只出 Lv1 完整行
|
||||
const tiers = collectTiers(base, hSkill.overrides, entries);
|
||||
lines.push(...buildTierLines(hSkill.name ?? base.name, tiers, ""));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 触发技能(atking/atked/dead/fstart/call):LvTriggerEntry 分档 ----
|
||||
for (const key of TRIGGER_KEYS) {
|
||||
const grouped = source[key] as TriggerGrouped | undefined;
|
||||
if (!grouped) continue;
|
||||
const trigName = SkillTriggerName[key] ?? key;
|
||||
const needCount = key === SkillTriggerType.Atking || key === SkillTriggerType.Atked;
|
||||
for (const sKey in grouped) {
|
||||
const entries = grouped[sKey];
|
||||
if (!entries?.length) continue;
|
||||
const base = SkillSet[Number(sKey)];
|
||||
if (!base) continue;
|
||||
const sorted = [...entries].sort((a, b) => (a.s_lv ?? 0) - (b.s_lv ?? 0));
|
||||
const name = getGroupedTriggerName(grouped, Number(sKey));
|
||||
// 触发技能以第 1 档为基础档,逐档 diff(t_num 变化并入新功能/数值漂移)
|
||||
const tiers: ISkillTier[] = sorted.map((e, i) => ({
|
||||
sLv: e.s_lv ?? (i + 1),
|
||||
heroLv: e.lv,
|
||||
skill: mergeSkillParams(base, e.overrides),
|
||||
}));
|
||||
const headPrefix = needCount && sorted[0] ? ` ${trigName}${valueRich(sorted[0].t_num)}次` : ` ${trigName}`;
|
||||
lines.push(...buildTierLines(name, tiers, headPrefix));
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user