refactor: 重构关卡战斗流程与AI逻辑,改为单场Boss战模式

1.  近战AI新增警戒线机制,无威胁时退回编队
2.  替换多回合波次为单场Boss战,简化刷怪与回合逻辑
3.  重构招募系统为开局免费三选一流程
4.  调整英雄与怪物基础属性平衡
5.  精简HUD与配置文件冗余代码
This commit is contained in:
panFD
2026-08-13 00:21:54 +08:00
parent a905db8e48
commit c27c81cfa6
9 changed files with 1513 additions and 2202 deletions

View File

@@ -48,7 +48,7 @@ import { Tooltip } from "../skill/Tooltip";
import { Timer } from "db://oops-framework/core/common/timer/Timer";
import { FieldSkillType } from "../common/config/SkillSet";
import { FieldSkillHelper } from "../hero/FieldSkillHelper";
import { spawningEngine, MAX_WAVE, DynamicTuner, WAVE_TIMEOUT, WAVE_TIMEOUT_BOSS, BATCH_INTERVAL, BATCH_COUNT, WAVE_DURATION } from "./RogueConfig";
import { spawningEngine, WAVE_DURATION, BATTLE_TIMEOUT } from "./RogueConfig";
const { ccclass, property } = _decorator;
/** 任务(关卡)生命周期阶段 */
@@ -82,11 +82,6 @@ export class MissionComp extends CCComp {
// ======================== 配置参数 ========================
/** 怪物数量上限(超过后暂停刷怪):略高于单回合上限 MAX_MONSTERS(54),只有跨回合堆积才触发泄压 */
private maxMonsterCount: number = 60;
/** 怪物数量恢复阈值(降至此值以下恢复刷怪,需留空间接纳后续批次,单批最大约 18 只) */
private resumeMonsterCount: number = 40;
// ======================== 编辑器绑定节点 ========================
/** 开始战斗按钮 */
@@ -196,8 +191,8 @@ export class MissionComp extends CCComp {
this.on(GameEvent.NewWave, this.onNewWave, this)
this.on(GameEvent.DO_AD_BACK, this.do_ad, this)
this.start_btn?.on(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this)
// 第一回合准备阶段,首个英雄召唤后自动开始战斗(无需玩家点击开始按钮)
oops.message.on(GameEvent.MasterCalled, this.onHeroSummoned, this);
// 开局免费三选一选完后自动进入战斗
oops.message.on("OpeningPicksDone", this.onOpeningPicksDone, this);
this.removeMemoryPanel()
}
@@ -219,7 +214,7 @@ export class MissionComp extends CCComp {
if (this.start_btn && this.start_btn.isValid) {
this.start_btn.off(NodeEventType.TOUCH_END, this.onStartFightBtnClick, this);
}
oops.message.off(GameEvent.MasterCalled, this.onHeroSummoned, this);
oops.message.off("OpeningPicksDone", this.onOpeningPicksDone, this);
}
/**
@@ -259,9 +254,8 @@ export class MissionComp extends CCComp {
this.clearTime += dt
this.update_time();
// 回合超时兜底:僵局(坦克 vs 高血 Boss 互磨不死)时强制收束回合Boss 回合阈值放宽
const timeout = this.isBossWave ? WAVE_TIMEOUT_BOSS : WAVE_TIMEOUT;
if (this.clearTime >= timeout) {
// 战斗超时兜底:僵局(坦克 vs 高血 Boss 互磨不死)时强制结算整局
if (this.clearTime >= BATTLE_TIMEOUT) {
this.onBattleTimeout();
}
}
@@ -522,11 +516,11 @@ export class MissionComp extends CCComp {
case MissionPhase.BattleStart:
// 触发战斗开始技能fstart
this.triggerHeroBattleSkills(true);
// Boss 回合预警:战斗正式开始、Boss 压轴进场(最后一批),提前给 UI 表现窗口
// Boss 预警:Boss 同步场立即登场eta=0
if (this.isBossWave) {
oops.message.dispatchEvent(GameEvent.BossWarning, {
wave: this.currentWave,
eta: BATCH_INTERVAL * (BATCH_COUNT - 1),
eta: 0,
});
}
break;
@@ -570,25 +564,16 @@ export class MissionComp extends CCComp {
const heroDeathCount = smc.mission.dead_heroes.length;
if (heroDeathCount > 0) allAlive = false;
// 【动态难度调节】根据本回合战况自动放水 / 加压
// 清场加速节省的时间还原进口径,避免"打得好"被节奏加速+强度加压双重惩罚
const effectiveClearTime = this.clearTime + smc.vmdata.mission_data.wave_early_skip;
const tuned = DynamicTuner.adjust(effectiveClearTime, heroDeathCount);
if (tuned) {
mLogger.log(this.debugMode, 'MissionComp', `[DynamicTuner] wave=${this.currentWave} effClear=${effectiveClearTime.toFixed(1)}s deaths=${heroDeathCount} factor=${DynamicTuner.factor.toFixed(3)}`);
}
// 快照供下一回合倒计时档位判定onNewWave 会清零 clearTime必须提前快照
// 快照供倒计时档位判定onNewWave 会清零 clearTime必须提前快照
this.lastWaveDeathCount = heroDeathCount;
this.lastWaveClearTime = effectiveClearTime;
this.lastWaveClearTime = this.clearTime;
// 【评分系统 - 战绩分】记录全员存活的胜利回合数(额外加分)
if (hasHero && allAlive) {
smc.vmdata.scores.wave_all_alive_count++;
}
// 【评分系统 - 战绩分】判断是否通过最后一关第20回合
if (this.currentWave === MAX_WAVE) {
smc.vmdata.scores.passed_wave_20 = true;
}
// 【评分系统 - 战绩分】单场 Boss 战定胜负,通过即视为通关
smc.vmdata.scores.passed_wave_20 = true;
}
// 触发战斗结束技能fend
@@ -751,13 +736,8 @@ export class MissionComp extends CCComp {
this.to_fight();
}
/**
* 英雄召唤事件回调:
* 第一回合准备阶段Prepare只有第 1 回合才会进入,
* 此时首个英雄一旦召唤即自动开战,无需玩家点击开始按钮。
* 后续回合走 PrepareStart 倒计时自动流转,不会进入 Prepare。
*/
private onHeroSummoned() {
/** 开局免费三选一选完,自动进入战斗(等效于点击开始按钮) */
private onOpeningPicksDone() {
if (this.currentPhase !== MissionPhase.Prepare) return;
if (!smc.mission.play || smc.mission.pause) return;
this.to_fight();
@@ -877,7 +857,7 @@ export class MissionComp extends CCComp {
const wave = Number(data?.wave ?? 0);
if (wave <= 0) return;
this.isBossWave = !!data?.bossWave;
this.isBossWave = true; // 单场 Boss 战,恒为 Boss 战(超时阈值/预警走 Boss 档)
if (wave > 1) {
// 在新一回合到来时,先进入 BattleEnd触发上一回合的战斗结束技能 (fend)2秒后自动进入下一回合的准备阶段
@@ -901,21 +881,10 @@ export class MissionComp extends CCComp {
// ======================== 怪物数量管理 ========================
/**
* 获取怪物数量阈值配置。
* @returns { max: 刷怪上限, resume: 恢复刷怪阈值 }
*/
private getMonsterThresholds(): { max: number; resume: number } {
const max = Math.max(1, Math.floor(this.maxMonsterCount));
const resume = Math.min(max - 1, Math.max(0, Math.floor(this.resumeMonsterCount)));
return { max, resume };
}
/**
* 同步怪物刷新状态(降频执行,每 0.2 秒一次):
* 1. 遍历所有 HeroAttrsComp 实体,统计怪物和英雄数量。
* 2. 检测英雄全灭。
* 3. 根据 max/resume 阈值切换 stop_spawn_mon 状态。
* 2. 检测英雄全灭 / 怪物全灭
*
* @param dt 帧间隔
*/
@@ -938,39 +907,24 @@ export class MissionComp extends CCComp {
});
this.handleHeroWipe(heroCount);
// 怪物全灭检测:如果战斗阶段场上没有任何活着的怪物,且待刷新的怪物队列也为空,直接结束战斗进入下一回合的准备阶段
// 怪物全灭检测:一场定胜负,全灭即整局通关,直接进入结算
const pendingCount = smc.vmdata.mission_data.pending_mon_num || 0;
if (monsterCount === 0 && pendingCount === 0 && smc.mission.play && !smc.mission.pause && this.currentPhase === MissionPhase.Battle) {
if (this.currentWave >= MAX_WAVE) {
// 20 回合通关
this.open_Victory(null, false);
} else {
// oops.message 全局事件总线:清屏庆祝(横幅/奖励由 BattleBannerComp 消费需在推进回合前派发clearTime 归零前取值)
const allAlive = this.checkAllHeroAlive();
const fastClear = this.clearTime < WAVE_DURATION * 0.5 ? 2
: this.clearTime < WAVE_DURATION * 0.75 ? 1 : 0;
oops.message.dispatchEvent(GameEvent.WaveClear, {
wave: this.currentWave,
clearTime: this.clearTime,
allAlive,
fastClear,
});
oops.message.dispatchEvent("TimeUpAdvanceWave");
}
// oops.message 全局事件总线:清屏庆祝(横幅/奖励由 BattleBannerComp 消费需在结算前派发clearTime 归零前取值)
const allAlive = this.checkAllHeroAlive();
const fastClear = this.clearTime < WAVE_DURATION * 0.5 ? 2
: this.clearTime < WAVE_DURATION * 0.75 ? 1 : 0;
oops.message.dispatchEvent(GameEvent.WaveClear, {
wave: this.currentWave,
clearTime: this.clearTime,
allAlive,
fastClear,
});
this.open_Victory(null, false);
return;
}
smc.vmdata.mission_data.mon_num = monsterCount;
const { max, resume } = this.getMonsterThresholds();
smc.vmdata.mission_data.mon_max = max;
const stopSpawn = !!smc.mission.stop_spawn_mon;
if (stopSpawn) {
// 降至恢复阈值以下 → 恢复刷怪
if (monsterCount <= resume) smc.mission.stop_spawn_mon = false;
return;
}
// 超过上限 → 暂停刷怪
if (monsterCount >= max) smc.mission.stop_spawn_mon = true;
}
/** 检测场上英雄是否全员存活(清屏 Perfect 判定用,独立于评分统计逻辑) */
@@ -998,13 +952,13 @@ export class MissionComp extends CCComp {
}
/**
* 回合超时强制结束(僵局兜底):
* 战斗超时强制结束(僵局兜底):
* 1. 预扣留存分(残留怪销毁后 mon_num 会被下一次同步清零BattleEnd 的累加会漏扣,故在此显式预扣并置防重标志)。
* 2. 销毁全部残留怪(不触发 MonDead、不掉金币——超时是对"清不掉"的惩罚)。
* 3. 复用正常回合推进流DynamicTuner 因 clearTime 远超阈值会自动放水,无需特殊处理
* 3. 一场定胜负,超时按怪物全灭同等处理——直接结算整局
*/
private onBattleTimeout() {
// 防重入:同一回合只触发一次
// 防重入:只触发一次
if (this.currentPhase !== MissionPhase.Battle) return;
smc.vmdata.scores.wave_remain_monsters += smc.vmdata.mission_data.mon_num;
@@ -1018,13 +972,9 @@ export class MissionComp extends CCComp {
});
smc.vmdata.mission_data.mon_num = 0;
mLogger.log(this.debugMode, 'MissionComp', `[WaveTimeout] wave=${this.currentWave} 超时强制结`);
mLogger.log(this.debugMode, 'MissionComp', `[BattleTimeout] 超时强制结`);
if (this.currentWave >= MAX_WAVE) {
this.open_Victory(null, false);
} else {
oops.message.dispatchEvent("TimeUpAdvanceWave");
}
this.open_Victory(null, false);
}
// ======================== 清理 ========================