diff --git a/assets/script/game/map/MissionComp.ts b/assets/script/game/map/MissionComp.ts index 26c5793f..7cb591ba 100644 --- a/assets/script/game/map/MissionComp.ts +++ b/assets/script/game/map/MissionComp.ts @@ -46,6 +46,7 @@ import { Tooltip } from "../skill/Tooltip"; import { CardInitCoins } from "../common/config/CardSet"; import { Timer } from "db://oops-framework/core/common/timer/Timer"; import { FieldSkillType } from "../common/config/SkillSet"; +import { spawningEngine } from "./RogueConfig"; const { ccclass, property } = _decorator; /** 任务(关卡)生命周期阶段 */ @@ -118,8 +119,8 @@ export class MissionComp extends CCComp { // ======================== 运行时状态 ======================== - /** 战斗倒计时(秒) */ - FightTime:number = FightSet.FiIGHT_TIME + /** 战斗已耗时(秒),正向计时 */ + clearTime:number = 0 /** 剩余复活次数 */ revive_times: number = 1; /** 掉落奖励列表 */ @@ -234,14 +235,7 @@ export class MissionComp extends CCComp { this.syncMonsterSpawnState(dt) if(smc.mission.stop_mon_action) return smc.vmdata.mission_data.fight_time+=dt - if (!this.isBossWave) { - this.FightTime-=dt - if (this.FightTime <= 0) { - // 时间到了,自动结束战斗进入准备阶段 - this.FightTime = FightSet.FiIGHT_TIME; - oops.message.dispatchEvent("TimeUpAdvanceWave"); - } - } + this.clearTime += dt this.update_time(); } } @@ -250,29 +244,11 @@ export class MissionComp extends CCComp { /** 更新时间/波数显示(仅在秒数变化时更新以减少 Label 操作) */ update_time(){ - if (this.isBossWave) { - const str = "∞"; - if (str != this.lastTimeStr) { - if (this.time_node && this.time_node.isValid) { - const timeChild = this.time_node.getChildByName("time"); - if (timeChild) { - const label = timeChild.getComponent(Label); - if (label) label.string = str; - } - } - this.lastTimeStr = str; - this.lastTimeSecond = -1; - } - return; - } - - const time = Math.max(0, this.FightTime); - const remainSecond = Math.floor(time); + const remainSecond = Math.floor(this.clearTime); if (remainSecond === this.lastTimeSecond) return; this.lastTimeSecond = remainSecond; let m = Math.floor(remainSecond / 60); let s = remainSecond % 60; - const wave = Math.max(1, this.currentWave || smc.vmdata.mission_data.level || 1); let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; if(str != this.lastTimeStr){ if (this.time_node && this.time_node.isValid) { @@ -422,7 +398,7 @@ export class MissionComp extends CCComp { const label = phaseNode.getComponent(Label); if (label) { const wave = Math.max(1, this.currentWave || (smc.vmdata && smc.vmdata.mission_data ? smc.vmdata.mission_data.level : 1) || 1); - label.string = `第 ${wave}/20 波`; + label.string = `第 ${wave}/30 波`; } // 阶段切换动感表现:只在进入战斗阶段跳动一下,让流程充满心流体验 @@ -497,8 +473,8 @@ export class MissionComp extends CCComp { smc.vmdata.scores.wave_all_alive_count++; } - // 【评分系统 - 战绩分】判断是否通过最后一关(第20回合) - if (this.currentWave === 20) { + // 【评分系统 - 战绩分】判断是否通过最后一关(第30回合) + if (this.currentWave === 30) { smc.vmdata.scores.passed_wave_20 = true; } } @@ -721,13 +697,13 @@ export class MissionComp extends CCComp { smc.mission.stop_spawn_mon = false; smc.vmdata.mission_data.in_fight=false smc.vmdata.mission_data.fight_time=0 + this.clearTime = 0 smc.vmdata.mission_data.mon_num=0 smc.vmdata.mission_data.level = 1 smc.vmdata.mission_data.mon_max = Math.max(1, Math.floor(this.maxMonsterCount)) this.currentPhase = MissionPhase.None; this.currentWave = 1; this.isBossWave = false; - this.FightTime=FightSet.FiIGHT_TIME this.rewards=[] this.revive_times = 1; this.lastTimeStr = ""; @@ -744,6 +720,8 @@ export class MissionComp extends CCComp { this.monsterCountSyncTimer = 0; this.lastPrepareCoinWave = 0; + spawningEngine.reset(); + // 重置所有的战局得分数据,防止上一局的数据污染 smc.resetScores(); @@ -786,7 +764,7 @@ export class MissionComp extends CCComp { smc.vmdata.mission_data.level = wave; this.grantPrepareCoinByWave(wave); this.lastTimeSecond = -1; - this.FightTime = FightSet.FiIGHT_TIME; + this.clearTime = 0; this.update_time(); // 检查并推送卡池升级事件 @@ -877,8 +855,17 @@ export class MissionComp extends CCComp { // 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,直接结束战斗进入下一波的准备阶段 if (monsterCount === 0 && smc.mission.play && !smc.mission.pause && this.currentPhase === MissionPhase.Battle) { - this.FightTime = FightSet.FiIGHT_TIME; - oops.message.dispatchEvent("TimeUpAdvanceWave"); + let heroesAliveRatio = heroCount / 6.0; // 假设最大 6 个站位,或者直接基于存活数算比例 + // 如果能获取当前已部署英雄数最好,这里简化处理,大于 4 个就算高存活 + heroesAliveRatio = Math.min(1.0, heroCount / 4.0); + spawningEngine.updateAdaptive(heroesAliveRatio, this.clearTime); + + if (this.currentWave >= 30) { + // 30 波通关 + this.open_Victory(null, false); + } else { + oops.message.dispatchEvent("TimeUpAdvanceWave"); + } return; } diff --git a/assets/script/game/map/MissionMonComp.ts b/assets/script/game/map/MissionMonComp.ts index 1ba10c4e..24469ab1 100644 --- a/assets/script/game/map/MissionMonComp.ts +++ b/assets/script/game/map/MissionMonComp.ts @@ -35,7 +35,7 @@ import { HeroInfo, HType } from "../common/config/heroSet"; import { smc } from "../common/SingletonModuleComp"; import { GameEvent } from "../common/config/GameEvent"; import {BoxSet, FacSet } from "../common/config/GameSet"; -import { MonList, MonType, SpawnPowerBias, StageBossGrow, StageGrow, UpType, WaveSlotConfig, DefaultWaveSlot, IWaveSlot } from "./RogueConfig"; +import { spawningEngine, GeneratedMonster, AffixType, MonType, MonList } from "./RogueConfig"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { MonMoveComp } from "../hero/MonMoveComp"; const { ccclass, property } = _decorator; @@ -94,6 +94,10 @@ export class MissionMonCompComp extends CCComp { private waveTargetCount: number = 0; /** 当前波已生成的怪物数量 */ private waveSpawnedCount: number = 0; + /** 等待生成的怪物队列(由新肉鸽引擎提供) */ + private pendingMonsters: GeneratedMonster[] = []; + /** 增量刷怪计时器 */ + private spawnTimer: number = 0; // ======================== 生命周期 ======================== @@ -108,14 +112,35 @@ export class MissionMonCompComp extends CCComp { * 帧更新: * 1. 检查游戏是否运行中。 * 2. 处理插队刷怪队列。 + * 3. 逐步从 pendingMonsters 队列中生成怪物(受 stop_spawn_mon 限制)。 */ protected update(dt: number): void { if(!smc.mission.play) return if(smc.mission.pause) return if(smc.mission.stop_mon_action) return; if(!smc.mission.in_fight) return; - if(smc.mission.stop_spawn_mon) return; + this.updateSpecialQueue(dt); + + if(smc.mission.stop_spawn_mon) return; + + // 逐步刷怪逻辑 + if (this.pendingMonsters.length > 0) { + this.spawnTimer += dt; + // 控制刷怪速率:例如每 0.2 秒刷 1-2 只 + if (this.spawnTimer > 0.2) { + this.spawnTimer = 0; + // 一次出 2 只,加快进度 + for (let i = 0; i < 2; i++) { + if (this.pendingMonsters.length === 0) break; + const monData = this.pendingMonsters.shift()!; + const lane = this.pickBalancedLane(); + this.addMonsterAt(lane, this.laneIndices[lane], monData); + this.laneIndices[lane]++; + this.waveSpawnedCount++; + } + } + } } // ======================== 事件处理 ======================== @@ -152,15 +177,16 @@ export class MissionMonCompComp extends CCComp { this.waveTargetCount = 0 this.waveSpawnedCount = 0 this.MonQueue = [] + this.pendingMonsters = [] + this.spawnTimer = 0 this.laneIndices = [0, 0, 0]; - let hasBoss = false; - const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot; - for (const slot of config) { - if (slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss) { - hasBoss = true; - } - } + // 预生成第一波数据以获取数量和 Boss 信息 + const monsters = spawningEngine.generateWave(this.currentWave); + this.pendingMonsters = monsters; + this.waveTargetCount = monsters.length; + let hasBoss = monsters.some(m => m.isBoss); + oops.message.dispatchEvent(GameEvent.NewWave, { wave: this.currentWave, total: this.waveTargetCount, @@ -199,13 +225,22 @@ export class MissionMonCompComp extends CCComp { this.queueTimer = 0; const isBoss = MonList[MonType.MeleeBoss].includes(item.uuid) || - MonList[MonType.LongBoss].includes(item.uuid) || - (MonList[MonType.FlyBoss] && MonList[MonType.FlyBoss].includes(item.uuid)); + MonList[MonType.LongBoss].includes(item.uuid); - const upType = this.getRandomUpType(); const lane = item.flyLane !== undefined && item.flyLane >= 0 && item.flyLane <= 2 ? item.flyLane : this.pickBalancedLane(); - this.addMonsterAt(lane, this.laneIndices[lane], item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1))); + // 构造一个模拟的 GeneratedMonster 数据传递给 addMonsterAt + const base = HeroInfo[item.uuid]; + const monData: GeneratedMonster = { + uuid: item.uuid, + type: MonType.Melee, // 简化的兜底,真实逻辑依赖 heroSet 配置 + hp: base ? base.hp : 100, + ap: base ? base.ap : 10, + affixes: [], + isBoss: isBoss, + spawnIndex: 0 + }; + this.addMonsterAt(lane, this.laneIndices[lane], monData, item.level); this.laneIndices[lane]++; } @@ -214,25 +249,21 @@ export class MissionMonCompComp extends CCComp { /** * 开始下一波: * 1. 波数 +1 并更新全局数据。 - * 2. 重置槽位并根据配置生成本波所有怪物。 - * 3. 分发 NewWave 事件。 + * 2. 分发 NewWave 事件(实际的生成在 resetSlotSpawnData 中触发)。 */ private onTimeUpAdvanceWave() { this.currentWave += 1; smc.vmdata.mission_data.level = this.currentWave; - // 检查本波是否有 Boss - let hasBoss = false; - const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot; - for (const slot of config) { - if (slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss) { - hasBoss = true; - } - } + // 预生成新一波数据以获取数量和 Boss 信息 + const monsters = spawningEngine.generateWave(this.currentWave); + this.pendingMonsters = monsters; + this.waveTargetCount = monsters.length; + let hasBoss = monsters.some(m => m.isBoss); oops.message.dispatchEvent(GameEvent.NewWave, { wave: this.currentWave, - total: this.waveTargetCount, // 此时还是上一波的怪物数量,但可以不传或后续修正 + total: this.waveTargetCount, bossWave: hasBoss, }); } @@ -241,60 +272,12 @@ export class MissionMonCompComp extends CCComp { this.resetSlotSpawnData(this.currentWave); } - /** 获取当前阶段(stage = wave - 1,用于属性成长计算) */ - private getCurrentStage(): number { - return Math.max(0, this.currentWave - 1); - } - - // ======================== 随机选取 ======================== - - /** 随机选取一种成长类型 */ - private getRandomUpType(): UpType { - const keys = Object.keys(StageGrow).map(v => Number(v) as UpType); - const index = Math.floor(Math.random() * keys.length); - return keys[index] ?? UpType.AP1_HP1; - } - - /** - * 根据怪物类型从对应池中随机选取 UUID。 - * @param monType 怪物类型(MonType 枚举值) - * @returns 怪物 UUID - */ - private getRandomUuidByType(monType: number): number { - const pool = (MonList as any)[monType] || MonList[MonType.Melee]; - if (!pool || pool.length === 0) return 6001; - const index = Math.floor(Math.random() * pool.length); - return pool[index]; - } - - /** - * 计算怪物属性成长值对。 - * Boss 在普通成长基础上叠加 StageBossGrow。 - * - * @param upType 成长类型 - * @param isBoss 是否为 Boss - * @returns [AP 成长值, HP 成长值] - */ - private resolveGrowPair(upType: UpType, isBoss: boolean): [number, number] { - const grow = StageGrow[upType] || StageGrow[UpType.AP1_HP1]; - if (!isBoss) return [grow[0], grow[1]]; - const bossGrow = StageBossGrow[upType] || StageBossGrow[UpType.AP1_HP1]; - return [grow[0] + bossGrow[0], grow[1] + bossGrow[1]]; - } - - /** 获取全局刷怪强度系数 */ - private getSpawnPowerBias(): number { - return SpawnPowerBias; - } - // ======================== 槽位管理 ======================== /** - * 重新分配并生成本波所有怪物: + * 重新分配本波所有怪物状态: * 1. 清理上一波残留怪物。 - * 2. 读取波次配置。 - * 3. 依据配置和 flyLane 属性,为每只怪物分配自增索引。 - * 4. 立即实例化所有怪物。 + * 2. pendingMonsters 已在 onTimeUpAdvanceWave / fight_ready 中准备好,只需重置 laneIndices 即可。 * * @param wave 当前波数 */ @@ -306,44 +289,10 @@ export class MissionMonCompComp extends CCComp { e.destroy(); } }); - - // 2. 读取波次配置 - const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot; - // 3. 重置排号索引 + // 2. 重置排号索引 this.laneIndices = [0, 0, 0]; - - let allMons: any[] = []; - - // 解析配置 - for (const slot of config) { - const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss; - - for (let i = 0; i < slot.count; i++) { - const uuid = this.getRandomUuidByType(slot.type); - const upType = this.getRandomUpType(); - // 优先使用配置的 lane,否则均衡分配 - let lane = slot.flyLane !== undefined ? slot.flyLane : this.pickBalancedLane(); - lane = Math.max(0, Math.min(2, lane)); - - const req = { uuid, isBoss, upType, monLv: wave, lane }; - allMons.push(req); - // 提前累加 laneIndices,以便本波内的均衡分配能正确计算 - this.laneIndices[lane]++; - } - } - - this.waveTargetCount = allMons.length; this.waveSpawnedCount = 0; - - // 由于上面循环中已经累加了 laneIndices,这里需要重置,以便下面真正生成时再累加(或者直接利用 allMons 生成) - this.laneIndices = [0, 0, 0]; - - // 4. 立即生成本波所有怪物 - for (const req of allMons) { - this.addMonsterAt(req.lane, this.laneIndices[req.lane], req.uuid, req.isBoss, req.upType, req.monLv); - this.laneIndices[req.lane]++; - } } // ======================== 怪物生成 ======================== @@ -353,17 +302,13 @@ export class MissionMonCompComp extends CCComp { * * @param laneIndex 三路索引 (0 上, 1 中, 2 下) * @param monIndex 该路级的第几个怪 (0, 1, 2...) - * @param uuid 怪物 UUID - * @param isBoss 是否为 Boss - * @param upType 属性成长类型 - * @param monLv 怪物等级 + * @param monData 新引擎生成的怪物数据 (含 uuid, hp, ap, affixes 等) + * @param monLv 怪物等级 (仅对旧有的 level 参数做兼容,实际属性由 monData 决定) */ private addMonsterAt( laneIndex: number, monIndex: number, - uuid: number = 1001, - isBoss: boolean = false, - upType: UpType = UpType.AP1_HP1, + monData: GeneratedMonster, monLv: number = 1 ) { let mon = ecs.getEntity(Monster); @@ -371,11 +316,11 @@ export class MissionMonCompComp extends CCComp { // 计算坐标 const spawnX = MissionMonCompComp.MON_SPAWN_START_X + monIndex * MissionMonCompComp.MON_SPAWN_GAP_X; - const landingY = BoxSet.GAME_LINE + MissionMonCompComp.LANE_Y_OFFSETS[laneIndex] + (isBoss ? 6 : 0); + const landingY = BoxSet.GAME_LINE + MissionMonCompComp.LANE_Y_OFFSETS[laneIndex] + (monData.isBoss ? 6 : 0); const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0); this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999; - mon.load(spawnPos, scale, uuid, isBoss, landingY, monLv, laneIndex); + mon.load(spawnPos, scale, monData.uuid, monData.isBoss, landingY, monLv, laneIndex); // 设置渲染排序 const move = mon.get(MonMoveComp); @@ -383,16 +328,15 @@ export class MissionMonCompComp extends CCComp { move.spawnOrder = this.globalSpawnOrder; } - // 计算最终属性 + // 应用新引擎计算好的最终属性和词缀 const model = mon.get(HeroAttrsComp); - const base = HeroInfo[uuid]; - if (!model || !base) return; - const stage = this.getCurrentStage(); - const grow = this.resolveGrowPair(upType, isBoss); - const bias = Math.max(0.1, this.getSpawnPowerBias()); - model.ap = Math.max(1, Math.floor((base.ap + stage * grow[0]) * bias)); - model.hp_max = Math.max(1, Math.floor((base.hp + stage * grow[1]) * bias)); + if (!model) return; + model.ap = monData.ap; + model.hp_max = monData.hp; model.hp = model.hp_max; + + // 将词缀记录到属性组件上,供战斗层使用 + (model as any).affixes = monData.affixes || []; } /** ECS 组件移除时触发(当前不销毁节点) */