/** * @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) * 12 方案矩阵:4 Boss × 3 方案(清空/保留交错),P1=基线(heroInfo.skills + RogueConfig 大招) * P2→P3 触发条件关卡调节:前期苛刻(血量 5% / 时间 120s)→ 中期标准(20% / 90s)→ 后期易触发(35% / 75s) * 技能 s_uuid 引用 RogueConfig.BossUltPools 保证可达(近战 6100~6104 / 中程 6150~6151,6203~6204 / 远程 6200~6204) */ export const BossPhaseSet: Record = { // ========== 破阵先锋(近战 DPS,血量阈值) ========== // 方案 1:断剑回锋(清空+新)——每阶段全新招式集,玩家每阶段重学 10011: { name: "断剑回锋", switches: [ { trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6104, overrides: { ap: 350 } }], removes: [6100] }, { trigger: { hp_pct: 5 }, adds: [{ s_uuid: 6103, overrides: { ap: 400 } }], removes: [6104], attr_mod: { ap_pct: 50, defense_pct: -30 } }, ], }, // 方案 2:破阵三式(保留+加)——数值膨胀,技能池越打越大 10012: { name: "破阵三式", switches: [ { trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6102, overrides: { ap: 250 } }] }, { trigger: { hp_pct: 5 }, adds: [{ s_uuid: 6104, overrides: { ap: 350 } }], attr_mod: { ap_pct: 50, defense_pct: -20 } }, ], }, // 方案 3:破阵三式·逆鳞(清空变体)——P3 高爆发脆皮 10013: { name: "破阵三式·逆鳞", switches: [ { trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6103, overrides: { ap: 300 } }], removes: [6100] }, { trigger: { hp_pct: 5 }, adds: [{ s_uuid: 6104, overrides: { ap: 400 } }], removes: [6103], attr_mod: { ap_pct: 50, defense_pct: -30 } }, ], }, // ========== 雷巫·荒火(法师,时间阈值) ========== // 方案 1:雷云·蓄(清空+新)——法系读招,时间触发给玩家反应窗口 10021: { name: "雷云·蓄", switches: [ { trigger: { time: 60 }, adds: [{ s_uuid: 6201, overrides: { ap: 300 } }], removes: [6200], attr_mod: { ap_pct: 30, defense_pct: -10 } }, { trigger: { time: 120 }, adds: [{ s_uuid: 6204, overrides: { ap: 400 } }], removes: [6201], attr_mod: { ap_pct: 50, defense_pct: -15 } }, ], }, // 方案 2:雷云·蓄·散(保留+加)——雷→火→碎甲,机制叠加 10022: { name: "雷云·蓄·散", switches: [ { trigger: { time: 60 }, adds: [{ s_uuid: 6151, overrides: { ap: 350 } }], attr_mod: { ap_pct: 25 } }, { trigger: { time: 120 }, adds: [{ s_uuid: 6203, overrides: { ap: 250 } }], attr_mod: { ap_pct: 50, defense_pct: -15 } }, ], }, // 方案 3:雷云·散(清空变体)——P3 超高爆发 10023: { name: "雷云·散", switches: [ { trigger: { time: 60 }, adds: [{ s_uuid: 6150, overrides: { ap: 250 } }], removes: [6200], attr_mod: { ap_pct: 20 } }, { trigger: { time: 120 }, adds: [{ s_uuid: 6204, overrides: { ap: 400 } }], removes: [6150], attr_mod: { ap_pct: 60 } }, ], }, // ========== 霜羽·影哨(射手,血量阈值) ========== // 方案 1:单羽破阵(保留+加)——高频弹幕,血量阈值避免每秒判定 10031: { name: "单羽破阵", switches: [ { trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6151, overrides: { ap: 220 } }], attr_mod: { ap_pct: 20 } }, { trigger: { hp_pct: 5 }, adds: [{ s_uuid: 6150, overrides: { ap: 100 } }], attr_mod: { ap_pct: 50, defense_pct: -15 } }, ], }, // 方案 2:羽阵·三重(清空+新)——冰→火→雷,元素轮换 10032: { name: "羽阵·三重", switches: [ { trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6151, overrides: { ap: 220 } }], removes: [6150], attr_mod: { ap_pct: 30 } }, { trigger: { hp_pct: 5 }, adds: [{ s_uuid: 6204, overrides: { ap: 150 } }], removes: [6151], attr_mod: { ap_pct: 50 } }, ], }, // 方案 3:羽阵·三重·影哨(保留+加变体)——P3 脆皮化 10033: { name: "羽阵·三重·影哨", switches: [ { trigger: { hp_pct: 70 }, adds: [{ s_uuid: 6151, overrides: { ap: 220 } }], attr_mod: { ap_pct: 25 } }, { trigger: { hp_pct: 5 }, adds: [{ s_uuid: 6150, overrides: { ap: 100 } }], attr_mod: { ap_pct: 50, defense_pct: -15 } }, ], }, // ========== 磐盾·吞岩(坦克,时间阈值) ========== // 方案 1:磐盾·重压(清空+新)——消耗战,时间触发逼玩家固定窗口打完 10041: { name: "磐盾·重压", switches: [ { trigger: { time: 60 }, adds: [{ s_uuid: 6102, overrides: { ap: 350 } }], removes: [6100], attr_mod: { ap_pct: 30, defense_pct: 20 } }, { trigger: { time: 120 }, adds: [{ s_uuid: 6104, overrides: { ap: 400 } }], removes: [6102], attr_mod: { ap_pct: 40, defense_pct: 40 } }, ], }, // 方案 2:磐盾·三叠(保留+加)——护盾→自疗→爆发,越打越硬 10042: { name: "磐盾·三叠", switches: [ { trigger: { time: 60 }, adds: [{ s_uuid: 6101, overrides: { ap: 300 } }], attr_mod: { defense_pct: 20 } }, { trigger: { time: 120 }, adds: [{ s_uuid: 6103, overrides: { ap: 300 } }], attr_mod: { ap_pct: 30, defense_pct: 50 } }, ], }, // 方案 3:磐盾·三叠·崩岩(清空变体)——P3 反击+减攻 10043: { name: "磐盾·三叠·崩岩", switches: [ { trigger: { time: 60 }, adds: [{ s_uuid: 6101, overrides: { ap: 350 } }], removes: [6100], attr_mod: { ap_pct: 30 } }, { trigger: { time: 120 }, adds: [{ s_uuid: 6104, overrides: { ap: 400 } }], removes: [6101], attr_mod: { defense_pct: 30 } }, ], }, }; /** * 校验阶段方案表合法性(供 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; }