4 Commits

Author SHA1 Message Date
pan
9a24f0a9a5 refactor(missionMon): 调整怪物登场排布逻辑
将怪物从按X轴均匀分布改为按类型分三排登场,匹配英雄编队镜像布局,高优先级怪物自动占据前排
2026-08-20 17:30:36 +08:00
pan
965b04094d refactor(rogue): 调整Rogue模式怪物配置与战斗逻辑
1. 新增怪物出生站位配置
2. 调整单场战斗怪物数量为3只,支持0~2个Boss组合
3. 更新配置校验与怪物生成逻辑
4. 修复英雄预设体的尺寸与位置偏移
2026-08-20 17:19:24 +08:00
pan
e18bcf2133 fix(config): 统一怪物普攻伤害计算方式
补充怪物普攻使用SkillSet基础攻击的注释说明,为所有怪物技能添加overrides.ap=100以显式声明伤害为攻击力100%,修正部分怪物技能ID不匹配问题
2026-08-20 17:19:12 +08:00
pan
75ef3af42b fix(hero): 统一调整英雄预制体尺寸与属性配置
1. 修正多个英雄prefab的碰撞盒、显示尺寸参数,对齐统一规格
2. 调整mb1/3/4/5/6的本地位置参数适配新尺寸
3. 翻倍英雄基础血量属性适配平衡调整
4. 优化HeroAnmComp代码格式与动画播放逻辑
2026-08-20 15:38:24 +08:00
11 changed files with 203 additions and 131 deletions

View File

@@ -250,7 +250,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": -85.933, "y": -40.433,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -290,8 +290,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 384, "width": 191.9,
"height": 384 "height": 192
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1094,7 +1094,7 @@
}, },
"_size": { "_size": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 30, "width": 80,
"height": 100 "height": 100
}, },
"_id": "" "_id": ""

View File

@@ -981,8 +981,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 80, "width": 150,
"height": 100 "height": 150
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1090,12 +1090,12 @@
"_offset": { "_offset": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
"y": 50 "y": 75
}, },
"_size": { "_size": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 30, "width": 150,
"height": 100 "height": 150
}, },
"_id": "" "_id": ""
}, },

View File

@@ -250,7 +250,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": -84.866, "y": -84.432,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -978,8 +978,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 80, "width": 150,
"height": 100 "height": 150
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1087,12 +1087,12 @@
"_offset": { "_offset": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
"y": 50 "y": 75
}, },
"_size": { "_size": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 30, "width": 150,
"height": 100 "height": 150
}, },
"_id": "" "_id": ""
}, },

View File

@@ -250,7 +250,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": -84.192, "y": -57.035,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -290,8 +290,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 384, "width": 256,
"height": 384 "height": 256
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -981,8 +981,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 80, "width": 150,
"height": 100 "height": 150
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1090,12 +1090,12 @@
"_offset": { "_offset": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
"y": 50 "y": 75
}, },
"_size": { "_size": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 30, "width": 150,
"height": 100 "height": 150
}, },
"_id": "" "_id": ""
}, },

View File

@@ -60,7 +60,7 @@
}, },
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": -0.041, "x": 0,
"y": 0, "y": 0,
"z": 0 "z": 0
}, },
@@ -250,7 +250,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": -87.202, "y": -57.266,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -290,8 +290,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 384, "width": 256,
"height": 384 "height": 256
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -978,8 +978,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 80, "width": 150,
"height": 100 "height": 150
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1087,12 +1087,12 @@
"_offset": { "_offset": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
"y": 50 "y": 75
}, },
"_size": { "_size": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 30, "width": 150,
"height": 100 "height": 150
}, },
"_id": "" "_id": ""
}, },

View File

@@ -250,7 +250,7 @@
"_lpos": { "_lpos": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0, "x": 0,
"y": -81.733, "y": -53.96,
"z": 0 "z": 0
}, },
"_lrot": { "_lrot": {
@@ -290,8 +290,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 384, "width": 256,
"height": 384 "height": 256
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -978,8 +978,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 200, "width": 150,
"height": 220 "height": 150
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -1087,12 +1087,12 @@
"_offset": { "_offset": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
"x": 0, "x": 0,
"y": 110 "y": 75
}, },
"_size": { "_size": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 150, "width": 150,
"height": 220 "height": 150
}, },
"_id": "" "_id": ""
}, },

View File

