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

@@ -192,13 +192,10 @@ export class MissionCardComp extends CCComp {
private itemCardComps: ItemListComp[] = [];
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
private hasCalledHero: boolean = false;
/**
* 招募计数:每次点击招募 +1。
* 节奏为「每 4 次一循环」:前 3 次直接随机召唤,第 4 次走三选一。
*/
private recruitCount: number = 0;
/** 全远程阵容时近战卡的权重放大倍数(近战保底) */
private static readonly MELEE_PITY_WEIGHT_MUL = 3;
/** 开局免费连续三选一的总次数 */
private static readonly OPENING_FREE_PICK_COUNT = 3;
/** 开局免费三选一剩余次数(>0 时招募不扣费,选完 1 张自动弹出下一组) */
private openingFreePicks: number = 0;
// ======================== 面板划出状态 ========================
@@ -362,6 +359,10 @@ export class MissionCardComp extends CCComp {
// 暂不启用 herobox 同步显示英雄信息(后续可能恢复),保留代码备查
// this.refreshHeroBoxSlots();
// 重置开局免费三选一次数,随后立即弹出第 1 组三选一
this.openingFreePicks = MissionCardComp.OPENING_FREE_PICK_COUNT;
this.showCardsPanel();
mLogger.log(this.debugMode, "MissionCardComp", "mission start");
}
@@ -876,8 +877,18 @@ export class MissionCardComp extends CCComp {
if (heroIdx >= 0) {
const cards = this.buildDrawCards();
this.dispatchCardsToSlots(cards);
// 抽 1 张后关闭英雄卡池面板
this.hideCardsPanel();
// 开局免费三选一次数未用完时保持面板展开,连续弹出下一组三选一
if (this.openingFreePicks > 0) {
this.openingFreePicks--;
if (this.openingFreePicks <= 0) {
// 最后一组选完,免费次数耗尽,收起面板并通知自动进入战斗
this.hideCardsPanel();
oops.message.dispatchEvent("OpeningPicksDone");
}
} else {
// 抽 1 张后关闭英雄卡池面板
this.hideCardsPanel();
}
mLogger.log(this.debugMode, "MissionCardComp", "refresh hero pool after buy", {
triggerSlot: heroIdx
});
@@ -1226,40 +1237,22 @@ export class MissionCardComp extends CCComp {
}
/**
* 英雄招募按钮(每 4 次一循环):
* - 前 3 次:扣面板开启费后直接随机召唤 1 个英雄(不开面板,无需玩家选)
* - 第 4 次:扣费后展开三选一面板(缩放 0→1玩家选 1。
* 英雄招募按钮:每次都展开三选一面板,玩家选 1
* 开局免费三选一次数未用完时不扣面板开启费
* 满员HERO_MAX_NUM时气泡提示并拦截不扣费、不计数。
*/
private onShowHerosClick() {
oops.audio.playEffect("music/button");
// 满员拦截:随机招募与三选一都禁止
// 满员拦截
if (this.getAliveHeroCount() >= this.getMissionHeroMaxNum()) {
this.showSmallTip("panel_hero_full", "英雄槽已满");
return;
}
// 随机招募与三选一都需支付面板开启费,金币不足则提示并中止
if (!this.tryPayPanelOpenCost(0)) return;
// 先计数再按节奏分支count % 4 == 0 展开三选一,其余直接随机召唤
this.recruitCount++;
const isPickOneOfThree = (this.recruitCount % 4) === 0;
mLogger.log(this.debugMode, "MissionCardComp", "hero recruit", {
recruitCount: this.recruitCount,
mode: isPickOneOfThree ? "pick_one_of_three" : "random_summon"
});
// 收起装备/技能/药品覆盖面板,回到英雄面板
// 开局免费次数内不扣面板开启费
if (this.openingFreePicks <= 0 && !this.tryPayPanelOpenCost(0)) return;
this.slideToPanel(0);
if (isPickOneOfThree) {
// 第 4 次:展开英雄抽卡面板(缩放 0→1点亮按钮 active 高亮)
this.showCardsPanel();
} else {
// 前 3 次:直接随机召唤,不开面板
this.randomSummonOne();
}
this.showCardsPanel();
}
// ======================== 英雄信息盒herosBoxNode ========================
@@ -1812,60 +1805,11 @@ export class MissionCardComp extends CCComp {
}
/**
* 直接随机召唤 1 个英雄:
* - 全英雄卡池按权重随机(不过滤已召唤英雄,配合二合一合成)。
* - 近战保底:场上已有英雄且全部为远程/中程(无近战)时,近战卡权重放大。
*/
private randomSummonOne() {
const pool = this.buildRandomSummonPool();
if (pool.length === 0) return;
const pick = this.weightedPick(pool);
if (!pick) return;
mLogger.log(this.debugMode, "MissionCardComp", "random summon", { uuid: pick.uuid });
oops.message.dispatchEvent(GameEvent.CallHero, {
uuid: pick.uuid,
hero_lv: 1,
card_lv: pick.card_lv ?? 1
});
}
/**
* 构建随机召唤卡池:全英雄卡,必要时按近战保底放大近战权重。
* @returns 带运行时权重的卡配置数组weight 已按保底调整)
* 构建随机召唤卡池:全英雄卡。
* @returns 卡配置数组
*/
private buildRandomSummonPool(): CardConfig[] {
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
if (heroCards.length === 0) return [];
// 全远程判定:有存活英雄但没有任何近战 → 触发放大近战权重
const meleeMul = this.isAllRangedFormation()
? MissionCardComp.MELEE_PITY_WEIGHT_MUL
: 1;
if (meleeMul === 1) return heroCards;
return heroCards.map(c => {
const hero = HeroInfo[c.uuid];
const isMelee = hero && hero.type === HType.Melee;
// 复制一份避免污染卡池原始 weight
return isMelee ? { ...c, weight: (c.weight ?? 0) * meleeMul } : c;
});
}
/**
* 判断当前场上是否为「全远程阵容」:
* 有至少 1 个存活英雄且没有任何近战Melee英雄。
*/
private isAllRangedFormation(): boolean {
let heroCount = 0;
let meleeCount = 0;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp);
if (!model || model.fac !== FacSet.HERO || model.is_dead) return;
heroCount++;
if (model.type === HType.Melee) meleeCount++;
});
return heroCount > 0 && meleeCount === 0;
return CardPoolList.filter(c => c.type === CardType.Hero);
}
// ======================== 阶段切换 ========================