Populate BossPhaseSet with add-style and stance-swap plans per boss (melee pool 6100-6104, ranged pool 6150-6151/6200-6204); stance-swap lists the full position ult pool in removes so random injected ults are always cleared. Mount phase_ids on 6006/6101-6106.
269 lines
12 KiB
TypeScript
269 lines
12 KiB
TypeScript
/**
|
||
* @file BossPhaseSet.ts
|
||
* @description Boss 多阶段战斗方案配置(固定 3 阶段:P1 基线 / P2 / P3)
|
||
*
|
||
* 数据流:heroInfo.phase_ids(允许方案)→ RogueConfig.makeBoss 随机取一
|
||
* → Mon.load 第 8 参 → BossPhaseComp → BossPhaseSystem 检测切换
|
||
*
|
||
* 设计约束:
|
||
* - 方案 id 使用 10000 段(9100 段已被 TalentSet 9101-9199 / ICardSet 9101~9110 占用)
|
||
* - 增量驱动:切换 = removes 移除 + adds 注入,P1 = 基线技能(heroInfo.skills + RogueConfig 大招)无需配置
|
||
* - removes/adds 禁止操作普攻首键(SCastSystem.buildSkillCandidates 硬假设首键=普攻垫底;
|
||
* JS delete+set 会把键移到末尾,破坏普攻垫底契约)
|
||
* - 混合触发:同一切换点 hp_pct / time 任一满足即切换;
|
||
* hp_pct 切换点必须构成前缀(time-only 点之后禁止再配 hp_pct,防乱序连切)
|
||
*/
|
||
|
||
import { SkillSet, SkillOverrides } from "./SkillSet";
|
||
|
||
/** 阶段切换触发条件(混合触发:任一条件满足即切换) */
|
||
export interface PhaseTrigger {
|
||
/** 血量百分比阈值(1~99):Boss 血量比例 ≤ 该值时触发 */
|
||
hp_pct?: number;
|
||
/** Boss 在场战斗秒数:超过该时长触发 */
|
||
time?: number;
|
||
}
|
||
|
||
/** 切换瞬间一次性属性修正(基于当前值链式乘算,P3 基于 P2 修正后的值) */
|
||
export interface PhaseAttrMod {
|
||
/** 攻击 ±% */
|
||
ap_pct?: number;
|
||
/** 防御 ±%(applyDefense 除法递减模型,收益非线性,配值按公式校验) */
|
||
defense_pct?: number;
|
||
}
|
||
|
||
/** 注入技能条目(与 RogueConfig 大招条目同构,cd 缺省由系统兜底 8s) */
|
||
export interface PhaseSkillEntry {
|
||
s_uuid: number;
|
||
cd?: number;
|
||
overrides?: SkillOverrides;
|
||
}
|
||
|
||
/** 单个切换点定义(switches[0] = P1→P2,switches[1] = P2→P3) */
|
||
export interface PhaseSwitchDef {
|
||
trigger: PhaseTrigger;
|
||
/** 注入的技能(追加进 skills 表,SCastSystem 按 CD 轮转)。禁止操作普攻首键 */
|
||
adds?: PhaseSkillEntry[];
|
||
/** 移除的技能 s_uuid 列表(含 RogueConfig 注入的大招)。禁止操作普攻首键 */
|
||
removes?: number[];
|
||
attr_mod?: PhaseAttrMod;
|
||
}
|
||
|
||
/** 阶段方案:固定 2 个切换点 = 3 阶段,P1 为基线 */
|
||
export interface BossPhasePlan {
|
||
name: string;
|
||
switches: [PhaseSwitchDef, PhaseSwitchDef];
|
||
}
|
||
|
||
/**
|
||
* 阶段方案表(id 段 10000,按 Boss 分组,详见 docs/superpowers/specs/2026-10-06-boss-phase-content-design.md)
|
||
* 换招式技巧:removes 列满该 Boss 定位的整个大招池 id(delete 空键无副作用),
|
||
* 无论 RogueConfig 随机注入了哪个大招一律清净,再 adds 新技能组
|
||
* - 近战定位池:[6100, 6101, 6102, 6103, 6104]
|
||
* - 远程定位池:[6150, 6151, 6200, 6201, 6202, 6203, 6204]
|
||
*/
|
||
export const BossPhaseSet: Record<number, BossPhasePlan> = {
|
||
// ========== 6101 兽人首领-双刀dps(血量,近战池) ==========
|
||
/** 追加式:P2 换裂气斩、P3 加闪电斩 + 攻+20% */
|
||
10001: {
|
||
name: "狂暴进化",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6102, overrides: { ap: 180 } }], removes: [6100] },
|
||
{ trigger: { hp_pct: 40, time: 60 }, adds: [{ s_uuid: 6103, overrides: { ap: 180 } }], attr_mod: { ap_pct: 20 } },
|
||
],
|
||
},
|
||
/** 追加式:双刀越挥越快,技能只加不换 */
|
||
10002: {
|
||
name: "嗜血连斩",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6103, overrides: { ap: 160 } }], attr_mod: { ap_pct: 15 } },
|
||
{ trigger: { hp_pct: 40 }, adds: [{ s_uuid: 6104, overrides: { ap: 180 } }], attr_mod: { ap_pct: 25, defense_pct: -10 } },
|
||
],
|
||
},
|
||
/** 换招式:每阶段换一把"刀",系数逐段上调 */
|
||
10003: {
|
||
name: "换刃",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6101, overrides: { ap: 200 } }] },
|
||
{ trigger: { hp_pct: 40 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6104, overrides: { ap: 220 } }], attr_mod: { ap_pct: 20, defense_pct: -15 } },
|
||
],
|
||
},
|
||
// ========== 6102 兽人首领-坦克型(时间,近战池) ==========
|
||
/** 追加式:越久越硬,反击越痛 */
|
||
10010: {
|
||
name: "重压觉醒",
|
||
switches: [
|
||
{ trigger: { time: 30 }, adds: [{ s_uuid: 6100, overrides: { ap: 150 } }], attr_mod: { defense_pct: 20 } },
|
||
{ trigger: { time: 60 }, adds: [{ s_uuid: 6104, overrides: { ap: 180 } }], attr_mod: { ap_pct: 20 } },
|
||
],
|
||
},
|
||
/** 换招式:外壳剥落,换进攻形态 */
|
||
10011: {
|
||
name: "崩岩",
|
||
switches: [
|
||
{ trigger: { time: 30 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6102, overrides: { ap: 200 } }], attr_mod: { ap_pct: 15 } },
|
||
{ trigger: { time: 60 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6104, overrides: { ap: 220 } }], attr_mod: { ap_pct: 25, defense_pct: -20 } },
|
||
],
|
||
},
|
||
// ========== 6103 兽人首领-魔法师(时间,远程池) ==========
|
||
/** 追加式:元素逐一汇入,弹幕越来越密 */
|
||
10020: {
|
||
name: "元素汇聚",
|
||
switches: [
|
||
{ trigger: { time: 30 }, adds: [{ s_uuid: 6204, overrides: { ap: 150 } }] },
|
||
{ trigger: { time: 60 }, adds: [{ s_uuid: 6202, overrides: { ap: 150 } }], attr_mod: { ap_pct: 20 } },
|
||
],
|
||
},
|
||
/** 换招式:每阶段念诵新禁咒,旧的被遗忘 */
|
||
10021: {
|
||
name: "禁咒",
|
||
switches: [
|
||
{ trigger: { time: 30 }, removes: [6150, 6151, 6200, 6201, 6202, 6203, 6204], adds: [{ s_uuid: 6201, overrides: { ap: 180 } }] },
|
||
{ trigger: { time: 60 }, removes: [6150, 6151, 6200, 6201, 6202, 6203, 6204], adds: [{ s_uuid: 6204, overrides: { ap: 200 } }], attr_mod: { ap_pct: 30, defense_pct: -15 } },
|
||
],
|
||
},
|
||
// ========== 6104 兽人首领-射手(血量,远程池) ==========
|
||
/** 追加式:箭种逐段解锁 */
|
||
10030: {
|
||
name: "箭雨升级",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6150, overrides: { ap: 150 } }] },
|
||
{ trigger: { hp_pct: 40 }, adds: [{ s_uuid: 6151, overrides: { ap: 160 } }], attr_mod: { ap_pct: 20 } },
|
||
],
|
||
},
|
||
/** 换招式:放弃弹幕,换单体重炮 */
|
||
10031: {
|
||
name: "狙击姿态",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, removes: [6150, 6151, 6200, 6201, 6202, 6203, 6204], adds: [{ s_uuid: 6203, overrides: { ap: 180 } }] },
|
||
{ trigger: { hp_pct: 40 }, removes: [6150, 6151, 6200, 6201, 6202, 6203, 6204], adds: [{ s_uuid: 6204, overrides: { ap: 200 } }], attr_mod: { ap_pct: 25, defense_pct: -15 } },
|
||
],
|
||
},
|
||
// ========== 6105 亡灵首领-法师(时间,远程池) ==========
|
||
/** 追加式:毒雾与雷暴层层叠加 */
|
||
10040: {
|
||
name: "亡灵潮汐",
|
||
switches: [
|
||
{ trigger: { time: 30 }, adds: [{ s_uuid: 6202, overrides: { ap: 150 } }] },
|
||
{ trigger: { time: 60 }, adds: [{ s_uuid: 6204, overrides: { ap: 150 } }], attr_mod: { ap_pct: 25 } },
|
||
],
|
||
},
|
||
/** 换招式:燃尽旧躯壳换新法术 */
|
||
10041: {
|
||
name: "灵魂爆发",
|
||
switches: [
|
||
{ trigger: { time: 30 }, removes: [6150, 6151, 6200, 6201, 6202, 6203, 6204], adds: [{ s_uuid: 6200, overrides: { ap: 180 } }], attr_mod: { defense_pct: 15 } },
|
||
{ trigger: { time: 60 }, removes: [6150, 6151, 6200, 6201, 6202, 6203, 6204], adds: [{ s_uuid: 6204, overrides: { ap: 220 } }], attr_mod: { ap_pct: 30 } },
|
||
],
|
||
},
|
||
// ========== 6106 亡灵首领-骑马战士(时间,近战池) ==========
|
||
/** 追加式:战马冲锋节奏渐强 */
|
||
10050: {
|
||
name: "冲锋陷阵",
|
||
switches: [
|
||
{ trigger: { time: 30 }, adds: [{ s_uuid: 6101, overrides: { ap: 160 } }] },
|
||
{ trigger: { time: 60 }, adds: [{ s_uuid: 6104, overrides: { ap: 180 } }], attr_mod: { ap_pct: 20, defense_pct: -10 } },
|
||
],
|
||
},
|
||
/** 换招式:弃守转攻,每段更凶更脆 */
|
||
10051: {
|
||
name: "背水一战",
|
||
switches: [
|
||
{ trigger: { time: 30 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6103, overrides: { ap: 200 } }], attr_mod: { ap_pct: 15, defense_pct: -10 } },
|
||
{ trigger: { time: 60 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6104, overrides: { ap: 220 } }], attr_mod: { ap_pct: 30, defense_pct: -20 } },
|
||
],
|
||
},
|
||
// ========== 6006 骷髅领主(血量,近战池) ==========
|
||
/** 追加式:骨架越碎越硬 */
|
||
10060: {
|
||
name: "亡灵复苏",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6100, overrides: { ap: 150 } }], attr_mod: { defense_pct: 25 } },
|
||
{ trigger: { hp_pct: 40 }, adds: [{ s_uuid: 6102, overrides: { ap: 160 } }], attr_mod: { ap_pct: 15, defense_pct: 15 } },
|
||
],
|
||
},
|
||
/** 换招式:拼装新武器,攻高防低 */
|
||
10061: {
|
||
name: "骷髅狂暴",
|
||
switches: [
|
||
{ trigger: { hp_pct: 70 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6103, overrides: { ap: 180 } }] },
|
||
{ trigger: { hp_pct: 40 }, removes: [6100, 6101, 6102, 6103, 6104], adds: [{ s_uuid: 6104, overrides: { ap: 220 } }], attr_mod: { ap_pct: 25, defense_pct: -15 } },
|
||
],
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 校验阶段方案表合法性(供 validateRogueConfig 追加调用,建议启动期执行一次)
|
||
* @returns 错误信息数组,空数组表示校验通过
|
||
*/
|
||
export function validateBossPhaseSet(): string[] {
|
||
const errors: string[] = [];
|
||
|
||
for (const key in BossPhaseSet) {
|
||
const plan = BossPhaseSet[key];
|
||
const tag = `BossPhaseSet[${key}](${plan.name})`;
|
||
|
||
if (plan.switches.length !== 2) {
|
||
errors.push(`${tag} 切换点必须恰为 2 个(固定 3 阶段),实际 ${plan.switches.length}`);
|
||
continue;
|
||
}
|
||
|
||
let seenTimeOnly = false;
|
||
let lastHpPct = 101;
|
||
let lastTime = 0;
|
||
|
||
plan.switches.forEach((def, i) => {
|
||
const t = def.trigger;
|
||
const stag = `${tag} switches[${i}]`;
|
||
|
||
// 条件完备性与取值范围
|
||
if (t.hp_pct === undefined && t.time === undefined) {
|
||
errors.push(`${stag} trigger 至少需要 hp_pct 或 time 之一`);
|
||
return;
|
||
}
|
||
if (t.hp_pct !== undefined && (t.hp_pct <= 0 || t.hp_pct >= 100)) {
|
||
errors.push(`${stag} hp_pct 必须在 (0,100) 区间,实际 ${t.hp_pct}`);
|
||
}
|
||
if (t.time !== undefined && t.time <= 0) {
|
||
errors.push(`${stag} time 必须 > 0,实际 ${t.time}`);
|
||
}
|
||
|
||
// hp_pct 前缀规则:time-only 点之后禁止再配 hp_pct(防"P2 time / P3 hp_pct"乱序连切)
|
||
if (seenTimeOnly && t.hp_pct !== undefined) {
|
||
errors.push(`${stag} hp_pct 切换点必须构成前缀:time-only 切换点之后禁止再配置 hp_pct`);
|
||
}
|
||
if (t.hp_pct === undefined && t.time !== undefined) {
|
||
seenTimeOnly = true;
|
||
}
|
||
|
||
// hp_pct 严格递减 / time 严格递增
|
||
if (t.hp_pct !== undefined) {
|
||
if (t.hp_pct >= lastHpPct) {
|
||
errors.push(`${stag} hp_pct=${t.hp_pct} 未严格小于前一档 ${lastHpPct}`);
|
||
}
|
||
lastHpPct = t.hp_pct;
|
||
}
|
||
if (t.time !== undefined) {
|
||
if (t.time <= lastTime) {
|
||
errors.push(`${stag} time=${t.time} 未严格大于前一档 ${lastTime}`);
|
||
}
|
||
lastTime = t.time;
|
||
}
|
||
|
||
// 技能引用存在性(error 级)
|
||
for (const add of def.adds ?? []) {
|
||
if (!add.s_uuid || !SkillSet[add.s_uuid]) {
|
||
errors.push(`${stag} adds 技能 ${add.s_uuid} 未在 SkillSet 中定义`);
|
||
}
|
||
}
|
||
for (const s_uuid of def.removes ?? []) {
|
||
if (!SkillSet[s_uuid]) {
|
||
errors.push(`${stag} removes 技能 ${s_uuid} 未在 SkillSet 中定义`);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
return errors;
|
||
}
|