@@ -27,6 +27,11 @@ export enum FightSet {
HERO_ROW_FRONT_X = -200, // 前排(最靠右迎敌):坦克 / 战士 HERO_ROW_FRONT_X = -200, // 前排(最靠右迎敌):坦克 / 战士
HERO_ROW_MID_X = -260, // 中排:刺客 / 射手 HERO_ROW_MID_X = -260, // 中排:刺客 / 射手
HERO_ROW_BACK_X = -320, // 后排(最靠左):法师 / 辅助 HERO_ROW_BACK_X = -320, // 后排(最靠左):法师 / 辅助
/** 怪物三排出生/编队基准 x(y 均为 BoxSet.GAME_LINE):前排迎敌、中排、后排靠右 */
MON_ROW_FRONT_X = 200, // 前排(最靠左迎敌):近战 / 重型 / 近战Boss
MON_ROW_MID_X = 260, // 中排:远程 / 刺客 / 远程Boss
MON_ROW_BACK_X = 320, // 后排(最靠右):辅助 / 召唤师
WAVE_COIN_BASE = 7, // 回合金币基础奖励 WAVE_COIN_BASE = 7, // 回合金币基础奖励
WAVE_COIN_GROW = 2, // 回合金币递增值 WAVE_COIN_GROW = 2, // 回合金币递增值
WAVE_COIN_MAX = 17, // 回合金币最大基础奖励 WAVE_COIN_MAX = 17, // 回合金币最大基础奖励

View File

@@ -1,4 +1,4 @@
import { v3 } from "cc" import { v3 } from "cc"
import { BoxSet, FacSet, FightSet } from "./GameSet" import { BoxSet, FacSet, FightSet } from "./GameSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet" import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet"
import { BuffList, BuffConfig, ModOp } from "./BuffSet" import { BuffList, BuffConfig, ModOp } from "./BuffSet"
@@ -145,12 +145,12 @@ export const HRoleName: Record<HRole, string> = {
* Why: 局外升级机制将在加载时(Hero.ts / Mon.ts)统一重算属性,配置表只承担"基准+增量"的静态表达。 * Why: 局外升级机制将在加载时(Hero.ts / Mon.ts)统一重算属性,配置表只承担"基准+增量"的静态表达。
*/ */
export const HERO_ROLE_BASE: Record<HRole, { hp: number; ap: number; def: number }> = { export const HERO_ROLE_BASE: Record<HRole, { hp: number; ap: number; def: number }> = {
[HRole.Tank]: { hp: 300, ap: 20, def: 50 }, // 坦克:高防高血,前排承伤 [HRole.Tank]: { hp: 600, ap: 20, def: 50 }, // 坦克:高防高血,前排承伤
[HRole.Warrior]: { hp: 280, ap: 22, def: 30 }, // 战士:均衡 [HRole.Warrior]: { hp: 560, ap: 22, def: 30 }, // 战士:均衡
[HRole.Assassin]: { hp: 280, ap: 22, def: 10 }, // 刺客:低防,近战爆发 [HRole.Assassin]: { hp: 560, ap: 22, def: 10 }, // 刺客:低防,近战爆发
[HRole.Archer]: { hp: 140, ap: 32, def: 10 }, // 射手:低防,远程物理主C [HRole.Archer]: { hp: 280, ap: 32, def: 10 }, // 射手:低防,远程物理主C
[HRole.Mage]: { hp: 140, ap: 32, def: 10 }, // 法师:低防,远程增益 [HRole.Mage]: { hp: 280, ap: 32, def: 10 }, // 法师:低防,远程增益
[HRole.Support]: { hp: 140, ap: 32, def: 15 }, // 辅助:低防,光环/治疗 [HRole.Support]: { hp: 280, ap: 32, def: 15 }, // 辅助:低防,光环/治疗
}; };
/** /**
@@ -782,6 +782,8 @@ export const HeroInfo: Record<number, heroInfo> = {
* 性能基准:1 级怪物与 1 级英雄单体性能持平 —— * 性能基准:1 级怪物与 1 级英雄单体性能持平 ——
* 近战怪基准 hp300/ap22/cd1.20,远程怪基准 hp160/ap28/cd1.20, * 近战怪基准 hp300/ap22/cd1.20,远程怪基准 hp160/ap28/cd1.20,
* 重型/Boss 以更高 hp 换取更低 DPS,承担"墙"与"压场"定位。 * 重型/Boss 以更高 hp 换取更低 DPS,承担"墙"与"压场"定位。
* 怪物普攻统一使用 SkillSet 基础单体攻击(6001~6014 段),
* 并通过 overrides.ap=100 显式声明伤害为攻击力的 100%。
* 运行时最终属性 = 本表基础值 × RogueConfig.MON_HP_MUL / MON_AP_MUL * 运行时最终属性 = 本表基础值 × RogueConfig.MON_HP_MUL / MON_AP_MUL
* (Boss 额外 × BOSS_HP_BONUS_MUL),校准目标:整场战斗双方对砍 ≈ 30s。 * (Boss 额外 × BOSS_HP_BONUS_MUL),校准目标:整场战斗双方对砍 ≈ 30s。
*/ */
@@ -791,7 +793,7 @@ export const HeroInfo: Record<number, heroInfo> = {
// 前排怪物 — 近战镜像 1 级英雄基准(hp300/ap22/cd1.20),变体微分定位 // 前排怪物 — 近战镜像 1 级英雄基准(hp300/ap22/cd1.20),变体微分定位
6001: { 6001: {
uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 300, ap: 22, def: 20, speed: 70, uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 300, ap: 22, def: 20, speed: 70,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "基础前排怪, 与1级近战英雄持平" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "基础前排怪, 与1级近战英雄持平"
}, },
6002: { 6002: {
uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 340, ap: 26, def: 25, speed: 110, uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 340, ap: 26, def: 25, speed: 110,
@@ -799,56 +801,56 @@ export const HeroInfo: Record<number, heroInfo> = {
}, },
6003: { 6003: {
uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 800, ap: 14, def: 60, speed: 50, uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 800, ap: 14, def: 60, speed: 50,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0 } }, info: "重型坦克怪, 高HP低攻承伤位" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "重型坦克怪, 高HP低攻承伤位"
}, },
// 后排怪物 — 远程镜像 1 级法师基准(hp160/ap28/cd1.20),变体微分定位 // 后排怪物 — 远程镜像 1 级法师基准(hp160/ap28/cd1.20),变体微分定位
6004: { 6004: {
uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 160, ap: 28, def: 8, speed: 70, uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 160, ap: 28, def: 8, speed: 70,
skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "后排输出怪, 与1级远程英雄持平" skills: { 6006: { uuid: 6006, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "后排输出怪, 与1级远程英雄持平"
}, },
6005: { 6005: {
uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 130, ap: 26, def: 5, speed: 130, uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 130, ap: 26, def: 5, speed: 130,
skills: { 6102: { uuid: 6102, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0 } }, info: "高攻速低HP刺客" skills: { 6002: { uuid: 6002, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0, overrides: { ap: 100, hit_count: 2 } } }, info: "高攻速低HP刺客"
}, },
// 特殊位怪物 // 特殊位怪物
6006: { 6006: {
uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 900, ap: 20, def: 40, speed: 60, uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 900, ap: 20, def: 40, speed: 60,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0 } }, info: "前排MiniBoss级坦克" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "前排MiniBoss级坦克"
}, },
6007: { 6007: {
uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 240, ap: 18, def: 10, speed: 70, uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 240, ap: 18, def: 10, speed: 70,
skills: { 6102: { uuid: 6102, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "后排辅助法师怪" skills: { 6009: { uuid: 6009, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100, hit_count: 2 } } }, info: "后排辅助法师怪"
}, },
6008: { 6008: {
uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 220, ap: 24, def: 10, speed: 70, uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 220, ap: 24, def: 10, speed: 70,
skills: { 6014: { uuid: 6014, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "后排召唤系法师怪" skills: { 6010: { uuid: 6010, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100, hit_count: 2 } } }, info: "后排召唤系法师怪"
}, },
// BOSS怪物 — 本表 hp/ap 仅作外观/技能档位参考,运行时面板由 RogueConfig 的 // BOSS怪物 — 本表 hp/ap 仅作外观/技能档位参考,运行时面板由 RogueConfig 的
// BOSS_BASE_HP / BOSS_BASE_AP(初始英雄 2 倍强度)统一决定,不读取本表数值 // BOSS_BASE_HP / BOSS_BASE_AP(初始英雄 2 倍强度)统一决定,不读取本表数值
6101: { 6101: {
uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1200, ap: 24, def: 45, speed: 120, uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1200, ap: 24, def: 45, speed: 120,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "前排Boss, 攻速型" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "前排Boss, 攻速型"
}, },
6102: { 6102: {
uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1500, ap: 18, def: 55, speed: 60, uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1500, ap: 18, def: 55, speed: 60,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0 } }, info: "前排Boss, 超高HP" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "前排Boss, 超高HP"
}, },
6103: { 6103: {
uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 28, def: 25, speed: 110, uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 28, def: 25, speed: 110,
skills: { 6102: { uuid: 6102, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "后排法系Boss" skills: { 6009: { uuid: 6009, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100, hit_count: 2 } } }, info: "后排法系Boss"
}, },
6104: { 6104: {
uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1100, ap: 24, def: 25, speed: 70, uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1100, ap: 24, def: 25, speed: 70,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0 } }, info: "后排位Boss, 均衡型" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "后排位Boss, 均衡型"
}, },
6105: { 6105: {
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 30, def: 20, speed: 110, uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 1000, ap: 30, def: 20, speed: 110,
skills: { 6014: { uuid: 6014, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 80, timed_buff_id: 7002 } } }, info: "后排高AP Boss" skills: { 6011: { uuid: 6011, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100, hit_count: 2, timed_buff_id: 7002 } } }, info: "后排高AP Boss"
}, },
6106: { 6106: {
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1800, ap: 20, def: 60, speed: 130, uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1800, ap: 20, def: 60, speed: 130,
skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0 } }, info: "前排终极Boss, 最高HP+高速" skills: { 6004: { uuid: 6004, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow].cd, ccd: 0, overrides: { ap: 100 } } }, info: "前排终极Boss, 最高HP+高速"
}, },

