Compare commits
3 Commits
8fa54e894a
...
bf697906ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf697906ae | ||
|
|
0457078cbb | ||
|
|
04f39217ac |
File diff suppressed because it is too large
Load Diff
@@ -1,143 +1,318 @@
|
||||
/**
|
||||
* @file HeroSkillDesc.ts
|
||||
* @description 英雄技能描述生成器
|
||||
* @description 英雄技能/能力描述的【唯一文字表达标准层】
|
||||
*
|
||||
* 职责:
|
||||
* 将英雄配置(heroInfo)中的特殊触发技能转换为可读的描述文本,
|
||||
* 供 HInfoComp 的 info_node 显示。每行一个技能,用换行符连接。
|
||||
* 设计目标:
|
||||
* heroSet / SkillSet 中的 info / infos 仅作"基础备注",不再承担面板展示。
|
||||
* 所有功能性描述(触发技能、驻场光环、复活、等级额外属性加成)统一由本模块
|
||||
* 依据结构化配置动态拼出,数值永远来自 mergeSkillParams 合并后的实时配置,
|
||||
* 杜绝"改数值忘改文案"的漂移。
|
||||
*
|
||||
* 生成格式:
|
||||
* {触发条件}:{技能名} {效果描述}
|
||||
* 例: "受击2次:护盾 护盾2次"
|
||||
* 例: "攻击1次:攻击强化 全体友方攻击力提升5点,持续1次"
|
||||
* 例: "场上存活:攻击加成 英雄攻击力+20%"
|
||||
* 输出(双格式,供 UI 按需取用):
|
||||
* - buildSkillDesc(source) → 纯文本(Label 用),数值无颜色
|
||||
* - buildSkillDescRich(source) → 富文本(RichText 用,BBCode),数值用 <color> 高亮
|
||||
* 两者共用同一套结构化行构建逻辑,仅渲染阶段区分,保证纯/富文本内容严格一致。
|
||||
*
|
||||
* 通用性(纯配置驱动):
|
||||
* 1. 触发框架:按触发类型遍历(SkillSet + overrides,不动)
|
||||
* 2. 效果文案:查 SkillDescSet 结构化模板渲染
|
||||
* 3. 目标/属性名:由本模块统一语义化(target/hit_count、ATTR_NAME)
|
||||
*
|
||||
* 数据源(入参抽象 ISkillDescSource):
|
||||
* heroInfo(静态 1 级配置)与 HeroAttrsComp(运行时应用 evolve 后的当前等级配置)
|
||||
* 字段结构一致,天然都满足。HeroBoxComp 传 model 显示当前等级;卡池/商店传 heroInfo 显示 1 级。
|
||||
*
|
||||
* 依赖:
|
||||
* - heroSet :SkillTriggerDesc(触发条件模板)、SkillTriggerType(触发类型枚举)、heroInfo
|
||||
* - SkillSet :SkillSet(技能基础配置)、mergeSkillParams(合并覆盖参数)、SkillKind(技能类型枚举)
|
||||
* - FieldSkillSet(驻场技能配置)
|
||||
* - heroSet :SkillTriggerDesc、SkillTriggerType、HeroEvolve
|
||||
* - SkillSet :SkillSet、mergeSkillParams、SkillOverrides、SkillKind、TGroup
|
||||
* - SkillDescSet:结构化描述模板
|
||||
* - FieldSkillSet(驻场技能)、BuffList(计时 buff 默认值兜底)、Attrs(属性枚举)
|
||||
*/
|
||||
import { heroInfo, SkillTriggerDesc, SkillTriggerType } from "./heroSet";
|
||||
import { FieldSkillSet, mergeSkillParams, SkillKind, SkillOverrides, SkillSet } from "./SkillSet";
|
||||
import { HeroEvolve, SkillTriggerDesc, SkillTriggerType } from "./heroSet";
|
||||
import { FieldSkillSet, mergeSkillParams, SkillConfig, SkillKind, SkillOverrides, SkillSet, TGroup } from "./SkillSet";
|
||||
import { FieldDescSet, FieldValueFmt, SkillDescSet } from "./SkillDescSet";
|
||||
import { BuffList } from "./BuffSet";
|
||||
import { Attrs } from "./HeroAttrs";
|
||||
|
||||
/**
|
||||
* 需要遍历的触发技能类型列表(不含 Field 和 Revive,这两者结构不同需单独处理)
|
||||
* Field:number[](驻场技能 uuid 列表)
|
||||
* Revive:{ s_uuid, r_num, upr }(单个对象)
|
||||
* 生成器入参窄接口:只约束实际读取的字段。
|
||||
* heroInfo 与 HeroAttrsComp 的对应字段结构完全一致,天然都满足此接口。
|
||||
*/
|
||||
const TRIGGER_KEYS: SkillTriggerType[] = [
|
||||
SkillTriggerType.Call, // 召唤时触发
|
||||
SkillTriggerType.Dead, // 死亡时触发
|
||||
SkillTriggerType.FStart, // 战斗开始时触发
|
||||
SkillTriggerType.FEnd, // 战斗结束时触发
|
||||
SkillTriggerType.Atking, // 攻击后触发
|
||||
SkillTriggerType.Atked, // 受击后触发
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据合并后的技能配置生成效果描述文本
|
||||
*
|
||||
* 按 SkillKind 分类输出:
|
||||
* - Heal :"治疗伙伴{ap}"
|
||||
* - Shield :"护盾{ap}次"(ap 表示可抵挡次数)
|
||||
* - Gold :"金币+{gold}"
|
||||
* - Support :直接使用技能自身的 info 字段(如"全体友方攻击力提升5点,持续1次")
|
||||
* - Damage :"伤害{ap}%" + 可选的暴击/冰冻/击退/多段附加描述
|
||||
*
|
||||
* @param skill 经过 mergeSkillParams 合并 overrides 后的完整技能配置
|
||||
* @returns 效果描述字符串,各部分用空格连接
|
||||
*/
|
||||
function buildEffectDesc(skill: ReturnType<typeof mergeSkillParams>): string {
|
||||
const parts: string[] = [];
|
||||
const kind = skill.kind;
|
||||
|
||||
if (kind === SkillKind.Heal) {
|
||||
// 治疗类:ap 为治疗量
|
||||
parts.push(`治疗伙伴${skill.ap}`);
|
||||
} else if (kind === SkillKind.Shield) {
|
||||
// 护盾类:ap 为可抵挡的攻击次数
|
||||
parts.push(`护盾${skill.ap}次`);
|
||||
} else if (kind === SkillKind.Gold) {
|
||||
// 金币类:gold 为获取金币数
|
||||
parts.push(`金币+${skill.gold ?? 0}`);
|
||||
} else if (kind === SkillKind.Support) {
|
||||
// 辅助类(buff):直接使用技能的 info 描述文案
|
||||
parts.push(String(skill.info));
|
||||
} else if (kind === SkillKind.Damage || kind === undefined) {
|
||||
// 伤害类(含无 kind 的默认技能):ap 为攻击力百分比
|
||||
parts.push(`伤害${skill.ap}%`);
|
||||
// 多段伤害
|
||||
if (skill.hit_count > 1) parts.push(`${skill.hit_count}段`);
|
||||
// 附加暴击率
|
||||
if (skill.crt) parts.push(`暴击+${skill.crt}%`);
|
||||
// 附加冰冻概率
|
||||
if (skill.frz) parts.push(`冰冻+${skill.frz}%`);
|
||||
if (skill.stun) parts.push(`击晕+${skill.stun}%`);
|
||||
// 附加击退概率
|
||||
if (skill.bck) parts.push(`击退+${skill.bck}%`);
|
||||
}
|
||||
|
||||
return parts.join(" ");
|
||||
export interface ISkillDescSource {
|
||||
/** 当前等级(用于计算 revive 实际复活次数、evolve 累计加成;静态 heroInfo 恒为 1) */
|
||||
lv?: number;
|
||||
call?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
dead?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
fstart?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
fend?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
atking?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
atked?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
field?: number[];
|
||||
revive?: { s_uuid: number; r_num: number; upr: number };
|
||||
/** 等级进化配置(heroInfo 自带;HeroAttrsComp 记录快照),用于累计 ap/hp 额外加成文案 */
|
||||
evolve?: Record<number, HeroEvolve>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将英雄的特殊触发技能配置转换为可读描述文本
|
||||
*
|
||||
* 处理流程:
|
||||
* 1. 遍历 6 种触发类型(call/dead/fstart/fend/atking/atked),从 hero[key] 取出
|
||||
* {s_uuid, t_num, overrides?}[],通过 s_uuid 查 SkillSet 获取技能基础配置,
|
||||
* 用 mergeSkillParams 合并 overrides 得到实际参数,再拼成一行描述。
|
||||
* 2. 单独处理 field(驻场技能):从 FieldSkillSet 查找配置,输出光环效果。
|
||||
* 3. 单独处理 revive(复活技能):结构与上述不同,是单个对象而非数组。
|
||||
*
|
||||
* 输出示例(5006 疾风刺客):
|
||||
* "攻击1次:攻击强化 全体友方攻击力提升5点,持续1次
|
||||
* 死亡时:攻击强化 全体友方攻击力提升8点,持续1次"
|
||||
*
|
||||
* @param hero 英雄静态配置(HeroInfo 中的条目)
|
||||
* @returns 多行技能描述文本,每行一个技能,用 \n 分隔
|
||||
* 一条能力的结构化行:数值片段单独抽出,供富文本高亮与纯文本复用。
|
||||
* 渲染结果 = prefix + value(可高亮) + suffix。
|
||||
*/
|
||||
export function buildSkillDesc(hero: heroInfo): string {
|
||||
const lines: string[] = [];
|
||||
interface ISkillLine {
|
||||
/** 数值前的文本(含触发条件、技能名、目标、动词等) */
|
||||
prefix: string;
|
||||
/** 需要高亮的核心数值文本(如 "15%"、"4次"、"+30"),无数值时为 "" */
|
||||
value: string;
|
||||
/** 数值后的文本(如 ",持续5秒" 之类尾部补充) */
|
||||
suffix: string;
|
||||
}
|
||||
|
||||
// ---- 第一步:处理 6 种标准触发技能(结构均为 {s_uuid, t_num, overrides?}[]) ----
|
||||
/** 需要遍历的触发技能类型列表(不含 Field 和 Revive,这两者结构不同需单独处理) */
|
||||
const TRIGGER_KEYS: SkillTriggerType[] = [
|
||||
SkillTriggerType.Call,
|
||||
SkillTriggerType.Dead,
|
||||
SkillTriggerType.FStart,
|
||||
SkillTriggerType.FEnd,
|
||||
SkillTriggerType.Atking,
|
||||
SkillTriggerType.Atked,
|
||||
];
|
||||
|
||||
/** 属性标准中文名映射(统一称谓,供 evolve 额外加成文案使用),仅覆盖 ap_bonus/hp_bonus 用到的属性 */
|
||||
const ATTR_NAME: Partial<Record<Attrs, string>> = {
|
||||
[Attrs.ap]: "攻击力",
|
||||
[Attrs.hp_max]: "最大生命",
|
||||
};
|
||||
|
||||
/** 富文本数值高亮色(深绿色,与项目绿色系同源,深色面板上清晰可读) */
|
||||
const RICH_VALUE_COLOR = "#1E8449";
|
||||
|
||||
/** 解析目标描述文本(与战斗 SCastSystem 目标选取语义严格一致) */
|
||||
function buildTargetDesc(skill: SkillConfig): string {
|
||||
switch (skill.TGroup) {
|
||||
case TGroup.Self:
|
||||
return "自身";
|
||||
case TGroup.Enemy:
|
||||
return "敌方";
|
||||
case TGroup.Ally:
|
||||
case TGroup.All:
|
||||
return "全体友方";
|
||||
case TGroup.Team: {
|
||||
const n = Math.max(1, Math.floor(skill.hit_count ?? 1));
|
||||
// 治疗类命中最缺血队友(SCastSystem.pickHealTargetsByMostMissingHp)
|
||||
if (skill.kind === SkillKind.Heal) {
|
||||
return n > 1 ? `${n}名最缺血队友` : "最缺血队友";
|
||||
}
|
||||
return `随机${n}名队友`;
|
||||
}
|
||||
default:
|
||||
return "全体友方";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 占位符模板替换,并把"核心数值片段"用 \x01...\x02 标记,便于上层拆分高亮。
|
||||
*
|
||||
* Why: 模板中 {value}{unit} 是玩家最关心的数值。这里在渲染时将其单独包裹,
|
||||
* 使富文本可对其单独上色,纯文本则剥离标记后原样拼接,保证两种输出内容一致。
|
||||
*
|
||||
* @returns 含 \x01hlValue\x02 标记的完整文本
|
||||
*/
|
||||
function renderTemplate(templ: string, vars: Record<string, string | number>, hlValue: string): string {
|
||||
let out = templ;
|
||||
for (const key of Object.keys(vars)) {
|
||||
out = out.split(`{${key}}`).join(String(vars[key]));
|
||||
}
|
||||
// 用 \x01...\x02 包裹数值片段作为标记,便于上层拆分为 prefix/value/suffix
|
||||
return out.split(hlValue).join(`\x01${hlValue}\x02`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 由一行模板渲染结果构造结构化行(剥离 \x01\x02 标记,把数值片段拆到 value 字段)
|
||||
* @param head 行首(触发条件:技能名 )
|
||||
* @param rendered 已用 \x01...\x02 标记数值片段的效果文本
|
||||
*/
|
||||
function makeLine(head: string, rendered: string): ISkillLine {
|
||||
const start = rendered.indexOf("\x01");
|
||||
const end = rendered.indexOf("\x02");
|
||||
if (start >= 0 && end > start) {
|
||||
// 恰好含一个数值片段:prefix=行首+数值前文本,value=数值,suffix=数值后文本
|
||||
return {
|
||||
prefix: `${head}${rendered.slice(0, start)}`,
|
||||
value: rendered.slice(start + 1, end),
|
||||
suffix: rendered.slice(end + 1),
|
||||
};
|
||||
}
|
||||
// 无数值片段(未命中模板或兜底 info):剥离残留标记,整行作为 prefix,不高亮
|
||||
return { prefix: `${head}${rendered.replace(/[\x01\x02]/g, "")}`, value: "", suffix: "" };
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据合并后的技能配置构造结构化效果行(查 SkillDescSet 模板渲染 + 数值高亮抽取)
|
||||
*
|
||||
* 计时模式默认值兜底:
|
||||
* overrides 仅给 timed_buff_id 而未覆写 buff_value / buff_duration 时,
|
||||
* 从 BuffList[timed_buff_id] 取默认值,保证 1 级基础档文案完整。
|
||||
*/
|
||||
function buildEffectLine(head: string, skill: SkillConfig): ISkillLine {
|
||||
const desc = SkillDescSet[skill.uuid];
|
||||
// 未入表技能回退基础备注 info,无法抽出数值,整行不高亮
|
||||
if (!desc) return { prefix: `${head}${String(skill.info ?? "")}`, value: "", suffix: "" };
|
||||
|
||||
const buff = skill.timed_buff_id !== undefined ? BuffList[skill.timed_buff_id] : undefined;
|
||||
const isTimed = buff !== undefined && !!desc.templTimed;
|
||||
const templ = isTimed ? desc.templTimed! : desc.templPerm;
|
||||
const valueField = isTimed ? (desc.timedField ?? desc.permField) : desc.permField;
|
||||
|
||||
// 计时模式默认值兜底:buff_value 未覆写时用 BuffList 默认修饰值
|
||||
let value = (skill[valueField as keyof SkillConfig] as number | undefined);
|
||||
if (value === undefined && isTimed) {
|
||||
value = buff!.modifiers?.[0]?.value ?? buff!.tick?.damage_or_heal ?? 0;
|
||||
}
|
||||
value = value ?? 0;
|
||||
const dur = skill.buff_duration ?? buff?.duration ?? 0;
|
||||
|
||||
// 高亮片段 = 数值 + 单位(如 "15%"、"4次"、"+30"),是玩家视觉焦点
|
||||
const hlValue = `${value}${desc.unit}`;
|
||||
const rendered = renderTemplate(templ, {
|
||||
target: buildTargetDesc(skill),
|
||||
value: value,
|
||||
unit: desc.unit,
|
||||
verb: desc.verb,
|
||||
dur: dur,
|
||||
}, hlValue);
|
||||
return makeLine(head, rendered);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驻场光环(field)结构化行:查 FieldDescSet 按 type 渲染,value 实时取数值并高亮。
|
||||
*
|
||||
* Why: FieldSkillConfig.info 是手写文案,改 value 易漂移。这里以 FieldSkillType
|
||||
* 为 key 查标准动词,数值按 fmt(比例/整数/减免)从 value 实时渲染。
|
||||
*/
|
||||
function buildFieldLine(head: string, uuid: number): ISkillLine | null {
|
||||
const fs = FieldSkillSet[uuid];
|
||||
if (!fs) return null;
|
||||
const desc = FieldDescSet[fs.type];
|
||||
// 未入表类型回退基础备注 info,整行不高亮
|
||||
if (!desc) return { prefix: `${head}${fs.name} ${fs.info}`, value: "", suffix: "" };
|
||||
|
||||
// 数值按格式渲染为高亮片段
|
||||
let valueText: string;
|
||||
switch (desc.fmt) {
|
||||
case FieldValueFmt.Percent:
|
||||
valueText = `+${Math.round(fs.value * 100)}%`;
|
||||
break;
|
||||
case FieldValueFmt.IntMinus:
|
||||
valueText = `-${fs.value}`;
|
||||
break;
|
||||
case FieldValueFmt.Int:
|
||||
default:
|
||||
valueText = `+${fs.value}`;
|
||||
break;
|
||||
}
|
||||
return { prefix: `${head}${fs.name} ${desc.verb}`, value: valueText, suffix: "" };
|
||||
}
|
||||
|
||||
/**
|
||||
* 复活(revive)结构化行:复刻战斗公式,高亮复活次数与回血百分比
|
||||
* 复活次数 = r_num + floor((lv - 1) * upr);回血百分比 = SkillSet[s_uuid].ap
|
||||
*/
|
||||
function buildReviveLine(source: ISkillDescSource): ISkillLine | null {
|
||||
const revive = source.revive;
|
||||
if (!revive) return null;
|
||||
const base = SkillSet[revive.s_uuid];
|
||||
if (!base) return null;
|
||||
const lv = Math.max(1, source.lv ?? 1);
|
||||
const maxCount = revive.r_num + Math.floor((lv - 1) * revive.upr);
|
||||
const hpPct = base.ap ?? 0;
|
||||
const tpl = SkillTriggerDesc[SkillTriggerType.Revive] ?? "复活时";
|
||||
// 高亮片段选回血百分比(数值中最关键的收益指标)
|
||||
return {
|
||||
prefix: `${tpl}:${base.name} 可复活${maxCount}次,每次恢复`,
|
||||
value: `${hpPct}%`,
|
||||
suffix: "生命",
|
||||
};
|
||||
}
|
||||
|
||||
/** evolve 额外属性加成(ap/hp)累计结构化行:高亮累计数值 */
|
||||
function buildEvolveBonusLines(source: ISkillDescSource): ISkillLine[] {
|
||||
const evolve = source.evolve;
|
||||
if (!evolve) return [];
|
||||
const lv = Math.max(1, source.lv ?? 1);
|
||||
let apTotal = 0;
|
||||
let hpTotal = 0;
|
||||
for (let elv = 2; elv <= lv; elv++) {
|
||||
const evo = evolve[elv];
|
||||
if (!evo) continue;
|
||||
apTotal += evo.ap_bonus ?? 0;
|
||||
hpTotal += evo.hp_bonus ?? 0;
|
||||
}
|
||||
const lines: ISkillLine[] = [];
|
||||
if (apTotal !== 0) lines.push({ prefix: "成长:额外", value: `+${apTotal}`, suffix: ATTR_NAME[Attrs.ap] ?? "攻击力" });
|
||||
if (hpTotal !== 0) lines.push({ prefix: "成长:额外", value: `+${hpTotal}`, suffix: ATTR_NAME[Attrs.hp_max] ?? "最大生命" });
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建全部能力结构化行(触发技能 + 驻场光环 + 复活 + evolve 加成)。
|
||||
* 纯文本与富文本两个公开接口共用此结果,仅渲染阶段不同。
|
||||
*/
|
||||
function buildSkillLines(source: ISkillDescSource): ISkillLine[] {
|
||||
const lines: ISkillLine[] = [];
|
||||
|
||||
// ---- 6 种标准触发技能 ----
|
||||
for (const key of TRIGGER_KEYS) {
|
||||
const arr = hero[key] as { s_uuid: number; t_num: number; overrides?: SkillOverrides }[] | undefined;
|
||||
const arr = source[key] as { s_uuid: number; t_num: number; overrides?: SkillOverrides }[] | undefined;
|
||||
if (!arr?.length) continue;
|
||||
// 从 SkillTriggerDesc 取模板,如 "攻击n次"、"受击n次"、"召唤时" 等
|
||||
const tpl = SkillTriggerDesc[key] ?? key;
|
||||
for (const item of arr) {
|
||||
// 通过 s_uuid 查找技能基础配置
|
||||
const base = SkillSet[item.s_uuid];
|
||||
if (!base) continue;
|
||||
// 合并 overrides 得到实际技能参数(ap/hit_count/crt/frz/stun/bck 等可能被覆盖)
|
||||
const skill = mergeSkillParams(base, item.overrides);
|
||||
// 将模板中的 "n" 替换为实际触发次数
|
||||
const trigger = tpl.replace("n", String(item.t_num));
|
||||
lines.push(`${trigger}:${base.name} ${buildEffectDesc(skill)}`);
|
||||
lines.push(buildEffectLine(`${trigger}:${base.name} `, skill));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 第二步:处理驻场技能(field)—— 结构为 number[],uuid 指向 FieldSkillSet ----
|
||||
const fieldUuids = hero[SkillTriggerType.Field] as number[] | undefined;
|
||||
// ---- 驻场光环(field):查 FieldDescSet 结构化渲染,value 数值高亮 ----
|
||||
const fieldUuids = source[SkillTriggerType.Field] as number[] | undefined;
|
||||
if (fieldUuids?.length) {
|
||||
const tpl = SkillTriggerDesc[SkillTriggerType.Field] ?? "场上存活";
|
||||
for (const uuid of fieldUuids) {
|
||||
const fs = FieldSkillSet[uuid];
|
||||
if (fs) lines.push(`${tpl}:${fs.name} ${fs.info}`);
|
||||
const line = buildFieldLine(`${tpl}:`, uuid);
|
||||
if (line) lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 第三步:处理复活技能(revive)—— 结构为 {s_uuid, r_num, upr} 单个对象 ----
|
||||
const revive = hero[SkillTriggerType.Revive] as { s_uuid: number; r_num: number; upr: number } | undefined;
|
||||
if (revive) {
|
||||
const base = SkillSet[revive.s_uuid];
|
||||
if (base) {
|
||||
const skill = mergeSkillParams(base);
|
||||
const tpl = SkillTriggerDesc[SkillTriggerType.Revive] ?? "复活时";
|
||||
lines.push(`${tpl} : ${base.name} ${buildEffectDesc(skill)}`);
|
||||
}
|
||||
}
|
||||
// ---- 复活(revive)----
|
||||
const reviveLine = buildReviveLine(source);
|
||||
if (reviveLine) lines.push(reviveLine);
|
||||
|
||||
return lines.join("\n");
|
||||
// ---- evolve 额外属性加成(ap/hp)----
|
||||
lines.push(...buildEvolveBonusLines(source));
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 纯文本描述(Label 用):数值无颜色。
|
||||
* @param source 触发技能数据源(heroInfo 静态配置 或 HeroAttrsComp 运行时当前等级配置)
|
||||
* @returns 多行纯文本,每行一条能力,用 \n 分隔
|
||||
*/
|
||||
export function buildSkillDesc(source: ISkillDescSource): string {
|
||||
return buildSkillLines(source)
|
||||
.map(l => `${l.prefix}${l.value}${l.suffix}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 富文本描述(RichText 用,BBCode):数值用 <color> 高亮。
|
||||
* @param source 触发技能数据源(同 buildSkillDesc)
|
||||
* @returns 多行富文本串,数值片段包裹 <color=#1E8449>,用 \n 分隔
|
||||
*/
|
||||
export function buildSkillDescRich(source: ISkillDescSource): string {
|
||||
return buildSkillLines(source)
|
||||
.map(l => l.value
|
||||
? `${l.prefix}<color=${RICH_VALUE_COLOR}>${l.value}</color>${l.suffix}`
|
||||
: `${l.prefix}${l.suffix}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
150
assets/script/game/common/config/SkillDescSet.ts
Normal file
150
assets/script/game/common/config/SkillDescSet.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @file SkillDescSet.ts
|
||||
* @description 技能描述结构化配置表(方案 B:独立描述表,纯 UI 用)
|
||||
*
|
||||
* 职责:
|
||||
* 为触发技能提供"结构化字段 + 占位符模板",供 HeroSkillDesc.buildSkillDesc
|
||||
* 在合并 overrides 后动态拼出效果文案。杜绝 SkillConfig.info 写死数值导致的
|
||||
* "改数值忘改文案"漂移。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 与 SkillSet 同 key(skill uuid),但只覆盖需要描述的触发技能;
|
||||
* 普攻/怪物技能不入表,由生成器回退旧 info 兜底。
|
||||
* - 数值永远从 mergeSkillParams 后的实时 skill 取值(permField/timedField),
|
||||
* overrides 改了数值,文案自动同步。
|
||||
* - 永久模式(overrides 直接改写 ap 等)与计时模式(overrides 走 timed_buff_id)
|
||||
* 分别用 templPerm / templTimed 两套模板。
|
||||
*
|
||||
* 模板占位符:
|
||||
* {target} 目标(自身/随机N名队友/全体友方/敌方)
|
||||
* {value} 取值(来自 permField 或 timedField 指向的字段)
|
||||
* {unit} 单位(""/%/次)
|
||||
* {verb} 效果动词(攻击力/暴击率/护盾…)
|
||||
* {dur} 持续时间秒数(计时模式用,取 buff_duration)
|
||||
*/
|
||||
import { FieldSkillType, SkillOverrides } from "./SkillSet";
|
||||
|
||||
/**
|
||||
* 单个技能的结构化描述配置
|
||||
*/
|
||||
export interface SkillDescConfig {
|
||||
/** 效果动词,如 "攻击力"/"暴击率"/"护盾" */
|
||||
verb: string;
|
||||
/** 单位,如 ""/"%"/"次" */
|
||||
unit: string;
|
||||
/** 永久模式取值字段(overrides 直接改写的字段),通常 "ap" */
|
||||
permField: keyof SkillOverrides;
|
||||
/** 计时模式取值字段(overrides 走 timed_buff_id 时),通常 "buff_value" */
|
||||
timedField?: keyof SkillOverrides;
|
||||
/** 永久模式文案模板 */
|
||||
templPerm: string;
|
||||
/** 计时模式文案模板(含 {dur}),不填则该技能不支持计时模式 */
|
||||
templTimed?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 技能描述表,key = SkillSet 的技能 uuid
|
||||
* 首批覆盖现役英雄涉及的触发技能;未入表技能由生成器回退 skill.info
|
||||
*/
|
||||
export const SkillDescSet: Record<number, SkillDescConfig> = {
|
||||
// 6301 护盾:ap = 可格挡次数
|
||||
6301: {
|
||||
verb: "", unit: "次", permField: "ap",
|
||||
templPerm: "{target}获得{value}{unit}格挡护盾",
|
||||
},
|
||||
// 6302 治疗:ap = 攻击力百分比
|
||||
6302: {
|
||||
verb: "", unit: "", permField: "ap",
|
||||
templPerm: "{target}回复攻击力{value}%生命",
|
||||
},
|
||||
// 6401 攻击强化:永久 ap=攻击点;计时 buff_value=攻击点
|
||||
6401: {
|
||||
verb: "攻击力", unit: "", permField: "ap", timedField: "buff_value",
|
||||
templPerm: "{target}{verb}+{value}",
|
||||
templTimed: "{target}{verb}+{value},持续{dur}秒",
|
||||
},
|
||||
// 6402 生命强化:ap = 最大生命点
|
||||
6402: {
|
||||
verb: "最大生命", unit: "", permField: "ap",
|
||||
templPerm: "{target}{verb}+{value}",
|
||||
},
|
||||
// 6403 暴击强化:永久 ap=暴击%;计时 buff_value=暴击%
|
||||
6403: {
|
||||
verb: "暴击率", unit: "%", permField: "ap", timedField: "buff_value",
|
||||
templPerm: "{target}{verb}+{value}{unit}",
|
||||
templTimed: "{target}{verb}+{value}{unit},持续{dur}秒",
|
||||
},
|
||||
// 6404 暴伤强化:ap = 暴击伤害%
|
||||
6404: {
|
||||
verb: "暴击伤害", unit: "%", permField: "ap",
|
||||
templPerm: "{target}{verb}+{value}{unit}",
|
||||
},
|
||||
// 6405 击晕强化:ap = 击晕概率%
|
||||
6405: {
|
||||
verb: "击晕概率", unit: "%", permField: "ap",
|
||||
templPerm: "{target}{verb}+{value}{unit}",
|
||||
},
|
||||
// 6408 穿刺强化:ap = 穿刺概率%
|
||||
6408: {
|
||||
verb: "穿刺概率", unit: "%", permField: "ap",
|
||||
templPerm: "{target}{verb}+{value}{unit}",
|
||||
},
|
||||
// 6409 风怒强化:ap = 风怒次数
|
||||
6409: {
|
||||
verb: "风怒次数", unit: "次", permField: "ap",
|
||||
templPerm: "{target}{verb}+{value}{unit}",
|
||||
},
|
||||
// 6501 复活:ap = 复活生命值百分比
|
||||
6501: {
|
||||
verb: "", unit: "", permField: "ap",
|
||||
templPerm: "{target}以{value}%生命复活",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 驻场光环数值格式:比例型(value 0.1 → +10%)或整数型(value 1 → +1)
|
||||
*/
|
||||
export enum FieldValueFmt {
|
||||
/** 比例:value*100 取整 + "%"(如 0.1 → "+10%") */
|
||||
Percent = 1,
|
||||
/** 整数:直接 +N(如 1 → "+1") */
|
||||
Int = 2,
|
||||
/** 整数减:直接 -N(如 1 → "-1",用于费用减免) */
|
||||
IntMinus = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个驻场光环的结构化描述配置(文字表达标准层统一称谓)
|
||||
*/
|
||||
export interface FieldDescConfig {
|
||||
/** 效果动词,如 "英雄攻击力"/"召唤触发技能次数" */
|
||||
verb: string;
|
||||
/** 数值格式(决定 value 如何渲染为高亮片段) */
|
||||
fmt: FieldValueFmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 驻场光环描述表,key = FieldSkillType。
|
||||
* 数值永远取 FieldSkillConfig.value 实时渲染,替代手写 info,杜绝漂移。
|
||||
*/
|
||||
export const FieldDescSet: Record<number, FieldDescConfig> = {
|
||||
[FieldSkillType.SummonCount]: { verb: "召唤触发技能次数", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.DeadCount]: { verb: "死亡触发技能次数", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.StartCount]: { verb: "战斗开始触发技能次数", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.EndCount]: { verb: "战斗结束触发技能次数", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.WaveGold]: { verb: "每回合金币收益", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.SellGold]: { verb: "卖出英雄金币", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.WaveHeal]: { verb: "战斗结束生命回复量", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroAtk]: { verb: "英雄攻击力", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroStun]: { verb: "英雄击晕概率", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroCrit]: { verb: "英雄暴击率", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroCritDamage]: { verb: "英雄暴击伤害", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroSpeed]: { verb: "英雄攻击速度", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.BuyDiscount]: { verb: "购买卡牌费用", fmt: FieldValueFmt.IntMinus },
|
||||
[FieldSkillType.RefreshDiscount]: { verb: "刷新卡牌费用", fmt: FieldValueFmt.IntMinus },
|
||||
[FieldSkillType.HeroHp]: { verb: "英雄最大生命", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroWindFury]: { verb: "英雄风怒概率", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.HeroPuncture]: { verb: "英雄穿刺概率", fmt: FieldValueFmt.Percent },
|
||||
[FieldSkillType.AtkCount]: { verb: "攻击触发技能次数", fmt: FieldValueFmt.Int },
|
||||
[FieldSkillType.BeAtkCount]: { verb: "被攻击触发技能次数", fmt: FieldValueFmt.Int },
|
||||
};
|
||||
9
assets/script/game/common/config/SkillDescSet.ts.meta
Normal file
9
assets/script/game/common/config/SkillDescSet.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d60c5899-0170-4269-bc3d-3bea21badd0d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -166,6 +166,8 @@ export class Hero extends ecs.Entity {
|
||||
|
||||
// 应用等级进化配置(从 lv2 开始逐级覆盖)
|
||||
if (hero.evolve) {
|
||||
// 记录 evolve 配置快照(只读引用),供 UI 计算 ap/hp 额外加成文案
|
||||
model.evolve = hero.evolve;
|
||||
for (let elv = 2; elv <= hero_lv; elv++) {
|
||||
const evo = hero.evolve[elv];
|
||||
if (!evo) continue;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType } from "../common/config/heroSet";
|
||||
import { HeroDisVal, HeroInfo, HSkillInfo, HType, SkillTriggerType, HeroEvolve } from "../common/config/heroSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
|
||||
@@ -52,6 +52,9 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
[SkillTriggerType.Field]?: number[];
|
||||
[SkillTriggerType.Revive]?: { s_uuid: number, r_num: number, upr: number };
|
||||
|
||||
/** 等级进化配置快照(引用 heroInfo.evolve,只读;供 UI 计算 ap/hp 额外加成文案) */
|
||||
evolve?: Record<number, HeroEvolve>;
|
||||
|
||||
// ==================== 特殊属性 ====================
|
||||
critical: number = 0; // 暴击率
|
||||
critical_res: number = 0; // 暴击抗性
|
||||
@@ -545,6 +548,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
this.atking = undefined;
|
||||
this.atked = undefined;
|
||||
this.revive = undefined;
|
||||
this.evolve = undefined;
|
||||
this.critical = 0;
|
||||
this.critical_res = 0;
|
||||
this.freeze_chance = 0;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* - HeroInfo(heroSet)—— 英雄静态配置
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, Node, Sprite, Label, resources, AnimationClip, SpriteFrame, NodeEventType, UITransform, Tween, tween, Vec3 } from "cc";
|
||||
import { _decorator, Node, Sprite, Label, RichText, resources, AnimationClip, SpriteFrame, NodeEventType, UITransform, Tween, tween, Vec3 } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
@@ -20,6 +20,7 @@ import { GameEvent } from "../common/config/GameEvent";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { getLvColor } from "../common/config/GameSet";
|
||||
import { buildSkillDescRich } from "../common/config/HeroSkillDesc";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@@ -52,6 +53,12 @@ export class HeroBoxComp extends CCComp {
|
||||
@property(Node)
|
||||
private noHero: Node = null;
|
||||
|
||||
/** 技能描述富文本(多行,展示当前等级触发技能,数值高亮) */
|
||||
@property(RichText)
|
||||
private skill_label: RichText = null;
|
||||
/** 技能描述富文本内容缓存(避免 refresh 降频内重复重解析排版) */
|
||||
private skillDescCache: string = "";
|
||||
|
||||
// ======================== 战斗信息标签(总值 + 附加值) ========================
|
||||
|
||||
/** 攻击总值 */
|
||||
@@ -321,6 +328,10 @@ export class HeroBoxComp extends CCComp {
|
||||
this.clearStatRow(this.freeze_all_label, this.freeze_plus_label);
|
||||
this.clearStatRow(this.puncture_all_label, this.puncture_plus_label);
|
||||
this.clearStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label);
|
||||
if (this.skill_label && this.skill_label.isValid) {
|
||||
this.skill_label.string = "";
|
||||
}
|
||||
this.skillDescCache = "";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,6 +399,16 @@ export class HeroBoxComp extends CCComp {
|
||||
const finalCritDmg = this.model.getFinalCritDamage();
|
||||
this.setStatRow(this.crit_dmg_all_label, this.crit_dmg_plus_label, finalCritDmg, finalCritDmg - (this.model.crit_damage ?? 0), "%");
|
||||
|
||||
// ---- 技能描述(当前等级触发技能,读运行时 model 最终配置;数值富文本高亮) ----
|
||||
if (this.skill_label && this.skill_label.isValid) {
|
||||
// 缓存未变则跳过重设,避免 refresh 降频内 RichText 重复重解析排版
|
||||
const desc = buildSkillDescRich(this.model);
|
||||
if (desc !== this.skillDescCache) {
|
||||
this.skillDescCache = desc;
|
||||
this.skill_label.string = desc;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 图标(UUID 变化时重新加载首帧动画) ----
|
||||
if (hero && heroUuid !== this.iconHeroUuid) {
|
||||
this.iconHeroUuid = heroUuid;
|
||||
|
||||
118
docs/superpowers/specs/2026-07-30-herobox-skill-desc-design.md
Normal file
118
docs/superpowers/specs/2026-07-30-herobox-skill-desc-design.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# HeroBox 当前等级技能信息面板设计(方案 B:独立 SkillDescSet 表)
|
||||
|
||||
## 背景与目标
|
||||
|
||||
HeroBoxComp 当前只展示数值属性(AP/HP/暴击/攻速…),没有技能信息区。需要在面板中加入**当前英雄当前等级**的技能描述。
|
||||
|
||||
关键诉求(用户已确认):
|
||||
- 抛弃 `heroSet` 的手写 `infos[]` 文案,改用**结构化字段 + overrides 动态拼描述**,杜绝"数值改了忘改文案"的漂移。
|
||||
- 描述必须反映**运行时当前等级**(evolve 已应用的最终配置),而非 1 级静态配置。
|
||||
- 数据与展示分离,不侵入战斗配置(SkillSet/FieldSkillSet 一行不动)。
|
||||
|
||||
## 现状分析(已验证)
|
||||
|
||||
- `HeroSkillDesc.buildSkillDesc(hero)` 已存在,且**触发框架已通用**:按触发类型遍历 → 查 `SkillSet` → `mergeSkillParams` 合并 overrides → 拼行。
|
||||
- 其 `buildEffectDesc` 按 `SkillKind` 硬编码 if-else,且 Support 类直接复用 `skill.info` 写死的数值文案 → **这是唯一不通用的点,也是漂移根因**。
|
||||
- `HeroAttrsComp` 运行时持有**应用 evolve 后的当前等级最终触发配置**(Hero.ts 中 base → 逐级 evolve 覆盖),字段结构与 `heroInfo` 完全一致。
|
||||
- `buildSkillDesc` 现有 2 个调用方(HersListComp:205、HInfoComp:448)都传静态 `heroInfo`(卡池/商店场景,无运行时 model)。**签名不能粗暴改成只收 model**。
|
||||
|
||||
## 方案 B 总体形态
|
||||
|
||||
三层都通用,纯配置驱动:
|
||||
|
||||
1. **触发框架**:不动(已通用)。
|
||||
2. **技能数值**:不动(SkillSet + overrides)。
|
||||
3. **效果文案**:新增 `SkillDescSet` 独立表,把硬编码 if-else 变成"结构化配置 + 占位符模板"。
|
||||
|
||||
结果:`buildSkillDesc(source)` 成为纯渲染器。**新增英雄 = 纯配置,零代码**;新增技能 = 补一行 SkillDescSet 配置。
|
||||
|
||||
## 设计细节
|
||||
|
||||
### 1. 生成器入参抽象(兼容两种数据源)
|
||||
|
||||
抽取窄接口,`heroInfo` 与 `HeroAttrsComp` 天然都满足(字段名、结构一致):
|
||||
|
||||
```ts
|
||||
export interface ISkillDescSource {
|
||||
call?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
dead?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
fstart?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
fend?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
atking?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
atked?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
|
||||
field?: number[];
|
||||
revive?: { s_uuid: number; r_num: number; upr: number };
|
||||
}
|
||||
// buildSkillDesc(source: ISkillDescSource): string
|
||||
```
|
||||
|
||||
- HeroBoxComp 传 `this.model` → 显示当前等级。
|
||||
- 卡池/商店传 `heroInfo` → 显示 1 级(无 model 场景),老调用方零改动。
|
||||
|
||||
### 2. SkillDescSet 结构化描述表(核心)
|
||||
|
||||
新文件 `assets/script/game/common/config/SkillDescSet.ts`,与 SkillSet 同 key、纯 UI 用,只覆盖需要描述的触发技能(普攻/怪物技能跳过):
|
||||
|
||||
```ts
|
||||
export interface SkillDescConfig {
|
||||
/** 效果动词,如 "攻击力"/"暴击率"/"护盾" */
|
||||
verb: string;
|
||||
/** 单位,如 ""/"%"/"次" */
|
||||
unit: string;
|
||||
/** 永久模式取值字段(overrides 直接改写),通常 "ap" */
|
||||
permField: keyof SkillOverrides;
|
||||
/** 计时模式取值字段(overrides 走 timed_buff_id),通常 "buff_value" */
|
||||
timedField?: keyof SkillOverrides;
|
||||
/** 永久模式文案模板 */
|
||||
templPerm: string;
|
||||
/** 计时模式文案模板(含 {dur}),不填则该技能不支持计时模式 */
|
||||
templTimed?: string;
|
||||
}
|
||||
```
|
||||
|
||||
模板占位符:`{target}` `{value}` `{unit}` `{verb}` `{dur}`。
|
||||
|
||||
渲染逻辑(生成器内统一处理):
|
||||
- `target`:TGroup.Self→"自身";Team+hit_count>1→"随机{hit_count}名队友";Team/Ally→"全体友方";Enemy→"敌方"。
|
||||
- 计时判定:合并后 skill 存在 `timed_buff_id` → 用 `templTimed`+`timedField`;否则 `templPerm`+`permField`。
|
||||
- `value`:从合并后 skill 对应字段取值;`dur`:`buff_duration` 值。
|
||||
- 查不到 SkillDescSet 条目 → 回退旧 `skill.info`(保证未迁移技能不报错、渐进迁移)。
|
||||
|
||||
### 3. HeroSkillDesc 改造
|
||||
|
||||
- 入参从 `heroInfo` 改为 `ISkillDescSource`。
|
||||
- 删除 `buildEffectDesc` 的 SkillKind 硬编码分支,改为查 `SkillDescSet[uuid]` 渲染。
|
||||
- `field`(驻场)继续直接读 `FieldSkillSet[uuid].info`(已结构化 type+value,无需改造)。
|
||||
- `revive` 用 6501 的模板渲染复活血量百分比。
|
||||
|
||||
### 4. HeroBoxComp 集成 + 迁移范围
|
||||
|
||||
**HeroBoxComp 改动**:
|
||||
- 新增 `@property(Label)` 技能描述节点(单 Label 多行,与 HInfoComp 一致;放属性栏下方)。
|
||||
- `refresh()` 追加:`skill_label.string = buildSkillDesc(this.model)`。
|
||||
- `applyEmpty()` 清空该 label。
|
||||
- **展示范围**:仅触发技能(atking/atked/dead/fstart/fend/field/revive)。普攻不展示——AP/攻速已由数值栏体现,触发技能才是英雄身份核心。
|
||||
|
||||
**迁移范围(首批)**:
|
||||
- SkillDescSet 首批覆盖现役英雄涉及的 ~10 个触发技能:6301/6302/6401/6402/6403/6404/6405/6501 + fstart 用的 7111(计时)。
|
||||
- `infos[]` 字段保留但不再在面板使用(卡池/详情页后续统一迁移,本次不动)。
|
||||
|
||||
## UI 取向(默认,可覆盖)
|
||||
|
||||
- 格式:单个多行 Label(最省改动,与 HInfoComp 一致)。
|
||||
- 范围:仅触发技能,不含普攻。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 动作 |
|
||||
|------|------|
|
||||
| `assets/script/game/common/config/SkillDescSet.ts` | 新建 |
|
||||
| `assets/script/game/common/config/HeroSkillDesc.ts` | 改造(入参抽象 + 查表渲染) |
|
||||
| `assets/script/game/map/HeroBoxComp.ts` | 集成技能描述 Label |
|
||||
|
||||
## 非目标(YAGNI)
|
||||
|
||||
- 不改造 SkillSet/FieldSkillSet 结构。
|
||||
- 不做图标/富文本富 UI(本次纯文本)。
|
||||
- 不迁移卡池/详情页的 `infos[]` 展示。
|
||||
- 普攻技能不做描述。
|
||||
Reference in New Issue
Block a user