refactor: 重构复活机制,将复活逻辑整合到dead触发组

1.  新增SkillOverrides的revive_hp和revive_count配置项,替代原独立复活字段
2.  移除所有revive相关的旧配置、解析函数和UI展示逻辑
3.  调整战斗逻辑,从dead触发组的overrides中读取复活配置
4.  更新文档说明,统一复活机制的配置方式
5.  清理冗余的导入和类型定义
This commit is contained in:
panFD
2026-09-03 22:28:38 +08:00
parent 87f771edd1
commit b0038e74c8
12 changed files with 120 additions and 131 deletions

View File

@@ -25,8 +25,8 @@
*/
import {
HeroInfo, heroInfo, SkillTriggerType, SkillTriggerName, HSkillInfo, LvTriggerEntry,
TriggerGrouped, resolveSkillOverrides, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv,
getGroupedTriggerName, getFieldSkillName, getReviveSkillName,
TriggerGrouped, resolveSkillOverrides, resolveTriggerByLv, resolveFieldByLv,
getGroupedTriggerName, getFieldSkillName,
} from "./config/heroSet";
import { SkillSet, mergeSkillParams, SkillConfig, SkillKind, TGroup, FieldSkillSet } from "./config/SkillSet";
import { BuffList } from "./config/BuffSet";
@@ -219,12 +219,16 @@ export function buildHeroSkillDesc(heroUuid: number, showName: boolean = true, s
lines.push(`${nameSeg(getFieldSkillName(hero, entry.uuid))}${desc.verb}${valueRich(val)}`);
}
// ---- 复活(revive):次数复刻战斗公式 r_num + floor((lv-1)*upr) ----
const revive = resolveReviveByLv(hero.revive, lv);
if (revive) {
const count = revive.r_num + Math.floor((lv - 1) * revive.upr);
const hpPct = SkillSet[revive.s_uuid]?.ap ?? 0;
lines.push(`${nameSeg(getReviveSkillName(hero, revive.s_uuid))}每回合可复活${valueRich(count)}次, 死亡后以${valueRich(`${Math.round(hpPct)}%`)}生命原地重生`);
// ---- 复活(已纳入 dead 触发组):从 dead 配置提取 revive_hp / revive_count ----
for (const item of resolveTriggerByLv(hero.dead, lv)) {
const reviveHp = item.overrides?.revive_hp;
const reviveCount = item.overrides?.revive_count ?? 0;
if (reviveHp === undefined || reviveCount <= 0) continue;
const base = SkillSet[item.s_uuid];
if (!base) continue;
const skill = mergeSkillParams(base, item.overrides);
const text = `每回合可复活${valueRich(reviveCount)}次, 死亡后以${valueRich(`${Math.round(reviveHp)}%`)}生命原地重生`;
lines.push(`${nameSeg(getGroupedTriggerName(hero.dead!, item.s_uuid))}${SkillTriggerName[SkillTriggerType.Dead]}, ${text}`);
}
return lines.join("\n");
@@ -392,32 +396,40 @@ function collectFieldTierLines(hero: heroInfo, fieldUuid: number): HeroSkillTier
}
/**
* 收集复活技能分档行(hero.revive 各英雄等级档,无 s_lv 概念,按档位序号编号)。
* slv1 给完整效果(复活次数 + 重生血量);slv2+ 给复活次数变化。
* 收集复活技能分档行(已纳入 dead 触发组,从 dead[skillUuid] 提取 revive_hp / revive_count)。
* slv1 给完整效果(复活次数 + 重生血量);slv2+ 给复活次数/血量变化。
* 档位配置了 desc 时直接采用。
*/
function collectReviveTierLines(hero: heroInfo, sUuid: number): HeroSkillTierLine[] {
const matched = (hero.revive ?? [])
.filter(e => e.s_uuid === sUuid)
const entries = hero.dead?.[sUuid];
if (!entries?.length) return [];
// 筛选带复活参数的档位,按 lv 升序
const matched = entries
.filter(e => e.overrides?.revive_hp !== undefined && (e.overrides?.revive_count ?? 0) > 0)
.sort((a, b) => a.lv - b.lv);
if (matched.length === 0) return [];
const lines: HeroSkillTierLine[] = [];
let prevCount: number | undefined;
let prevHp: number | undefined;
matched.forEach((e, i) => {
// 复活次数复刻战斗公式:r_num + floor((英雄等级-1)*upr)
const count = e.r_num + Math.floor((e.lv - 1) * e.upr);
const hpPct = Math.round(SkillSet[e.s_uuid]?.ap ?? 0);
const count = e.overrides!.revive_count!;
const hpPct = Math.round(e.overrides!.revive_hp!);
if (e.desc) {
lines.push({ sLv: i + 1, heroLv: e.lv, text: e.desc });
} else if (i === 0) {
lines.push({ sLv: 1, heroLv: e.lv, text: `每回合可复活${valueRich(count)}次, 死亡后以${valueRich(`${hpPct}%`)}生命原地重生` });
} else if (prevCount !== undefined && count !== prevCount) {
lines.push({ sLv: i + 1, heroLv: e.lv, text: `复活次数${valueRich(prevCount)}次提升至${valueRich(count)}次` });
} else if (prevCount !== undefined && (count !== prevCount || hpPct !== prevHp)) {
const parts: string[] = [];
if (count !== prevCount) parts.push(`复活次数${valueRich(prevCount)}次提升至${valueRich(count)}次`);
if (hpPct !== prevHp) parts.push(`回血比例${valueRich(prevHp)}%提升至${valueRich(hpPct)}%`);
lines.push({ sLv: i + 1, heroLv: e.lv, text: parts.join(",") });
} else {
lines.push({ sLv: i + 1, heroLv: e.lv, text: "无变化" });
}
prevCount = count;
prevHp = hpPct;
});
return lines;
}
@@ -487,7 +499,7 @@ function buildTierLines(tiers: ISkillTierInfo[], triggerLabel: string): HeroSkil
* 1. hero.skills[skillUuid] —— 普攻/大招
* 2. 5 类触发技能组 —— call/dead/fstart/atking/atked
* 3. hero.field —— 驻场光环词条(FieldSkillSet uuid)
* 4. hero.revive —— 复活技能(SkillSet uuid)
* 4. hero.dead 中的复活技能 —— 已纳入 dead 触发组(revive_hp / revive_count)
* 均未命中返回 []。
*
* Why: 同一底座技能可能被多个英雄以不同 overrides 引用,必须以英雄语境取档;
@@ -503,9 +515,8 @@ export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number, slo
const hero = HeroInfo[heroUuid];
if (!hero) return [];
// 槽位指定:光环/复活结构特殊直接生成行;触发组走统一档位结构
// 槽位指定:光环结构特殊直接生成行;触发组走统一档位结构(复活已纳入 dead)
if (slot === SkillTriggerType.Field) return collectFieldTierLines(hero, skillUuid);
if (slot === SkillTriggerType.Revive) return collectReviveTierLines(hero, skillUuid);
if (slot !== undefined) {
const hit = collectTriggerTiers(hero, slot, skillUuid);
return hit ? buildTierLines(hit.tiers, hit.label) : [];
@@ -530,12 +541,11 @@ export function buildHeroSkillTierLines(heroUuid: number, skillUuid: number, slo
}
}
// 光环/复活路径(无 ISkillTierInfo 中间结构,直接生成 HeroSkillTierLine)
// 光环路径(无 ISkillTierInfo 中间结构,直接生成 HeroSkillTierLine)
if (tiers.length === 0) {
const fieldLines = collectFieldTierLines(hero, skillUuid);
if (fieldLines.length > 0) return fieldLines;
const reviveLines = collectReviveTierLines(hero, skillUuid);
if (reviveLines.length > 0) return reviveLines;
// 复活已纳入 dead 触发组,通过 dead 路径处理
return [];
}
@@ -580,9 +590,9 @@ function resolveTriggerSkillName(hero: heroInfo, skillUuid: number): string {
if (hero.field?.some(e => e.uuids.some(u => u.uuid === skillUuid))) {
return getFieldSkillName(hero, skillUuid);
}
// 复活技能:名称取英雄语境(LvReviveEntry.name 优先,回退固定称谓)
if (hero.revive?.some(e => e.s_uuid === skillUuid)) {
return getReviveSkillName(hero, skillUuid);
// 复活技能:名称取英雄语境(dead 组名,回退底座名)
if (hero.dead?.[skillUuid]?.length) {
return getGroupedTriggerName(hero.dead, skillUuid);
}
return SkillSet[skillUuid]?.name ?? "";
}

View File

@@ -184,6 +184,10 @@ export interface SkillOverrides {
is_accel?: boolean;
/** 单次施法额外释放次数(0=单次,1=双发,2=三发;弹道由 resolveRepeatCastTargetPos 分散上/中/下三路) */
num?: number;
/** 复活回血比例(%),有值表示死亡时触发复活(仅 dead 触发有效) */
revive_hp?: number;
/** 复活次数上限,配合 revive_hp 使用(仅 dead 触发有效) */
revive_count?: number;
}
/**

View File

@@ -1,4 +1,4 @@
import { v3 } from "cc"
import { v3 } from "cc"
import { BoxSet, FacSet, FightSet } from "./GameSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet"
import { BuffList, BuffConfig, ModOp } from "./BuffSet"
@@ -285,17 +285,9 @@ export function getFieldSkillName(hero: heroInfo | undefined, uuid: number): str
}
/**
* 查询复活技能的对外展示名(英雄语境,LvReviveEntry.name 优先,缺省回退固定称谓"凤凰涅槃")。
* Why: 名称收敛进 heroSet 英雄配置,复活底座 6501 在不同英雄下可有特色名(如"回光返照")。
* @param hero 英雄配置(含 revive 档位的 heroInfo)
* @param sUuid 复活底座技能 uuid(指向 SkillSet 6501)
* 复活已纳入 dead 触发组:通过 SkillOverrides.revive_hp / revive_count 配置,
* 不再需要独立的 revive 字段和解析函数。
*/
export function getReviveSkillName(hero: heroInfo | undefined, sUuid: number): string {
for (const e of hero?.revive ?? []) {
if (e.s_uuid === sUuid && e.name) return e.name;
}
return "凤凰涅槃";
}
/**
* 英雄/怪物基础信息接口
@@ -322,7 +314,6 @@ export interface heroInfo {
[SkillTriggerType.Field]?: LvFieldEntry[]; // 驻场光环(按 lv 升序,运行时取 ≤英雄等级 的最大档)
[SkillTriggerType.Atking]?: TriggerGrouped; // 普通攻击后触发的技能配置,s_uuid: 技能id, t_num: 触发所需的普攻次数
[SkillTriggerType.Atked]?: TriggerGrouped; // 受击后触发的技能配置,s_uuid: 技能id, t_num: 触发所需的受击次数
[SkillTriggerType.Revive]?: LvReviveEntry[]; // 复活技能配置(按 lv 升序,运行时取 ≤英雄等级 的最大档)
dis?: number; // 攻击距离(像素)
speed?: number; // 移动速度(像素/秒)
skills: Record<number, HSkillInfo>; // 携带技能ID列表
@@ -431,20 +422,6 @@ export interface LvFieldEntry {
desc?: string;
}
/**
* 复活技能按等级解锁:运行时取 ≤英雄等级 的最大 lv 档。
* name 为该复活技能在英雄语境下的对外展示名(写在第 1 档),缺省回退固定称谓"凤凰涅槃"。
*/
export interface LvReviveEntry {
lv: number;
s_uuid: number;
r_num: number;
upr: number;
name?: string; // 英雄语境对外展示名(写在第 1 档)
/** 档位描述(可选,缺省由 HeroSkillText 自动生成) */
desc?: string;
}
/**
* 额外属性加成按等级叠加:运行时累加所有 ≤英雄等级 的档位。
*/
@@ -908,16 +885,6 @@ export function resolveFieldByLv(field: LvFieldEntry[] | undefined, lv: number):
return pick ? pick.uuids : [];
}
/** 解析复活技能在目标等级下的有效配置(取 ≤lv 的最大档) */
export function resolveReviveByLv(revive: LvReviveEntry[] | undefined, lv: number): { s_uuid: number; r_num: number; upr: number } | undefined {
if (!revive || revive.length === 0) return undefined;
let pick: LvReviveEntry | undefined;
for (const e of revive) {
if (e.lv <= lv && (!pick || e.lv > pick.lv)) pick = e;
}
return pick ? { s_uuid: pick.s_uuid, r_num: pick.r_num, upr: pick.upr } : undefined;
}
/** 计算目标等级下额外属性加成(hp/ap/def_bonus 一次性奖励 + 成长类型每级递增) */
function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: number, def: number } {
const lv = Math.max(1, targetLv);
@@ -1020,6 +987,23 @@ function getWaveCount(seconds: number): number {
return seconds / HERO_POWER_WEIGHTS.WAVE_DURATION;
}
/**
* 从 dead 触发配置中解析最大复活次数(revive_count)。
* 遍历所有 dead 技能的 overrides,取 revive_count 最大值。
* @param dead 死亡触发配置
* @param lv 英雄等级
* @returns 最大复活次数,无复活配置时返回 0
*/
export function resolveMaxReviveCount(dead: TriggerGrouped | undefined, lv: number): number {
const triggers = resolveTriggerByLv(dead, lv);
let maxRevive = 0;
for (const t of triggers) {
const count = t.overrides?.revive_count ?? 0;
if (count > maxRevive) maxRevive = count;
}
return maxRevive;
}
/** 计算触发技在目标时间窗内的触发次数 */
function getTriggerCount(
type: SkillTriggerType,
@@ -1035,7 +1019,9 @@ function getTriggerCount(
case SkillTriggerType.FStart:
return getWaveCount(seconds);
case SkillTriggerType.Dead:
return getWaveCount(seconds) * (1 + (resolveReviveByLv(hero.revive, FightSet.HERO_MAX_LV)?.r_num || 0));
// 死亡触发次数 = 回合数 × (1 + 最大复活次数),复活从 dead 配置中提取
const maxRevive = resolveMaxReviveCount(hero.dead, FightSet.HERO_MAX_LV);
return getWaveCount(seconds) * (1 + maxRevive);
}
return 1;
}
@@ -1316,9 +1302,14 @@ export function calcHeroPowerDetailed(hero: heroInfo, targetLv: number = 1, seco
const field = fieldEntries.length > 0 ? calcFieldPowerDetailed(fieldEntries, hero, targetLv, seconds) : { total: 0, items: [] as FieldSkillEval[] };
let reviveValue = 0;
const revive = resolveReviveByLv(hero.revive, targetLv);
if (revive) {
reviveValue = hpEff * revive.upr * revive.r_num * weights.W_revive * getWaveCount(seconds);
// 复活价值从 dead 触发配置中提取(revive_hp / revive_count)
const deadTriggers = resolveTriggerByLv(hero.dead, targetLv);
for (const t of deadTriggers) {
const reviveHp = t.overrides?.revive_hp;
const reviveCount = t.overrides?.revive_count ?? 0;
if (reviveHp !== undefined && reviveCount > 0) {
reviveValue = hpEff * (reviveHp / 100) * reviveCount * weights.W_revive * getWaveCount(seconds);
}
}
const total = hpValue + dpsValue + atking.total + atked.total + fstart.total + dead.total + field.total + reviveValue;

View File

@@ -22,6 +22,8 @@
> - **support / mage → 远程**(普攻 6009\~6012,大招 6200 / 6201 / 6202\~6204)
>
> 辅助技能(6301 护盾 / 6302 治疗 / 6412 防御强化 / 6501 复活)不受形态约束,任意形态可用;作大招(skills[1])时同样按 lv1/lv9/lv16 走 `lv_entries` 分档强化。
**复活机制说明**:复活已纳入 `dead` 触发组,通过 `overrides.revive_hp`(复活回血比例)和 `overrides.revive_count`(复活次数上限)配置,不再使用独立的 `revive` 字段。复活技能仍使用 6501 底座,但数值通过 `overrides` 注入。
## 说明模板
```
@@ -40,18 +42,18 @@ skill[1]_lv16:大招终段强化档(lv_entries)
atking[0]_lv4:攻击触发(t_num 模拟概率,1=每次 3≈33% 4≈25%)
atked[0]_lv4:无
fstart[0]_lv4:无
dead[0]_lv4:无
dead[0]_lv1:死亡触发(可含复活效果:overrides.revive_hp / revive_count)
dead[0]_lv8:死亡触发强化档
dead[0]_lv16:死亡触发终段档
field[0]_lv4:驻场光环(无品质门槛,所有英雄均可配置)
revive[0]_lv1:无(与 skills[1] 互斥二选一;配置时与大招同走 lv1/lv8/lv16 三档)
revive[0]_lv8:无
revive[0]_lv16:无
简化说明:原作缺失机制的替代方案(如有)
```
**通用约束**:
- 等级节奏:lv1 激活 skills[0]/skills[1];lv2 解锁 skills[0] 普攻强化档(`lv_entries` 覆写 ap/弹道/附加效果);lv4 解锁 atking/atked/fstart/dead/field 触发技能(触发类技能统一 lv4 激活,各触发槽最多 1 个,无品质门槛);大招(skills[1])lv9/lv16 走 `lv_entries` 分档提升;lv12 按职业对单项基础属性 +50%(基数取 lv11 当前等级有效值:坦克→def、战士/辅助→hp、刺客/射手/法师→ap)
- **revive 与大招互斥**:`revive[0]` 与 `skills[1]` 只能二选一;配置 revive 的英雄不走大招体系,revive 采用与大招相同的 3 级成长机制(lv1/lv8/lv16 走 `lv_entries` 分档强化)
- **dead 触发档位**:死亡触发技能(含复活)按 lv1/lv8/lv16 三档配置,与普通触发技统一节奏;`t_num` 固定为 1(只要死亡就触发)
- **复活配置规则**:复活效果通过 `dead[s_uuid]` 的 `overrides.revive_hp`(回血比例 %)+ `overrides.revive_count`(次数上限)注入;`s_uuid` 通常使用 6501(复活底座),也可与其他 dead 技能(如 6302 治疗)合并配置,实现"死亡时先触发技能效果、再判定复活"
- **品质(rarity)仅影响 lv1 初始基础属性**:相邻品质 AP/HP/DEF 相差 5%(系数 1+0.05×(rarity-1)),不再决定 uuid 段位、光环/技能解锁、属性补偿等任何机制;其余配置参数完全一致
- 能力只能从能力池(BuffList 7001\~7024 / FieldSkillSet 7001\~7020 / SkillSet 底座)引用 + `overrides` 数值覆盖,**禁止新增同义底座**
@@ -81,7 +83,7 @@ revive[0]_lv16:无
- [ ] `timed_buff_id` 指向 BuffList 已存在条目(能力池 2.1/2.2 内)
- [ ] debuff/buff 只通过 `overrides` 注入,未改动 SkillSet/BuffList 底座
- [ ] `atking`/`atked`/`fstart`/`dead`/`field` 按 s\_uuid 分组、按 lv 升序,触发类技能统一 lv4 激活(无品质门槛,field 光环任意品质可配)
- [ ] `revive[0]` 与 `skills[1]` 二选一(互斥);配置 revive 时按 lv1/lv8/lv16 走 `lv_entries` 3 级成长,与大招同机制
- [ ] 复活配置在 `dead` 触发组中,使用 `overrides.revive_hp`(回血比例 %)+ `overrides.revive_count`(次数上限),不再使用独立 `revive` 字段
- [ ] lv12:按职业注入单项属性 +50%(坦克 def、战士/辅助 hp、刺客/射手/法师 ap),基数取 lv11 有效值
- [ ] 品质(rarity)仅影响 lv1 初始 AP/HP/DEF(相邻差 5%,运行时品质系数统一乘,配置保持裸值),不影响 uuid 段位/光环/技能解锁/属性补偿
- [ ] `t_num` 概率模拟符合预期(1=每次,3≈33%,4≈25%)