View File

@@ -5,15 +5,15 @@ import { FlashSprite } from "./hit-flash-white/scripts/FlashSprite";
const { ccclass, property } = _decorator; const { ccclass, property } = _decorator;
@ccclass('HeroAnmComp') @ccclass('HeroAnmComp')
export default class HeroAnmComp extends Component{ export default class HeroAnmComp extends Component {
fsSprite:FlashSprite = null!; fsSprite: FlashSprite = null!;
private anmcon:any=null private anmcon: any = null
private _hasStop = true; private _hasStop = true;
private _atkIndex = 0; private _atkIndex = 0;
private default_anim:string='idle' private default_anim: string = 'idle'
anms:any[]=["idle","move","atk0","max0","max1"] anms: any[] = ["idle", "move", "atk0", "max0", "max1"]
onLoad () { onLoad() {
this.anmcon=this.node.getComponent(Animation) this.anmcon = this.node.getComponent(Animation)
this.fsSprite = this.node.getComponent(FlashSprite); this.fsSprite = this.node.getComponent(FlashSprite);
this.anmcon.on(Animation.EventType.FINISHED, this.onAnimationFinished, this); this.anmcon.on(Animation.EventType.FINISHED, this.onAnimationFinished, this);
} }
@@ -24,46 +24,61 @@ export default class HeroAnmComp extends Component{
} }
} }
stop () { /** 大招是否播放中(最高优先级,期间禁止一切动画切换) */
private get maxPlaying(): boolean {
return !!(this.anmcon.getState("max0")?.isPlaying || this.anmcon.getState("max1")?.isPlaying);
}
/** 普攻是否播放中(次优先级,仅可被大招打断) */
private get atkPlaying(): boolean {
return !!this.anmcon.getState("atk0")?.isPlaying;
}
/** buff 是否播放中(可被大招/普攻打断,但拦截 idle/move) */
private get buffPlaying(): boolean {
return !!this.anmcon.getState("buff")?.isPlaying;
}
stop() {
this._hasStop = true; this._hasStop = true;
} }
onAnimationFinished(type:Animation.EventType, state:AnimationState){ onAnimationFinished(type: Animation.EventType, state: AnimationState) {
if(state.name!="idle"&&state.name!="move"){ if (state.name != "idle" && state.name != "move") {
this.anmcon.play(this.default_anim) this.anmcon.play(this.default_anim)
} }
} }
atked(){ atked() {
this.fsSprite.clickFlash(); this.fsSprite.clickFlash();
} }
move () { move() {
if(this.anmcon.getState("move").isPlaying) return if (this.maxPlaying || this.atkPlaying || this.buffPlaying) return
this.anmcon.play("move") if (this.anmcon.getState("move").isPlaying) return
this.default_anim='move' this.anmcon.play("move")
this.default_anim = 'move'
} }
atk () { atk() {
if(this.anmcon.getState("max0").isPlaying||this.anmcon.getState("max1").isPlaying) return if (this.maxPlaying) return
this.anmcon.play(`atk0`) this.anmcon.play(`atk0`)
} }
max () { max() {
if(this.anmcon.getState("max0").isPlaying||this.anmcon.getState("max1").isPlaying) return if (this.maxPlaying) return
this.anmcon.play("max0") this.anmcon.play("max0")
} }
play(anm:string=""){ play(anm: string = "") {
if(anm==="") return if (anm === "") return
if(this.anmcon.getState("max0").isPlaying||this.anmcon.getState("max1").isPlaying) return if (this.maxPlaying) return
this.anmcon.play(anm) this.anmcon.play(anm)
} }
idle () { idle() {
if(this.anmcon.getState("idle").isPlaying) return if (this.maxPlaying || this.atkPlaying || this.buffPlaying) return
if (this.anmcon.getState("idle").isPlaying) return
this.anmcon.play("idle") this.anmcon.play("idle")
this.default_anim='idle' this.default_anim = 'idle'
} }
buff(){ buff() {
if(this.anmcon.getState("max0").isPlaying||this.anmcon.getState("max1").isPlaying) return if (this.maxPlaying) return
this.anmcon.play("buff") this.anmcon.play("buff")
} }
dead(){ dead() {
} }
} }

