feat(关卡系统): 实现章节-关-节三级关卡推进机制
本次提交重构了关卡系统,实现了完整的小节级关卡推进流程: 1. 新增关卡配置表StageSet,定义章节-关-节三级结构与怪物数值公式 2. 重构刷怪逻辑,按小节配置生成对应怪物波次 3. 实现关卡进度存档与恢复,支持断点续玩 4. 新增结算弹窗分支逻辑,区分小节胜利/关卡通关/失败状态 5. 优化胜利按钮文案与显示逻辑,将"再来一局"改为"下一关" 6. 新增一套完整的关卡事件系统,驱动各模块协同工作
This commit is contained in:
@@ -1,55 +1,38 @@
|
||||
/**
|
||||
* @file RogueConfig.ts
|
||||
* @description Boss 组合刷怪系统(单场 Boss 战定胜负)
|
||||
* @description 关卡刷怪系统(章节-关-节三级推进)
|
||||
*
|
||||
* 游戏机制:
|
||||
* - 整局只有一场战斗:开局英雄三次三选一后自动开战。
|
||||
* - 怪物从 BossComboLibrary 按权重随机抽一组(固定 3 只怪:0~2 Boss + 剩余小怪),
|
||||
* 战斗开始瞬间一次全量登场(X∈[100,320] 均匀铺开),双方立即互相攻击。
|
||||
* - 怪物全灭 → 胜利结算;英雄全灭 → 失败结算;无回合递进、无分批刷怪。
|
||||
* - 玩家线性推进 1 章 10 关 × 每关 10 小节,进度精确到小节并持久化。
|
||||
* - 普通关(1~4, 6~9):9 只小怪,前 3 只立即刷,后续每 5s 刷 1 只。
|
||||
* - 精英关(5):3 只精英同时刷出。
|
||||
* - Boss 关(10):2 精英先刷,Boss 延迟 1s 刷出。
|
||||
* - 怪物属性 = 基础 × 0.8 × (1 + 0.05×(lv-1)) × 类型倍率(精英1.5 / Boss3.0)。
|
||||
*
|
||||
* 设计层次:
|
||||
* 1. BossComboLibrary - Boss 组合库(小怪构成 + Boss/小怪技能池 + 抽取权重)
|
||||
* 1. StageSet - 关卡配置(章节/关/节 + 数值公式)
|
||||
* 2. MonSkillPool/BossSkillPool - 怪物技能池(atking / atked / dead 等全触发类型)
|
||||
* 3. RogueSpawningEngine - 生成引擎(基础属性 × 可调系数)
|
||||
* 3. StageSpawningEngine - 生成引擎(按小节配置生成怪物列表)
|
||||
*
|
||||
* 核心公式(基础属性 × 可调系数):
|
||||
* 小怪 hp = HeroInfo 基础hp × MON_HP_MUL,小怪 ap = HeroInfo 基础ap × MON_AP_MUL
|
||||
* Boss 基础面板固定为初始英雄 2 倍强度(BOSS_BASE_HP/BOSS_BASE_AP),与小怪共用同一套系数,
|
||||
* 场上 Boss 恰好为小怪 2 倍强度(如需额外压场可调 BOSS_HP_BONUS_MUL)。
|
||||
* 小怪基础面板与 1 级英雄持平(近战镜像 hp300/ap22/cd1.8),默认系数校准为
|
||||
* 整场战斗(3 英雄 vs 3 只怪)双方对砍 ≈ WAVE_DURATION(30s)。
|
||||
* 手感微调只改 MON_HP_MUL / MON_AP_MUL / BOSS_HP_BONUS_MUL 三个系数即可。
|
||||
* 核心公式:
|
||||
* 最终属性 = HeroInfo 基础属性 × MON_START_COEF × (1 + MON_GROW_SLOPE×(lv-1)) × 类型倍率
|
||||
* 1 级小怪 ≈ 英雄 × 0.8,10 级小怪 ≈ 英雄 × 1.16,保持平缓成长。
|
||||
*/
|
||||
|
||||
import { HeroInfo, MonType, MonTypeName, TriggerGrouped } from "../common/config/heroSet";
|
||||
import { SkillOverrides } from "../common/config/SkillSet";
|
||||
import { FacSet } from "../common/config/GameSet";
|
||||
import {
|
||||
SectionType, SectionConfig, getSectionConfig, calcMonsterAttrs,
|
||||
NORMAL_MON_COUNT, NORMAL_FIRST_SPAWN, ELITE_COUNT, BOSS_ELITE_COUNT, BOSS_DELAY,
|
||||
MON_START_COEF, MON_GROW_SLOPE, ELITE_MUL, BOSS_MUL,
|
||||
} from "../common/config/StageSet";
|
||||
|
||||
// ======================== 1. 战斗常量 ========================
|
||||
|
||||
/** 单场战斗时长基准(秒):供清屏速度评价(BattleBannerComp 清屏奖励)等使用 */
|
||||
export const WAVE_DURATION = 30;
|
||||
|
||||
/** 怪物登场固定构成:单场战斗固定 3 只怪,Boss 数量由组合配置决定(0~2 个),
|
||||
* 剩余位置由小怪填充。例如:3 小怪、1 Boss + 2 小怪、2 Boss + 1 小怪(最大难度) */
|
||||
export const BOSS_COUNT = 2;
|
||||
export const MINION_COUNT = 3;
|
||||
|
||||
/** 怪物血量可调系数:最终 hp = HeroInfo 基础hp × MON_HP_MUL。
|
||||
* 2.6 = 校准后怪物总血量 / 3 英雄 30s 输出(≈30 DPS),双方对砍 ≈ 30s 分胜负 */
|
||||
export const MON_HP_MUL = 2.6;
|
||||
/** 怪物攻击可调系数:最终 ap = HeroInfo 基础ap × MON_AP_MUL。
|
||||
* 0.9 使 4 小怪+1 Boss 的总 DPS ≈ 20,30s 恰好磨光 3 英雄总血量(≈600) */
|
||||
export const MON_AP_MUL = 0.9;
|
||||
|
||||
/** Boss 基础面板 = 初始英雄基准(hp300/ap22)× 2 倍强度,
|
||||
* 不读取 Boss HeroInfo 的差异化高基础值,避免数值膨胀 */
|
||||
export const BOSS_BASE_HP = 600;
|
||||
export const BOSS_BASE_AP = 44;
|
||||
|
||||
/** Boss 血量额外加成(在 MON_HP_MUL 之上乘算,默认 1.0 表示不再额外膨胀,保留作压场微调旋钮) */
|
||||
export const BOSS_HP_BONUS_MUL = 1.0;
|
||||
/** 战斗超时(秒):超过后强制结算整局(残留怪销毁并扣留存分) */
|
||||
export const BATTLE_TIMEOUT = 90;
|
||||
|
||||
@@ -97,89 +80,7 @@ export function getMonsterGoldDrop(monType: number, isBoss: boolean): number {
|
||||
return Math.max(0, Math.floor(isBoss ? cfg.boss : cfg.base));
|
||||
}
|
||||
|
||||
// ======================== 3. Boss 组合库 ========================
|
||||
|
||||
/** Boss 组合中小怪的槽位定义(type + 数量,Boss 另由 BOSS_COUNT 提供) */
|
||||
export interface BossComboMinionSlot {
|
||||
type: MonType;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** Boss 组合配置:一场战斗随机抽一组登场(固定 3 只怪,Boss 数量由 bossCount 决定) */
|
||||
export interface BossComboConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
/** 本场 Boss 数量(0~2),剩余位置由小怪填充 */
|
||||
bossCount: number;
|
||||
/** 小怪构成(合计数量建议等于 MINION_COUNT - bossCount,超出截断、不足自动补近战) */
|
||||
minions: BossComboMinionSlot[];
|
||||
/** Boss 技能池 id(引用 BossSkillPool,可选) */
|
||||
boss_skill_pool?: string[];
|
||||
/** 小怪技能池 id(引用 MonSkillPool,可选) */
|
||||
skill_pool?: string[];
|
||||
/** 在组合库中的抽取权重 */
|
||||
weight: number;
|
||||
}
|
||||
|
||||
/** Boss 组合库(硬编码):每场战斗按权重随机抽一组,固定 3 只怪。
|
||||
* 组合类型:
|
||||
* - 3 小怪(0 Boss):均衡难度
|
||||
* - 1 Boss + 2 小怪:标准难度
|
||||
* - 2 Boss + 1 小怪:最大难度 */
|
||||
export const BossComboLibrary: Record<string, BossComboConfig> = {
|
||||
/** 3 小怪:均衡组合 */
|
||||
rage_charge: {
|
||||
id: "rage_charge", name: "狂暴冲锋组", weight: 10, bossCount: 0,
|
||||
minions: [{ type: MonType.Melee, count: 2 }, { type: MonType.Heavy, count: 1 }],
|
||||
},
|
||||
iron_wall: {
|
||||
id: "iron_wall", name: "铁壁推进组", weight: 8, bossCount: 0,
|
||||
minions: [{ type: MonType.Heavy, count: 2 }, { type: MonType.Support, count: 1 }],
|
||||
skill_pool: ["tough"],
|
||||
},
|
||||
doom_ritual: {
|
||||
id: "doom_ritual", name: "毁灭仪式组", weight: 6, bossCount: 0,
|
||||
minions: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Support, count: 1 }],
|
||||
skill_pool: ["warcry"],
|
||||
},
|
||||
/** 1 Boss + 2 小怪:标准难度 */
|
||||
assassin_raid: {
|
||||
id: "assassin_raid", name: "刺客突袭组", weight: 7, bossCount: 1,
|
||||
minions: [{ type: MonType.Assassin, count: 1 }, { type: MonType.Melee, count: 1 }],
|
||||
boss_skill_pool: ["boss_rage", "boss_doom"],
|
||||
skill_pool: ["berserk"],
|
||||
},
|
||||
blood_legion: {
|
||||
id: "blood_legion", name: "吸血军团组", weight: 5, bossCount: 1,
|
||||
minions: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }],
|
||||
boss_skill_pool: ["boss_iron", "boss_doom"],
|
||||
skill_pool: ["leech"],
|
||||
},
|
||||
/** 2 Boss + 1 小怪:最大难度 */
|
||||
twin_boss: {
|
||||
id: "twin_boss", name: "双王降临", weight: 3, bossCount: 2,
|
||||
minions: [{ type: MonType.Support, count: 1 }],
|
||||
boss_skill_pool: ["boss_rage", "boss_iron", "boss_doom"],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 按权重随机抽取一组 Boss 组合
|
||||
* @returns 组合配置,库为空时返回 null
|
||||
*/
|
||||
export function pickBossCombo(): BossComboConfig | null {
|
||||
const combos = Object.values(BossComboLibrary).filter(c => c && c.weight > 0);
|
||||
if (combos.length === 0) return null;
|
||||
const total = combos.reduce((s, c) => s + c.weight, 0);
|
||||
let r = Math.random() * total;
|
||||
for (const c of combos) {
|
||||
r -= c.weight;
|
||||
if (r <= 0) return c;
|
||||
}
|
||||
return combos[combos.length - 1];
|
||||
}
|
||||
|
||||
// ======================== 4. 怪物技能池 ========================
|
||||
// ======================== 3. 怪物技能池 ========================
|
||||
|
||||
/**
|
||||
* 怪物技能套装(一套完整的触发技能配置)
|
||||
@@ -279,50 +180,17 @@ export const BossSkillPool: Record<string, MonSkillSet> = {
|
||||
},
|
||||
};
|
||||
|
||||
// ======================== 5. 配置校验 ========================
|
||||
// ======================== 4. 配置校验 ========================
|
||||
|
||||
/**
|
||||
* 校验 BossComboLibrary / MonSkillPool 引用一致性
|
||||
* 校验 MonSkillPool / BossSkillPool 引用一致性
|
||||
* 建议在游戏启动时调用一次,便于发现配置错误
|
||||
* @returns 错误信息数组,空数组表示校验通过
|
||||
*/
|
||||
export function validateRogueConfig(): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// 1. 校验组合引用与数量(固定 3 只怪:bossCount + minionTotal 必须等于 MINION_COUNT)
|
||||
for (const id in BossComboLibrary) {
|
||||
const combo = BossComboLibrary[id];
|
||||
let minionTotal = 0;
|
||||
for (const slot of combo.minions) {
|
||||
minionTotal += slot.count;
|
||||
if (!MonList[slot.type] || MonList[slot.type].length === 0) {
|
||||
errors.push(`组合 ${id} 引用的怪物类型 ${slot.type}(${MonTypeName[slot.type] || "?"}) 在 MonList 中无可用 uuid`);
|
||||
}
|
||||
}
|
||||
const totalMon = combo.bossCount + minionTotal;
|
||||
if (totalMon !== MINION_COUNT) {
|
||||
errors.push(`组合 ${id} 总怪数=${totalMon}(bossCount=${combo.bossCount} + minions=${minionTotal})与 MINION_COUNT=${MINION_COUNT} 不一致`);
|
||||
}
|
||||
if (combo.bossCount < 0 || combo.bossCount > BOSS_COUNT) {
|
||||
errors.push(`组合 ${id} bossCount=${combo.bossCount} 超出允许范围 [0, ${BOSS_COUNT}]`);
|
||||
}
|
||||
if (combo.skill_pool) {
|
||||
for (const skillId of combo.skill_pool) {
|
||||
if (!MonSkillPool[skillId]) {
|
||||
errors.push(`组合 ${id} 引用了不存在的技能: ${skillId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (combo.boss_skill_pool) {
|
||||
for (const skillId of combo.boss_skill_pool) {
|
||||
if (!BossSkillPool[skillId]) {
|
||||
errors.push(`组合 ${id} 引用了不存在的 Boss 技能: ${skillId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 校验 Boss 类型在 MonList 中有怪
|
||||
// 校验 Boss 类型在 MonList 中有怪
|
||||
if ((!MonList[MonType.MeleeBoss] || MonList[MonType.MeleeBoss].length === 0)
|
||||
&& (!MonList[MonType.LongBoss] || MonList[MonType.LongBoss].length === 0)) {
|
||||
errors.push("MonList 中无任何 Boss 类型(MeleeBoss/LongBoss)可用 uuid");
|
||||
@@ -331,7 +199,7 @@ export function validateRogueConfig(): string[] {
|
||||
return errors;
|
||||
}
|
||||
|
||||
// ======================== 6. 生成结果接口 ========================
|
||||
// ======================== 5. 生成结果接口 ========================
|
||||
|
||||
/** 生成结果(传递给 MissionMonComp 用于实例化怪物) */
|
||||
export interface GeneratedMonster {
|
||||
@@ -340,11 +208,17 @@ export interface GeneratedMonster {
|
||||
hp: number;
|
||||
ap: number;
|
||||
isBoss: boolean;
|
||||
/** 是否为精英(精英关/Boss 关的精英) */
|
||||
isElite?: boolean;
|
||||
/** 是否为 Boss 护卫队成员(供 UI/统计识别) */
|
||||
isBossGuard?: boolean;
|
||||
/** 怪物等级(驱动技能等级增量,Mon.load 第 6 参) */
|
||||
lv: number;
|
||||
spawnIndex: number;
|
||||
/** 本怪所属批次(保留字段,单场全量登场恒为 0) */
|
||||
/** 本怪所属批次(0=首批立即刷,1=后续定时刷) */
|
||||
batch: number;
|
||||
/** 延迟刷出时间(秒),0 表示立即 */
|
||||
delay?: number;
|
||||
|
||||
/**
|
||||
* 怪物技能套装(覆盖全部触发时机)
|
||||
@@ -361,7 +235,7 @@ export interface GeneratedMonster {
|
||||
};
|
||||
}
|
||||
|
||||
// ======================== 7. 测试模式配置 ========================
|
||||
// ======================== 6. 测试模式配置 ========================
|
||||
|
||||
export const TestModeConfig = {
|
||||
enable: false, // 默认关闭测试模式
|
||||
@@ -378,74 +252,138 @@ export const TestModeConfig = {
|
||||
fstart: undefined as TriggerGrouped | undefined,
|
||||
};
|
||||
|
||||
// ======================== 8. 生成引擎 ========================
|
||||
// ======================== 7. 生成引擎 ========================
|
||||
|
||||
/**
|
||||
* Boss 组合生成引擎
|
||||
* 随机抽一组 Boss 组合,怪物最终属性 = HeroInfo 基础属性 × 可调系数
|
||||
* 关卡刷怪生成引擎
|
||||
* 按章节-关-节配置生成怪物列表,属性走 StageSet 数值公式
|
||||
*/
|
||||
export class RogueSpawningEngine {
|
||||
export class StageSpawningEngine {
|
||||
|
||||
/**
|
||||
* 生成一场战斗的怪物列表:随机一组 Boss 组合(固定 3 只怪,Boss 数量由 combo.bossCount 决定)。
|
||||
* Boss 压队尾(登场时由 MissionMonComp 统一布位)。
|
||||
* @returns 怪物列表(小怪在前、Boss 在尾)
|
||||
* 生成指定小节的怪物列表
|
||||
* @param chapter 章节号
|
||||
* @param stage 关卡号
|
||||
* @param section 小节号(1~10)
|
||||
* @returns 怪物列表(按刷出顺序排列)
|
||||
*/
|
||||
generateBossCombo(): GeneratedMonster[] {
|
||||
generateSectionMonsters(chapter: number, stage: number, section: number): GeneratedMonster[] {
|
||||
// 测试模式拦截:完全绕过生成引擎,直接返回硬编码测试怪
|
||||
if (TestModeConfig.enable) {
|
||||
return this.generateTestWave();
|
||||
}
|
||||
|
||||
const combo = pickBossCombo();
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
const bossCount = combo ? Math.max(0, Math.min(combo.bossCount, BOSS_COUNT)) : 0;
|
||||
|
||||
if (combo) {
|
||||
for (const slot of combo.minions) {
|
||||
for (let i = 0; i < slot.count; i++) {
|
||||
monsters.push(this.makeMonster(slot.type, monsters.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 组合库为空或数量不足兜底:补近战小怪至 MINION_COUNT - bossCount
|
||||
const minionTarget = MINION_COUNT - bossCount;
|
||||
while (monsters.length < minionTarget) {
|
||||
monsters.push(this.makeMonster(MonType.Melee, monsters.length));
|
||||
const cfg = getSectionConfig(chapter, stage, section);
|
||||
if (!cfg) {
|
||||
// 配置缺失兜底:生成 3 只 Lv1 近战小怪
|
||||
return this.makeFallbackMonsters();
|
||||
}
|
||||
|
||||
// Boss 压队尾
|
||||
for (let i = 0; i < bossCount; i++) {
|
||||
monsters.push(this.makeBoss());
|
||||
switch (cfg.type) {
|
||||
case SectionType.Elite:
|
||||
return this.generateEliteSection(cfg);
|
||||
case SectionType.Boss:
|
||||
return this.generateBossSection(cfg);
|
||||
case SectionType.Normal:
|
||||
default:
|
||||
return this.generateNormalSection(cfg);
|
||||
}
|
||||
|
||||
// 属性缩放:基础属性 × 可调系数(Boss 基础面板已是英雄 2 倍,BOSS_HP_BONUS_MUL 默认 1.0),
|
||||
// 校准目标:Boss 场上强度恰为小怪 2 倍,双方对砍 ≈ 30s
|
||||
for (const m of monsters) {
|
||||
m.hp = Math.max(1, Math.round(m.hp * MON_HP_MUL * (m.isBoss ? BOSS_HP_BONUS_MUL : 1)));
|
||||
m.ap = Math.max(1, Math.round(m.ap * MON_AP_MUL));
|
||||
}
|
||||
|
||||
// 挂载组合技能(Boss 专属池 / 小怪普通池)
|
||||
for (const m of monsters) {
|
||||
if (m.isBoss && combo?.boss_skill_pool) {
|
||||
m.skills = this.pickSkillSet(combo.boss_skill_pool, BossSkillPool);
|
||||
} else if (!m.isBoss && combo?.skill_pool) {
|
||||
m.skills = this.pickSkillSet(combo.skill_pool, MonSkillPool);
|
||||
}
|
||||
m.batch = 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < monsters.length; i++) {
|
||||
monsters[i].spawnIndex = i;
|
||||
}
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/** 重置引擎状态(每局开始时调用,预留) */
|
||||
reset(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通关:9 只小怪
|
||||
* 前 3 只 batch=0(立即刷),后 6 只 batch=1(定时刷)
|
||||
*/
|
||||
private generateNormalSection(cfg: SectionConfig): GeneratedMonster[] {
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
const types = cfg.monTypes ?? [MonType.Melee];
|
||||
|
||||
for (let i = 0; i < NORMAL_MON_COUNT; i++) {
|
||||
const type = types[i % types.length];
|
||||
const mon = this.makeMonster(type, cfg.monLv, i);
|
||||
mon.batch = i < NORMAL_FIRST_SPAWN ? 0 : 1;
|
||||
monsters.push(mon);
|
||||
}
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 精英关:3 只精英同时刷出
|
||||
*/
|
||||
private generateEliteSection(cfg: SectionConfig): GeneratedMonster[] {
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
const count = cfg.eliteCount ?? ELITE_COUNT;
|
||||
// 精英池:近战/重型/远程/刺客
|
||||
const eliteTypes = [MonType.Melee, MonType.Heavy, MonType.Long, MonType.Assassin];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const type = eliteTypes[i % eliteTypes.length];
|
||||
const mon = this.makeMonster(type, cfg.monLv, i);
|
||||
mon.isElite = true;
|
||||
mon.batch = 0;
|
||||
// 精英属性 = 普通 × ELITE_MUL
|
||||
mon.hp = Math.max(1, Math.round(mon.hp * ELITE_MUL));
|
||||
mon.ap = Math.max(1, Math.round(mon.ap * ELITE_MUL));
|
||||
monsters.push(mon);
|
||||
}
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boss 关:2 精英 + 1 Boss
|
||||
* 精英先刷(batch=0),Boss 延迟刷出(batch=0, delay=BOSS_DELAY)
|
||||
*/
|
||||
private generateBossSection(cfg: SectionConfig): GeneratedMonster[] {
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
const eliteCount = cfg.eliteCount ?? BOSS_ELITE_COUNT;
|
||||
const eliteTypes = [MonType.Melee, MonType.Heavy];
|
||||
|
||||
// 先刷精英
|
||||
for (let i = 0; i < eliteCount; i++) {
|
||||
const type = eliteTypes[i % eliteTypes.length];
|
||||
const mon = this.makeMonster(type, cfg.monLv, i);
|
||||
mon.isElite = true;
|
||||
mon.isBossGuard = true;
|
||||
mon.batch = 0;
|
||||
mon.hp = Math.max(1, Math.round(mon.hp * ELITE_MUL));
|
||||
mon.ap = Math.max(1, Math.round(mon.ap * ELITE_MUL));
|
||||
monsters.push(mon);
|
||||
}
|
||||
|
||||
// Boss 延迟刷出
|
||||
const boss = this.makeBoss(cfg.monLv, monsters.length);
|
||||
boss.batch = 0;
|
||||
boss.delay = BOSS_DELAY;
|
||||
// Boss 属性 = 普通 × BOSS_MUL
|
||||
boss.hp = Math.max(1, Math.round(boss.hp * BOSS_MUL));
|
||||
boss.ap = Math.max(1, Math.round(boss.ap * BOSS_MUL));
|
||||
monsters.push(boss);
|
||||
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/** 配置缺失兜底:3 只 Lv1 近战小怪 */
|
||||
private makeFallbackMonsters(): GeneratedMonster[] {
|
||||
const monsters: GeneratedMonster[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
monsters.push({
|
||||
uuid: 6001,
|
||||
type: MonType.Melee,
|
||||
hp: Math.round(560 * MON_START_COEF),
|
||||
ap: Math.round(22 * MON_START_COEF),
|
||||
isBoss: false,
|
||||
lv: 1,
|
||||
spawnIndex: i,
|
||||
batch: 0,
|
||||
});
|
||||
}
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/** 测试模式:完全绕过引擎逻辑 */
|
||||
private generateTestWave(): GeneratedMonster[] {
|
||||
const count = Math.max(1, TestModeConfig.spawnCount || 1);
|
||||
@@ -458,6 +396,7 @@ export class RogueSpawningEngine {
|
||||
hp: TestModeConfig.baseHp,
|
||||
ap: TestModeConfig.baseAp,
|
||||
isBoss: false,
|
||||
lv: 1,
|
||||
spawnIndex: i,
|
||||
batch: 0,
|
||||
skills: {
|
||||
@@ -472,8 +411,8 @@ export class RogueSpawningEngine {
|
||||
return monsters;
|
||||
}
|
||||
|
||||
/** 生成 Boss(基础面板固定为初始英雄 2 倍强度,属性随后由 generateBossCombo 统一系数缩放) */
|
||||
private makeBoss(): GeneratedMonster {
|
||||
/** 生成 Boss(基础面板按 StageSet 公式计算) */
|
||||
private makeBoss(monLv: number, spawnIndex: number): GeneratedMonster {
|
||||
const isMeleeBoss = Math.random() < 0.5;
|
||||
const type = isMeleeBoss ? MonType.MeleeBoss : MonType.LongBoss;
|
||||
|
||||
@@ -484,19 +423,26 @@ export class RogueSpawningEngine {
|
||||
}
|
||||
const uuid = uuids[Math.floor(Math.random() * uuids.length)];
|
||||
|
||||
// Boss 基础面板取 HeroInfo 基础值,属性由调用方乘 BOSS_MUL
|
||||
const baseInfo = HeroInfo[uuid];
|
||||
const baseHp = baseInfo ? baseInfo.hp : 600;
|
||||
const baseAp = baseInfo ? baseInfo.ap : 30;
|
||||
const attrs = calcMonsterAttrs(baseHp, baseAp, monLv, false, false);
|
||||
|
||||
return {
|
||||
uuid,
|
||||
type,
|
||||
hp: BOSS_BASE_HP,
|
||||
ap: BOSS_BASE_AP,
|
||||
hp: attrs.hp,
|
||||
ap: attrs.ap,
|
||||
isBoss: true,
|
||||
spawnIndex: 0, // 占位值,由 generateBossCombo 统一分配
|
||||
lv: monLv,
|
||||
spawnIndex,
|
||||
batch: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 生成普通怪物 */
|
||||
private makeMonster(type: MonType, spawnIndex: number): GeneratedMonster {
|
||||
/** 生成普通怪物(属性按 StageSet 公式计算) */
|
||||
private makeMonster(type: MonType, monLv: number, spawnIndex: number): GeneratedMonster {
|
||||
let uuids = MonList[type];
|
||||
if (!uuids || uuids.length === 0) {
|
||||
// 兜底 Melee
|
||||
@@ -507,13 +453,15 @@ export class RogueSpawningEngine {
|
||||
const baseInfo = HeroInfo[uuid];
|
||||
const baseHp = baseInfo ? baseInfo.hp : 100;
|
||||
const baseAp = baseInfo ? baseInfo.ap : 10;
|
||||
const attrs = calcMonsterAttrs(baseHp, baseAp, monLv, false, false);
|
||||
|
||||
return {
|
||||
uuid,
|
||||
type,
|
||||
hp: baseHp,
|
||||
ap: baseAp,
|
||||
hp: attrs.hp,
|
||||
ap: attrs.ap,
|
||||
isBoss: false,
|
||||
lv: monLv,
|
||||
spawnIndex,
|
||||
batch: 0,
|
||||
};
|
||||
@@ -550,6 +498,18 @@ export class RogueSpawningEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 9. 全局单例 ========================
|
||||
// ======================== 8. 全局单例 ========================
|
||||
|
||||
export const spawningEngine = new RogueSpawningEngine();
|
||||
export const spawningEngine = new StageSpawningEngine();
|
||||
|
||||
// 保留旧接口兼容(逐步迁移期使用)
|
||||
/** @deprecated 请使用 StageSpawningEngine.generateSectionMonsters */
|
||||
export class RogueSpawningEngine extends StageSpawningEngine {
|
||||
/** @deprecated 请使用 generateSectionMonsters */
|
||||
generateBossCombo(): GeneratedMonster[] {
|
||||
return this.generateSectionMonsters(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated 请使用 spawningEngine */
|
||||
export const rogueSpawningEngine = new RogueSpawningEngine();
|
||||
|
||||
Reference in New Issue
Block a user