feat(战斗): 实现战斗时间结束和怪物全灭自动推进波次

- 将战斗时间从600秒改为30秒,便于测试
- 战斗倒计时归零或场上怪物全灭时,自动结束当前波次并进入准备阶段
- 怪物波次切换时,新怪物继承被覆盖旧怪物的部分属性(生命值和攻击力)
- 调整波次初始化逻辑,确保战斗时间在每波开始时重置
- 新增事件 PhasePrepareEnd 和 TimeUpAdvanceWave 协调阶段切换
This commit is contained in:
panw
2026-04-15 15:48:41 +08:00
parent 3a07a7e9d2
commit 083a530a72
3 changed files with 82 additions and 19 deletions

View File

@@ -103,6 +103,8 @@ export class MissionMonCompComp extends CCComp {
onLoad(){
this.on(GameEvent.FightReady,this.fight_ready,this)
this.on("SpawnSpecialMonster", this.onSpawnSpecialMonster, this);
this.on("PhasePrepareEnd", this.onPhasePrepareEnd, this);
this.on("TimeUpAdvanceWave", this.onTimeUpAdvanceWave, this);
this.resetSlotSpawnData(1)
}
@@ -119,7 +121,6 @@ export class MissionMonCompComp extends CCComp {
if(smc.mission.stop_mon_action) return;
if(!smc.mission.in_fight) return;
this.refreshSlotOccupancy();
this.tryAdvanceWave();
if(!smc.mission.in_fight) return;
if(smc.mission.stop_spawn_mon) return;
this.updateSpecialQueue(dt);
@@ -154,11 +155,25 @@ export class MissionMonCompComp extends CCComp {
smc.mission.stop_spawn_mon = false
this.globalSpawnOrder = 0
this.queueTimer = 0
this.currentWave = 0
this.currentWave = 1
this.waveTargetCount = 0
this.waveSpawnedCount = 0
this.MonQueue = []
this.startNextWave()
let hasBoss = false;
const config = WaveSlotConfig[this.currentWave] || DefaultWaveSlot;
for (const slot of config) {
if (slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss) {
hasBoss = true;
}
}
oops.message.dispatchEvent(GameEvent.NewWave, {
wave: this.currentWave,
total: this.waveTargetCount,
bossWave: hasBoss,
});
// 不再直接调用 startNextWave(),等待进入 PrepareEnd 阶段再刷怪
mLogger.log(this.debugMode, 'MissionMonComp', "[MissionMonComp] Starting Wave System");
}
@@ -224,10 +239,9 @@ export class MissionMonCompComp extends CCComp {
* 2. 重置槽位并根据配置生成本波所有怪物。
* 3. 分发 NewWave 事件。
*/
private startNextWave() {
private onTimeUpAdvanceWave() {
this.currentWave += 1;
smc.vmdata.mission_data.level = this.currentWave;
this.resetSlotSpawnData(this.currentWave);
// 检查本波是否有 Boss
let hasBoss = false;
@@ -240,20 +254,13 @@ export class MissionMonCompComp extends CCComp {
oops.message.dispatchEvent(GameEvent.NewWave, {
wave: this.currentWave,
total: this.waveTargetCount,
total: this.waveTargetCount, // 此时还是上一波的怪物数量,但可以不传或后续修正
bossWave: hasBoss,
});
}
/**
* 尝试推进波次:
* 条件:队列为空 + 所有槽位无活怪 + 全局怪物数为 0。
*/
private tryAdvanceWave() {
if (this.MonQueue.length > 0) return;
if (this.hasActiveSlotMonster()) return;
if (smc.vmdata.mission_data.mon_num > 0) return;
this.startNextWave();
private onPhasePrepareEnd() {
this.resetSlotSpawnData(this.currentWave);
}
/** 获取当前阶段stage = wave - 1用于属性成长计算 */
@@ -316,6 +323,7 @@ export class MissionMonCompComp extends CCComp {
*/
private resetSlotSpawnData(wave: number = 1) {
const config: IWaveSlot[] = WaveSlotConfig[wave] || DefaultWaveSlot;
const oldOccupiedEids = [...this.slotOccupiedEids];
this.slotOccupiedEids = Array(MissionMonCompComp.MAX_SLOTS).fill(null);
let allMons: any[] = [];
@@ -368,11 +376,49 @@ export class MissionMonCompComp extends CCComp {
}
}
let absorbedEids = new Set<number>();
// 立即生成本波所有怪物
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
const req = assignedSlots[i];
if (req && req !== "occupied") {
this.addMonsterBySlot(i, req.uuid, req.isBoss, req.upType, req.monLv, req.slotsPerMon);
let inheritedHp = 0;
let inheritedAp = 0;
for (let j = 0; j < req.slotsPerMon; j++) {
let oldEid = oldOccupiedEids[i + j];
if (oldEid && !absorbedEids.has(oldEid)) {
absorbedEids.add(oldEid);
const entity = ecs.getEntityByEid(oldEid);
if (entity) {
const attrs = entity.get(HeroAttrsComp);
if (attrs && attrs.hp > 0 && !attrs.is_dead) {
inheritedHp += attrs.hp;
inheritedAp += Math.floor(attrs.ap / 2);
}
entity.destroy();
}
}
oldOccupiedEids[i + j] = null;
}
this.addMonsterBySlot(i, req.uuid, req.isBoss, req.upType, req.monLv, req.slotsPerMon, inheritedHp, inheritedAp);
}
}
// 清理被覆盖但没有被新怪占用槽位的旧怪(或者保留它们)
// 按照需求,保留未被覆盖的旧怪物
for (let i = 0; i < MissionMonCompComp.MAX_SLOTS; i++) {
if (oldOccupiedEids[i]) {
const entity = ecs.getEntityByEid(oldOccupiedEids[i]!);
if (entity) {
const attrs = entity.get(HeroAttrsComp);
if (attrs && attrs.hp > 0 && !attrs.is_dead) {
this.slotOccupiedEids[i] = oldOccupiedEids[i];
} else {
entity.destroy();
}
}
}
}
}
@@ -430,6 +476,8 @@ export class MissionMonCompComp extends CCComp {
upType: UpType = UpType.AP1_HP1,
monLv: number = 1,
slotsPerMon: number = 1,
inheritedHp: number = 0,
inheritedAp: number = 0,
) {
let mon = ecs.getEntity<Monster>(Monster);
let scale = -1;
@@ -464,8 +512,8 @@ export class MissionMonCompComp extends CCComp {
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));
model.ap = Math.max(1, Math.floor((base.ap + stage * grow[0]) * bias)) + inheritedAp;
model.hp_max = Math.max(1, Math.floor((base.hp + stage * grow[1]) * bias)) + inheritedHp;
model.hp = model.hp_max;
}