View File

@@ -7,7 +7,7 @@
* 2. 战斗开始:PhasePrepareEnd 时恢复怪物行动(in_fight 前怪物仅原地待命,不会推进/索敌)。 * 2. 战斗开始:PhasePrepareEnd 时恢复怪物行动(in_fight 前怪物仅原地待命,不会推进/索敌)。
* *
* 关键设计: * 关键设计:
* - 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场,玩家看清阵容后三选一。 * - 怪物按类型落排(近战/Boss→前排、远程/刺客→中排、辅助/召唤→后排),与英雄三排镜像对应。
* - 实际推进由统一 MoveComp 在战斗中向敌人移动自然形成,不再使用固定网格点。 * - 实际推进由统一 MoveComp 在战斗中向敌人移动自然形成,不再使用固定网格点。
* - 槽位索引仅用于 monGrid 寻路与 SCastSystem 索敌定位。 * - 槽位索引仅用于 monGrid 寻路与 SCastSystem 索敌定位。
*/ */
@@ -19,8 +19,9 @@ import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/O
import { Monster } from "../hero/Mon"; import { Monster } from "../hero/Mon";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent"; import { GameEvent } from "../common/config/GameEvent";
import { BoxSet } from "../common/config/GameSet"; import { BoxSet, FightSet } from "../common/config/GameSet";
import { spawningEngine, GeneratedMonster, TestModeConfig, BOSS_COUNT, MINION_COUNT } from "./RogueConfig"; import { MonType } from "../common/config/heroSet";
import { spawningEngine, GeneratedMonster, TestModeConfig } from "./RogueConfig";
import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { MoveComp } from "../hero/MoveComp"; import { MoveComp } from "../hero/MoveComp";
@@ -33,10 +34,23 @@ export class MissionMonCompComp extends CCComp {
/** 怪物出生掉落高度 */ /** 怪物出生掉落高度 */
private static readonly MON_DROP_HEIGHT = 0; private static readonly MON_DROP_HEIGHT = 0;
/** 怪物登场 X 范围下限(靠近英雄侧) */
private static readonly SPAWN_X_MIN = 100; /**
/** 怪物登场 X 范围上限(靠近右侧) */ * 怪物类型 → 编队优先级(与英雄 HRole 枚举值升序一致):
private static readonly SPAWN_X_MAX = 320; * 近战/重型/近战Boss → 0(最优先,占前排),
* 远程/刺客/远程Boss → 1(占中排),
* 辅助/召唤师 → 2(占后排)。
*/
private static readonly MON_TYPE_PRIORITY: Record<number, number> = {
[MonType.Melee]: 0,
[MonType.Heavy]: 0,
[MonType.MeleeBoss]: 0,
[MonType.Long]: 1,
[MonType.Assassin]: 1,
[MonType.LongBoss]: 1,
[MonType.Support]: 2,
[MonType.Summoner]: 2,
};
/** 槽位基准 Y(3行×4列结构仅供 monGrid 寻路与 SCastSystem 索敌定位) */ /** 槽位基准 Y(3行×4列结构仅供 monGrid 寻路与 SCastSystem 索敌定位) */
public static readonly MON_POSITIONS: Vec3[] = [ public static readonly MON_POSITIONS: Vec3[] = [
@@ -104,11 +118,16 @@ export class MissionMonCompComp extends CCComp {
smc.vmdata.mission_data.pending_mon_num = this.waveTargetCount; smc.vmdata.mission_data.pending_mon_num = this.waveTargetCount;
// 怪物先行登场:玩家侦查 Boss 组合后再做英雄三选一(in_fight 前怪物原地待命,不推进不索敌) // 怪物先行登场:玩家侦查 Boss 组合后再做英雄三选一(in_fight 前怪物原地待命,不推进不索敌)
const squad = this.spawnQueue; // 按编队优先级排序后依次登场:近战/Boss(0) → 远程/刺客(1) → 辅助/召唤(2)
const squad = [...this.spawnQueue].sort((a, b) => {
const pa = MissionMonCompComp.MON_TYPE_PRIORITY[a.type] ?? 1;
const pb = MissionMonCompComp.MON_TYPE_PRIORITY[b.type] ?? 1;
return pa - pb;
});
this.spawnQueue = []; this.spawnQueue = [];
smc.vmdata.mission_data.pending_mon_num = 0; smc.vmdata.mission_data.pending_mon_num = 0;
for (const monData of squad) { for (let i = 0; i < squad.length; i++) {
this.addMonsterAtGrid(this.waveSpawnedCount, monData, this.currentWave); this.addMonsterAtGrid(i, squad[i], this.currentWave);
this.waveSpawnedCount++; this.waveSpawnedCount++;
} }
@@ -126,9 +145,24 @@ export class MissionMonCompComp extends CCComp {
// ======================== 怪物生成 ======================== // ======================== 怪物生成 ========================
/**
* 解析怪物出生 X:按编队优先级名次映射三排锚点(与英雄编队排队逻辑一致)。
* 名次映射:rank0→前排(MON_ROW_FRONT_X)、rank1→中排(MON_ROW_MID_X)、rank≥2→后排(MON_ROW_BACK_X)。
* 效果:高优先级类型永远占据更靠前的排;当更高优先级类型插入时,低优先级类型自动向后让位。
*
* @param index 当前怪物在排序后列表中的索引(名次)
* @returns 出生 X 坐标
*/
private resolveSpawnX(index: number): number {
if (index === 0) return FightSet.MON_ROW_FRONT_X;
if (index === 1) return FightSet.MON_ROW_MID_X;
return FightSet.MON_ROW_BACK_X;
}
/** /**
* 在指定位置索引处生成一个怪物 * 在指定位置索引处生成一个怪物
* 5 只怪在 X∈[100,320] 内按登场序号均匀铺开,Boss/小怪同时登场直接开打 * 怪物按编队优先级名次落排(rank0→前排、rank1→中排、rank≥2→后排),
* 与英雄编队排队逻辑一致:高优先级类型永远占更靠前的排,低优先级自动让位。
*/ */
private addMonsterAtGrid( private addMonsterAtGrid(
posIndex: number, posIndex: number,
@@ -139,9 +173,7 @@ export class MissionMonCompComp extends CCComp {
let scale = -1; let scale = -1;
const basePos = MissionMonCompComp.MON_POSITIONS[posIndex % MissionMonCompComp.MON_POSITIONS.length]; const basePos = MissionMonCompComp.MON_POSITIONS[posIndex % MissionMonCompComp.MON_POSITIONS.length];
const spawnX = MissionMonCompComp.SPAWN_X_MIN const spawnX = this.resolveSpawnX(posIndex);
+ (MissionMonCompComp.SPAWN_X_MAX - MissionMonCompComp.SPAWN_X_MIN)
* (posIndex / Math.max(1, BOSS_COUNT + MINION_COUNT - 1));
const landingY = basePos.y; const landingY = basePos.y;
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0); const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999; this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;

View File

@@ -4,7 +4,7 @@
* *
* 游戏机制: * 游戏机制:
* - 整局只有一场战斗:开局英雄三次三选一后自动开战。 * - 整局只有一场战斗:开局英雄三次三选一后自动开战。
* - 怪物从 BossComboLibrary 按权重随机抽一组(1 Boss + 4 小怪), * - 怪物从 BossComboLibrary 按权重随机抽一组(固定 3 只怪:0~2 Boss + 剩余小怪),
* 战斗开始瞬间一次全量登场(X∈[100,320] 均匀铺开),双方立即互相攻击。 * 战斗开始瞬间一次全量登场(X∈[100,320] 均匀铺开),双方立即互相攻击。
* - 怪物全灭 → 胜利结算;英雄全灭 → 失败结算;无回合递进、无分批刷怪。 * - 怪物全灭 → 胜利结算;英雄全灭 → 失败结算;无回合递进、无分批刷怪。
* *
@@ -18,7 +18,7 @@
* Boss 基础面板固定为初始英雄 2 倍强度(BOSS_BASE_HP/BOSS_BASE_AP),与小怪共用同一套系数, * Boss 基础面板固定为初始英雄 2 倍强度(BOSS_BASE_HP/BOSS_BASE_AP),与小怪共用同一套系数,
* 场上 Boss 恰好为小怪 2 倍强度(如需额外压场可调 BOSS_HP_BONUS_MUL)。 * 场上 Boss 恰好为小怪 2 倍强度(如需额外压场可调 BOSS_HP_BONUS_MUL)。
* 小怪基础面板与 1 级英雄持平(近战镜像 hp300/ap22/cd1.8),默认系数校准为 * 小怪基础面板与 1 级英雄持平(近战镜像 hp300/ap22/cd1.8),默认系数校准为
* 整场战斗(3 英雄 vs 4 小怪 + 1 Boss)双方对砍 ≈ WAVE_DURATION(30s)。 * 整场战斗(3 英雄 vs 3 只怪)双方对砍 ≈ WAVE_DURATION(30s)。
* 手感微调只改 MON_HP_MUL / MON_AP_MUL / BOSS_HP_BONUS_MUL 三个系数即可。 * 手感微调只改 MON_HP_MUL / MON_AP_MUL / BOSS_HP_BONUS_MUL 三个系数即可。
*/ */
@@ -31,9 +31,10 @@ import { FacSet } from "../common/config/GameSet";
/** 单场战斗时长基准(秒):供清屏速度评价(BattleBannerComp 清屏奖励)等使用 */ /** 单场战斗时长基准(秒):供清屏速度评价(BattleBannerComp 清屏奖励)等使用 */
export const WAVE_DURATION = 30; export const WAVE_DURATION = 30;
/** 怪物登场固定构成:1 个 Boss + 4 个小怪(一次全量登场,打完即整局结束) */ /** 怪物登场固定构成:单场战斗固定 3 只怪,Boss 数量由组合配置决定(0~2 个),
export const BOSS_COUNT = 1; * 剩余位置由小怪填充。例如:3 小怪、1 Boss + 2 小怪、2 Boss + 1 小怪(最大难度) */
export const MINION_COUNT = 4; export const BOSS_COUNT = 2;
export const MINION_COUNT = 3;
/** 怪物血量可调系数:最终 hp = HeroInfo 基础hp × MON_HP_MUL。 /** 怪物血量可调系数:最终 hp = HeroInfo 基础hp × MON_HP_MUL。
* 2.6 = 校准后怪物总血量 / 3 英雄 30s 输出(≈30 DPS),双方对砍 ≈ 30s 分胜负 */ * 2.6 = 校准后怪物总血量 / 3 英雄 30s 输出(≈30 DPS),双方对砍 ≈ 30s 分胜负 */
@@ -104,11 +105,13 @@ export interface BossComboMinionSlot {
count: number; count: number;
} }
/** Boss 组合配置:一场战斗随机抽一组登场(1 Boss + minions 小怪) */ /** Boss 组合配置:一场战斗随机抽一组登场(固定 3 只怪,Boss 数量由 bossCount 决定) */
export interface BossComboConfig { export interface BossComboConfig {
id: string; id: string;
name: string; name: string;
/** 小怪构成(合计数量建议等于 MINION_COUNT,超出截断、不足自动补近战) */ /** 本场 Boss 数量(0~2),剩余位置由小怪填充 */
bossCount: number;
/** 小怪构成(合计数量建议等于 MINION_COUNT - bossCount,超出截断、不足自动补近战) */
minions: BossComboMinionSlot[]; minions: BossComboMinionSlot[];
/** Boss 技能池 id(引用 BossSkillPool,可选) */ /** Boss 技能池 id(引用 BossSkillPool,可选) */
boss_skill_pool?: string[]; boss_skill_pool?: string[];
@@ -118,37 +121,46 @@ export interface BossComboConfig {
weight: number; weight: number;
} }
/** Boss 组合库(硬编码):每场战斗按权重随机抽一组 */ /** Boss 组合库(硬编码):每场战斗按权重随机抽一组,固定 3 只怪。
* 组合类型:
* - 3 小怪(0 Boss):均衡难度
* - 1 Boss + 2 小怪:标准难度
* - 2 Boss + 1 小怪:最大难度 */
export const BossComboLibrary: Record<string, BossComboConfig> = { export const BossComboLibrary: Record<string, BossComboConfig> = {
/** 3 小怪:均衡组合 */
rage_charge: { rage_charge: {
id: "rage_charge", name: "狂暴冲锋组", weight: 10, id: "rage_charge", name: "狂暴冲锋组", weight: 10, bossCount: 0,
minions: [{ type: MonType.Melee, count: 3 }, { type: MonType.Heavy, count: 1 }], minions: [{ type: MonType.Melee, count: 2 }, { type: MonType.Heavy, count: 1 }],
boss_skill_pool: ["boss_rage"],
}, },
iron_wall: { iron_wall: {
id: "iron_wall", name: "铁壁推进组", weight: 8, id: "iron_wall", name: "铁壁推进组", weight: 8, bossCount: 0,
minions: [{ type: MonType.Heavy, count: 2 }, { type: MonType.Support, count: 1 }, { type: MonType.Long, count: 1 }], minions: [{ type: MonType.Heavy, count: 2 }, { type: MonType.Support, count: 1 }],
boss_skill_pool: ["boss_iron"],
skill_pool: ["tough"], skill_pool: ["tough"],
}, },
doom_ritual: { doom_ritual: {
id: "doom_ritual", name: "毁灭仪式组", weight: 6, id: "doom_ritual", name: "毁灭仪式组", weight: 6, bossCount: 0,
minions: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }], minions: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Support, count: 1 }],
boss_skill_pool: ["boss_doom"],
skill_pool: ["warcry"], skill_pool: ["warcry"],
}, },
/** 1 Boss + 2 小怪:标准难度 */
assassin_raid: { assassin_raid: {
id: "assassin_raid", name: "刺客突袭组", weight: 7, id: "assassin_raid", name: "刺客突袭组", weight: 7, bossCount: 1,
minions: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Melee, count: 2 }], minions: [{ type: MonType.Assassin, count: 1 }, { type: MonType.Melee, count: 1 }],
boss_skill_pool: ["boss_rage", "boss_doom"], boss_skill_pool: ["boss_rage", "boss_doom"],
skill_pool: ["berserk"], skill_pool: ["berserk"],
}, },
blood_legion: { blood_legion: {
id: "blood_legion", name: "吸血军团组", weight: 5, id: "blood_legion", name: "吸血军团组", weight: 5, bossCount: 1,
minions: [{ type: MonType.Melee, count: 2 }, { type: MonType.Long, count: 1 }, { type: MonType.Summoner, count: 1 }], minions: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }],
boss_skill_pool: ["boss_iron", "boss_doom"], boss_skill_pool: ["boss_iron", "boss_doom"],
skill_pool: ["leech"], 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"],
},
}; };
/** /**
@@ -277,7 +289,7 @@ export const BossSkillPool: Record<string, MonSkillSet> = {
export function validateRogueConfig(): string[] { export function validateRogueConfig(): string[] {
const errors: string[] = []; const errors: string[] = [];
// 1. 校验组合引用与数量 // 1. 校验组合引用与数量(固定 3 只怪:bossCount + minionTotal 必须等于 MINION_COUNT)
for (const id in BossComboLibrary) { for (const id in BossComboLibrary) {
const combo = BossComboLibrary[id]; const combo = BossComboLibrary[id];
let minionTotal = 0; let minionTotal = 0;
@@ -287,8 +299,12 @@ export function validateRogueConfig(): string[] {
errors.push(`组合 ${id} 引用的怪物类型 ${slot.type}(${MonTypeName[slot.type] || "?"}) 在 MonList 中无可用 uuid`); errors.push(`组合 ${id} 引用的怪物类型 ${slot.type}(${MonTypeName[slot.type] || "?"}) 在 MonList 中无可用 uuid`);
} }
} }
if (minionTotal !== MINION_COUNT) { const totalMon = combo.bossCount + minionTotal;
errors.push(`组合 ${id} 小怪数量=${minionTotal} 与 MINION_COUNT=${MINION_COUNT} 不一致`); 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) { if (combo.skill_pool) {
for (const skillId of combo.skill_pool) { for (const skillId of combo.skill_pool) {
@@ -371,7 +387,7 @@ export const TestModeConfig = {
export class RogueSpawningEngine { export class RogueSpawningEngine {
/** /**
* 生成一场战斗的怪物列表:随机一组 Boss 组合(1 Boss + 4 小怪,属性走基础×系数)。 * 生成一场战斗的怪物列表:随机一组 Boss 组合(固定 3 只怪,Boss 数量由 combo.bossCount 决定)。
* Boss 压队尾(登场时由 MissionMonComp 统一布位)。 * Boss 压队尾(登场时由 MissionMonComp 统一布位)。
* @returns 怪物列表(小怪在前、Boss 在尾) * @returns 怪物列表(小怪在前、Boss 在尾)
*/ */
@@ -383,6 +399,7 @@ export class RogueSpawningEngine {
const combo = pickBossCombo(); const combo = pickBossCombo();
const monsters: GeneratedMonster[] = []; const monsters: GeneratedMonster[] = [];
const bossCount = combo ? Math.max(0, Math.min(combo.bossCount, BOSS_COUNT)) : 0;
if (combo) { if (combo) {
for (const slot of combo.minions) { for (const slot of combo.minions) {
@@ -391,13 +408,14 @@ export class RogueSpawningEngine {
} }
} }
} }
// 组合库为空或数量不足兜底:补近战小怪至 MINION_COUNT // 组合库为空或数量不足兜底:补近战小怪至 MINION_COUNT - bossCount
while (monsters.length < MINION_COUNT) { const minionTarget = MINION_COUNT - bossCount;
while (monsters.length < minionTarget) {
monsters.push(this.makeMonster(MonType.Melee, monsters.length)); monsters.push(this.makeMonster(MonType.Melee, monsters.length));
} }
// Boss 压队尾 // Boss 压队尾
for (let i = 0; i < BOSS_COUNT; i++) { for (let i = 0; i < bossCount; i++) {
monsters.push(this.makeBoss()); monsters.push(this.makeBoss());
} }