refactor: 重构英雄养成与卡牌系统,移除旧合成机制
1. 移除三合一英雄合成与链式合成逻辑,统一使用升级卡进行英雄等级提升 2. 重构卡池系统:移除卡池等级机制,所有英雄卡牌统一为LV1 3. 重构升级逻辑:改为按UUID精确升级场上英雄,动态生成对应升级卡 4. 更新配置常量:拆分并重构成长倍率、英雄上限等战斗配置 5. 简化抽卡逻辑:不再按卡池等级分发卡牌,改为动态混合基础卡与升级卡 6. 清理废弃代码:移除卡池升级相关的UI、逻辑与配置
This commit is contained in:
@@ -764,14 +764,28 @@ export class CardComp extends CCComp {
|
||||
if (this.info_node) this.info_node.active = false;
|
||||
} else {
|
||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
||||
// 特殊卡(升级 / 刷新):显示卡名 + 品质后缀 + 描述
|
||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
||||
? SpecialUpgradeCardList[this.card_uuid]
|
||||
: SpecialRefreshCardList[this.card_uuid];
|
||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
|
||||
|
||||
// 动态升级卡:显示对应英雄名 + "升级"后缀 + 目标等级
|
||||
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) {
|
||||
const targetHero = HeroInfo[this.cardData.target_hero_uuid];
|
||||
const targetLv = Math.max(1, Math.floor(this.cardData.hero_lv ?? 1));
|
||||
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
|
||||
if (this.info_node) {
|
||||
this.info_node.active = true;
|
||||
this.setLabel(this.info_node, `升至 Lv.${targetLv}`);
|
||||
}
|
||||
} else {
|
||||
// 特殊卡(升级 / 刷新):显示卡名 + 品质后缀 + 描述
|
||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
||||
? SpecialUpgradeCardList[this.card_uuid]
|
||||
: SpecialRefreshCardList[this.card_uuid];
|
||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
|
||||
if (this.info_node) {
|
||||
this.info_node.active = true;
|
||||
this.setLabel(this.info_node, specialCard?.info || "");
|
||||
}
|
||||
}
|
||||
this.ap_node.active = false;
|
||||
this.hp_node.active = false;
|
||||
}
|
||||
|
||||
@@ -246,12 +246,18 @@ export class CardLiteComp extends CCComp {
|
||||
this.setLabel(this.name_node, `${spSuffix}${skillCard?.name || skill?.name || ""}${spSuffix}`);
|
||||
} else {
|
||||
if (this.lvl_node) this.lvl_node.node.active = false;
|
||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
||||
? SpecialUpgradeCardList[this.card_uuid]
|
||||
: SpecialRefreshCardList[this.card_uuid];
|
||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
|
||||
// 动态升级卡:显示对应英雄名 + "升级"后缀
|
||||
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) {
|
||||
const targetHero = HeroInfo[this.cardData.target_hero_uuid];
|
||||
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
|
||||
} else {
|
||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
||||
? SpecialUpgradeCardList[this.card_uuid]
|
||||
: SpecialRefreshCardList[this.card_uuid];
|
||||
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
|
||||
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
|
||||
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.cost_node) {
|
||||
|
||||
@@ -3,41 +3,43 @@
|
||||
* @description 卡牌系统核心控制器(战斗 UI 层 + 业务逻辑层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. **卡牌分发管理** —— 从卡池抽取 4 张卡,分发到 4 个 CardComp 槽位。
|
||||
* 2. **卡池升级** —— 消耗金币提升卡池等级(poolLv),解锁更高稀有度的卡牌。
|
||||
* 3. **金币费用管理** —— 抽卡费用(refreshCost)、升级费用(CardsUpSet)、
|
||||
* 波次折扣(CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE)的计算与扣除。
|
||||
* 4. **英雄数量上限校验** —— 在 UseHeroCard 事件的 guard 阶段判断是否允许
|
||||
* 再召唤英雄(含合成后腾位的特殊判断 canUseHeroCardByMerge)。
|
||||
* 5. **场上英雄信息面板(HInfoComp 列表)同步** ——
|
||||
* 1. **卡牌分发管理** —— 从卡池抽取 3 张卡,分发到 3 个 CardComp 槽位。
|
||||
* 抽卡规则:场上已有英雄时按权重混合"对应英雄的升级卡" + "其他英雄卡" + "刷新卡";
|
||||
* 场上无英雄时仅抽取英雄卡和刷新卡。
|
||||
* 2. **金币费用管理** —— 抽卡费用(refreshCost)的扣除。
|
||||
* 3. **英雄数量上限校验** —— 在 UseHeroCard 事件的 guard 阶段判断是否允许再召唤英雄。
|
||||
* 4. **场上英雄信息面板(HInfoComp 列表)同步** ——
|
||||
* 英雄上场时实例化面板,死亡时移除,定时刷新属性显示。
|
||||
* 6. **特殊卡执行** —— 处理英雄升级卡(SpecialUpgrade)和英雄刷新卡(SpecialRefresh)。
|
||||
* 7. **准备/战斗阶段切换** —— 控制卡牌面板的 展开/收起 动画。
|
||||
* 5. **特殊卡执行** —— 处理英雄升级卡(SpecialUpgrade,按 UUID 精确升级)和
|
||||
* 英雄刷新卡(SpecialRefresh)。
|
||||
* 6. **准备/战斗阶段切换** —— 控制卡牌面板的 展开/收起 动画。
|
||||
*
|
||||
* 关键设计:
|
||||
* - 4 个 CardComp 通过 cacheCardComps() 映射为有序数组 cardComps[],
|
||||
* - 3 个 CardComp 通过 cacheCardComps() 映射为有序数组 cardComps[],
|
||||
* 之后所有分发、清空操作均通过此数组进行。
|
||||
* - buildDrawCards() 保证每次抽取 4 张,不足时循环补齐。
|
||||
* - buildDrawCards() 动态合并升级卡池和基础卡池后抽取 3 张,不足时循环补齐。
|
||||
* - 英雄上限校验(onUseHeroCard)采用 guard/cancel 模式:
|
||||
* CardComp 发出 UseHeroCard 事件并传入 guard 对象,
|
||||
* 本组件可通过 guard.cancel=true 阻止使用。
|
||||
* - ensureHeroInfoPanel() 建立 EID → HInfoComp 的 Map 映射,
|
||||
* 支持英雄合成升级后面板热更新。
|
||||
*
|
||||
* 历史:
|
||||
* 旧版本曾包含"卡池等级(poolLv)"机制和"三合一合成腾位"判断,已全部移除:
|
||||
* - 卡牌不再分级,所有英雄卡统一 lv1。
|
||||
* - 英雄升级仅通过升级卡(SpecialUpgrade)触发,按 UUID 精确升级场上对应英雄。
|
||||
*
|
||||
* 依赖:
|
||||
* - CardComp —— 单卡槽位
|
||||
* - HInfoComp —— 英雄信息面板
|
||||
* - CardSet 模块 —— 卡池配置、抽卡规则、特殊卡数据
|
||||
* - HeroAttrsComp —— 英雄属性(合成校验 / 升级)
|
||||
* - MissionHeroComp —— 获取合成规则(needCount / maxLv)
|
||||
* - HeroAttrsComp —— 英雄属性(升级)
|
||||
* - smc.vmdata.mission_data —— 局内数据(coin / hero_num / hero_max_num)
|
||||
*/
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, SpriteAtlas, Tween, tween, Vec3, Widget } from "cc";
|
||||
import { _decorator, Label, Node, NodeEventType, Tween, tween, Vec3 } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { CARD_POOL_INIT_LEVEL, CARD_POOL_MAX_LEVEL, CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE, CardConfig, CardType, CardsUpSet, drawCardsByRule, getCardsByLv, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
|
||||
import { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
|
||||
import { CardComp } from "./CardComp";
|
||||
import { SCardComp } from "./SCardComp";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
@@ -45,11 +47,8 @@ import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { HeroInfo, HType } from "../common/config/heroSet";
|
||||
import { HeroViewComp } from "../hero/HeroViewComp";
|
||||
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES, SKILL_CARD_WAVES } from "../common/config/GameSet";
|
||||
import { MoveComp } from "../hero/MoveComp";
|
||||
import { MissionHeroComp } from "./MissionHeroComp";
|
||||
import { FacSet, FightSet, SKILL_CARD_WAVES } from "../common/config/GameSet";
|
||||
import { MissionEconomy } from "./MissionEconomy";
|
||||
import { MissionComp } from "./MissionComp";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
@@ -58,7 +57,7 @@ const { ccclass, property } = _decorator;
|
||||
/**
|
||||
* MissionCardComp —— 卡牌系统核心控制器
|
||||
*
|
||||
* 管理 4 个卡牌槽位的抽卡分发、卡池升级、金币费用、
|
||||
* 管理 3 个卡牌槽位的抽卡分发、金币费用、
|
||||
* 英雄上限校验、场上英雄信息面板同步以及特殊卡执行。
|
||||
*/
|
||||
@ccclass('MissionCardComp')
|
||||
@@ -99,13 +98,13 @@ export class MissionCardComp extends CCComp {
|
||||
/** 抽卡(刷新)按钮节点 */
|
||||
@property(Node)
|
||||
cards_chou: Node = null!
|
||||
/** 卡池升级按钮节点 */
|
||||
/** 卡池升级按钮节点(已废弃,保留节点引用防止 editor 报错) */
|
||||
@property(Node)
|
||||
cards_up: Node = null!
|
||||
/** 金币显示节点(含 icon + num 子节点) */
|
||||
@property(Node)
|
||||
coins_node: Node = null!
|
||||
/** 卡池等级显示节点 */
|
||||
/** 卡池等级显示节点(已废弃,保留节点引用) */
|
||||
@property(Node)
|
||||
pool_lv_node: Node = null!
|
||||
/** 英雄数量显示节点(含 icon + num 子节点) */
|
||||
@@ -135,12 +134,10 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
// ======================== 运行时状态 ========================
|
||||
|
||||
/** 四个槽位对应的 CardComp 控制器缓存(有序数组) */
|
||||
/** 三个槽位对应的 CardComp 控制器缓存(有序数组) */
|
||||
private cardComps: CardComp[] = [];
|
||||
/** 技能卡槽控制器缓存 */
|
||||
private skillCardComps: SCardComp[] = [];
|
||||
/** 当前卡池等级(仅影响抽卡来源,不直接改卡槽现有内容) */
|
||||
private poolLv: number = CARD_POOL_INIT_LEVEL;
|
||||
/** 是否已缓存卡牌面板基准缩放 */
|
||||
private hasCachedCardsBaseScale: boolean = false;
|
||||
/** 卡牌面板基准缩放(从场景读取) */
|
||||
@@ -157,20 +154,17 @@ export class MissionCardComp extends CCComp {
|
||||
/**
|
||||
* 组件加载:
|
||||
* 1. 绑定生命周期事件和按钮交互事件。
|
||||
* 2. 缓存 4 个 CardComp 子控制器引用。
|
||||
* 2. 缓存 3 个 CardComp 子控制器引用。
|
||||
* 3. 计算并设置槽位水平布局。
|
||||
* 4. 初始化卡牌面板缩放参数。
|
||||
* 5. 触发首次任务开始流程。
|
||||
*/
|
||||
onLoad() {
|
||||
this.bindEvents();
|
||||
this.cacheCardComps();
|
||||
this.layoutCardSlots();
|
||||
this.initCardsPanelPos();
|
||||
// this.onMissionStart(); // 移除 onLoad 自动触发,改为事件驱动
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "onLoad init", {
|
||||
slots: this.cardComps.length,
|
||||
poolLv: this.poolLv
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,15 +187,13 @@ export class MissionCardComp extends CCComp {
|
||||
/**
|
||||
* 任务开始:
|
||||
* 1. 进入准备阶段(展开卡牌面板)。
|
||||
* 2. 重置卡池等级为初始值。
|
||||
* 3. 初始化局内数据(金币、英雄数量上限)。
|
||||
* 4. 清空旧英雄信息面板和卡牌槽位。
|
||||
* 5. 重置按钮状态和 UI 显示。
|
||||
* 6. 执行首次抽卡并分发到 4 个槽位。
|
||||
* 2. 初始化局内数据(金币、英雄数量上限)。
|
||||
* 3. 清空旧英雄信息面板和卡牌槽位。
|
||||
* 4. 重置按钮状态和 UI 显示。
|
||||
* 5. 执行首次抽卡并分发到 3 个槽位。
|
||||
*/
|
||||
onMissionStart() {
|
||||
this.enterPreparePhase();
|
||||
this.poolLv = CARD_POOL_INIT_LEVEL;
|
||||
const missionData = this.getMissionData();
|
||||
if (missionData) {
|
||||
missionData.coin = Math.max(0, Math.floor(missionData.coin ?? 0));
|
||||
@@ -217,11 +209,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
this.layoutCardSlots();
|
||||
this.clearAllCards();
|
||||
// if (this.cards_up) {
|
||||
// this.cards_up.active = true;
|
||||
// }
|
||||
this.resetButtonScale(this.cards_chou);
|
||||
// this.resetButtonScale(this.cards_up);
|
||||
this.updateCoinAndCostUI();
|
||||
this.updateHeroNumUI(false, false);
|
||||
if (this.node && this.node.isValid) {
|
||||
@@ -235,12 +223,10 @@ export class MissionCardComp extends CCComp {
|
||||
this.showSkillCardPopup();
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "mission start", {
|
||||
poolLv: this.poolLv
|
||||
});
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "mission start");
|
||||
}
|
||||
|
||||
/** 任务结束:清空 4 槽 + 英雄面板并隐藏整个节点 */
|
||||
/** 任务结束:清空 3 槽 + 英雄面板并隐藏整个节点 */
|
||||
onMissionEnd() {
|
||||
this.clearAllCards();
|
||||
if (this.node && this.node.isValid) {
|
||||
@@ -271,7 +257,7 @@ export class MissionCardComp extends CCComp {
|
||||
* 绑定所有事件监听:
|
||||
* - 节点级事件:MissionStart / MissionEnd / NewWave / FightStart
|
||||
* - 全局消息:CoinAdd / MasterCalled / HeroDead / UseHeroCard / UseSpecialCard
|
||||
* - 按钮触控:抽卡(cards_chou)、升级(cards_up)
|
||||
* - 按钮触控:抽卡(cards_chou)
|
||||
*/
|
||||
private bindEvents() {
|
||||
/** 生命周期事件(节点级) */
|
||||
@@ -288,10 +274,9 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.on(GameEvent.UseHeroCard, this.onUseHeroCard, this);
|
||||
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
|
||||
oops.message.on(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
|
||||
oops.message.on(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
|
||||
oops.message.on(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
|
||||
|
||||
/** 按钮触控事件:抽卡与卡池升级 */
|
||||
/** 按钮触控事件:抽卡 */
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
|
||||
this.cards_chou?.on(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
|
||||
@@ -303,9 +288,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.skill_ad_refresh?.on(NodeEventType.TOUCH_START, this.onSkillAdDrawTouchStart, this);
|
||||
this.skill_ad_refresh?.on(NodeEventType.TOUCH_END, this.onSkillAdDrawTouchEnd, this);
|
||||
this.skill_ad_refresh?.on(NodeEventType.TOUCH_CANCEL, this.onSkillAdDrawTouchCancel, this);
|
||||
// this.cards_up?.on(NodeEventType.TOUCH_START, this.onUpgradeTouchStart, this);
|
||||
// this.cards_up?.on(NodeEventType.TOUCH_END, this.onUpgradeTouchEnd, this);
|
||||
// this.cards_up?.on(NodeEventType.TOUCH_CANCEL, this.onUpgradeTouchCancel, this);
|
||||
}
|
||||
// ======================== 事件回调 ========================
|
||||
|
||||
@@ -333,47 +315,17 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收卡池升级事件:
|
||||
* - 更新卡池等级
|
||||
* - 更新UI显示
|
||||
*/
|
||||
private onCardPoolUpgrade(event: string, args: any) {
|
||||
const targetLv = args?.targetLv;
|
||||
if (!targetLv) return;
|
||||
|
||||
if (targetLv > CARD_POOL_MAX_LEVEL) {
|
||||
this.poolLv = CARD_POOL_MAX_LEVEL;
|
||||
} else {
|
||||
this.poolLv = targetLv;
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "onCardPoolUpgrade", {
|
||||
targetLv,
|
||||
poolLv: this.poolLv
|
||||
});
|
||||
|
||||
// 提示卡池升级
|
||||
this.showSmallTip("pool_upgrade");
|
||||
|
||||
// 更新UI
|
||||
this.updatePoolLvUI();
|
||||
}
|
||||
|
||||
private onShowSmallTip(event: string, args: any) {
|
||||
const type = args as string;
|
||||
this.showSmallTip(type as any);
|
||||
}
|
||||
|
||||
public showSmallTip(type: "refresh_coin" | "pool_upgrade" | "buy_coin" | "hero_full") {
|
||||
public showSmallTip(type: "refresh_coin" | "buy_coin" | "hero_full") {
|
||||
let targetNode: Node | null = null;
|
||||
switch (type) {
|
||||
case "refresh_coin":
|
||||
targetNode = this.cards_chou;
|
||||
break;
|
||||
case "pool_upgrade":
|
||||
targetNode = this.pool_lv_node;
|
||||
break;
|
||||
case "buy_coin":
|
||||
targetNode = this.coins_node;
|
||||
break;
|
||||
@@ -481,14 +433,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.skill_card_node.active = false;
|
||||
}
|
||||
|
||||
// 首次完成技能选取后 关闭guide2,打开guide3
|
||||
// 之前这里有个逻辑漏洞:玩家如果在弹出 guide2 之前就已经因为手快点掉了 guide2,
|
||||
// smc.finish_guides 里就会有 2。
|
||||
// 但其实这里的本意是:只要发生了选取技能,并且 guide3 还没弹过,就弹 guide3。
|
||||
// 如果我们把它包在 `if (!smc.finish_guides.includes(2))` 里,
|
||||
// 当玩家点击 guide2 把它关掉时(finish_guides 存入了 2),
|
||||
// 再点技能卡触发这个方法,外层 if 就会进不去,guide3 就永远弹不出来了!
|
||||
|
||||
// 修复:独立判断 guide2 的关闭 和 guide3 的开启
|
||||
if (!smc.finish_guides.includes(2)) {
|
||||
smc.finish_guides.push(2);
|
||||
@@ -500,7 +444,6 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
// 驻场技能可能影响刷新费用(如"刷新优惠"),延迟到下一帧刷新费用 UI
|
||||
// 确保 MissSkillsComp 已创建 SkillBoxComp 并注册驻场效果
|
||||
this.scheduleOnce(() => this.updateCoinAndCostUI(), 0);
|
||||
}
|
||||
|
||||
@@ -513,7 +456,6 @@ export class MissionCardComp extends CCComp {
|
||||
oops.message.off(GameEvent.UseHeroCard, this.onUseHeroCard, this);
|
||||
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
|
||||
oops.message.off(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
|
||||
oops.message.off(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
|
||||
oops.message.off(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
|
||||
if (this.cards_chou && this.cards_chou.isValid) {
|
||||
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
|
||||
@@ -530,9 +472,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.skill_ad_refresh.off(NodeEventType.TOUCH_END, this.onSkillAdDrawTouchEnd, this);
|
||||
this.skill_ad_refresh.off(NodeEventType.TOUCH_CANCEL, this.onSkillAdDrawTouchCancel, this);
|
||||
}
|
||||
// this.cards_up?.off(NodeEventType.TOUCH_START, this.onUpgradeTouchStart, this);
|
||||
// this.cards_up?.off(NodeEventType.TOUCH_END, this.onUpgradeTouchEnd, this);
|
||||
// this.cards_up?.off(NodeEventType.TOUCH_CANCEL, this.onUpgradeTouchCancel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -575,28 +514,18 @@ export class MissionCardComp extends CCComp {
|
||||
/**
|
||||
* 使用英雄卡的 guard 校验(由 CardComp 通过 UseHeroCard 事件调用):
|
||||
* - 当前英雄数 < 上限 → 允许使用。
|
||||
* - 已满但新卡可触发合成(腾位) → 允许使用。
|
||||
* - 已满且不可合成 → 阻止使用(cancel=true),弹 toast。
|
||||
* - 已满 → 阻止使用(cancel=true),弹 toast。
|
||||
*
|
||||
* 注意:英雄不再支持合成腾位,满员时一律阻止。
|
||||
*/
|
||||
private onUseHeroCard(event: string, args: any) {
|
||||
const payload = args ?? event;
|
||||
if (!payload) return;
|
||||
|
||||
// 战斗阶段也允许召唤英雄(无需额外费用),仅校验英雄数量上限
|
||||
|
||||
const current = this.getAliveHeroCount();
|
||||
this.syncMissionHeroData(current);
|
||||
const heroMax = this.getMissionHeroMaxNum();
|
||||
if (current >= heroMax) {
|
||||
const heroUuid = Number(payload?.uuid ?? 0);
|
||||
const heroLv = Math.max(1, Math.floor(Number(payload?.hero_lv ?? 1)));
|
||||
const cardLv = Math.max(1, Math.floor(Number(payload?.pool_lv ?? 1)));
|
||||
// 检查是否可以通过合成腾出位置
|
||||
if (this.canUseHeroCardByMerge(heroUuid, heroLv)) {
|
||||
payload.cancel = false;
|
||||
payload.reason = "";
|
||||
return;
|
||||
}
|
||||
payload.cancel = true;
|
||||
payload.reason = "hero_limit";
|
||||
this.showSmallTip("hero_full");
|
||||
@@ -604,53 +533,11 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断新召唤的英雄是否能通过合成腾位:
|
||||
* 场上同 UUID 同等级数量 + 1(新卡自身)>= 合成所需数量 → 可以合成。
|
||||
*/
|
||||
private canUseHeroCardByMerge(heroUuid: number, heroLv: number): boolean {
|
||||
if (!heroUuid) return false;
|
||||
const mergeRule = this.getMergeRule();
|
||||
if (heroLv >= mergeRule.maxLv) return false;
|
||||
const sameCount = this.countAliveHeroesByUuidAndLv(heroUuid, heroLv);
|
||||
return sameCount + 1 >= mergeRule.needCount;
|
||||
}
|
||||
|
||||
/** 统计场上同 UUID 同等级的存活英雄数量 */
|
||||
private countAliveHeroesByUuidAndLv(heroUuid: number, heroLv: number): number {
|
||||
let count = 0;
|
||||
const actors = this.queryAliveHeroActors();
|
||||
for (let i = 0; i < actors.length; i++) {
|
||||
const model = actors[i].model;
|
||||
if (!model) continue;
|
||||
if (model.hero_uuid !== heroUuid) continue;
|
||||
if (model.lv !== heroLv) continue;
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 MissionHeroComp 实时读取合成规则。
|
||||
* 通过 ECS 查询获取,避免硬编码与 MissionHeroComp 不一致。
|
||||
* @returns { needCount: 合成所需数量, maxLv: 最大合成等级 }
|
||||
*/
|
||||
private getMergeRule(): { needCount: number, maxLv: number } {
|
||||
let needCount = FightSet.MERGE_NEED ? FightSet.MERGE_NEED : 2
|
||||
let maxLv = Math.max(1, Math.floor(FightSet.MERGE_MAX ?? 3));
|
||||
ecs.query(ecs.allOf(MissionHeroComp)).forEach((entity: ecs.Entity) => {
|
||||
const comp = entity.get(MissionHeroComp);
|
||||
if (!comp) return;
|
||||
needCount = comp.merge_need_count === 2 ? 2 : 3;
|
||||
maxLv = Math.max(1, Math.floor(comp.merge_max_lv ?? 3));
|
||||
});
|
||||
return { needCount, maxLv };
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用特殊卡事件回调:
|
||||
* - SpecialUpgrade:随机选一个指定等级的英雄升级到目标等级。
|
||||
* - SpecialRefresh:按英雄类型 / 指定等级重新抽取英雄卡。
|
||||
* - SpecialUpgrade:按卡牌携带的 target_hero_uuid 精确升级场上对应英雄。
|
||||
* target_hero_uuid 缺失时回退为随机升级一个可升级的英雄。
|
||||
* - SpecialRefresh:按英雄类型重新抽取英雄卡。
|
||||
*/
|
||||
private onUseSpecialCard(event: string, args: any) {
|
||||
const payload = args ?? event;
|
||||
@@ -659,14 +546,17 @@ export class MissionCardComp extends CCComp {
|
||||
if (!uuid) return;
|
||||
let success = false;
|
||||
if (type === CardType.SpecialUpgrade) {
|
||||
const card = SpecialUpgradeCardList[uuid];
|
||||
if (!card) return;
|
||||
success = this.tryUpgradeOneHero(card.currentLv, card.targetLv);
|
||||
if (!success) oops.gui.toast(`场上没有可从${card.currentLv}级升到${card.targetLv}级的英雄`);
|
||||
const template = SpecialUpgradeCardList[uuid];
|
||||
if (!template) return;
|
||||
const targetHeroUuid = Number(payload?.target_hero_uuid ?? 0);
|
||||
success = this.tryUpgradeHeroByUuid(targetHeroUuid);
|
||||
if (!success) {
|
||||
oops.gui.toast(`场上没有可升级的英雄`);
|
||||
}
|
||||
} else if (type === CardType.SpecialRefresh) {
|
||||
const card = SpecialRefreshCardList[uuid];
|
||||
if (!card) return;
|
||||
success = this.tryRefreshHeroCardsByEffect(card.refreshHeroType, card.refreshLv);
|
||||
success = this.tryRefreshHeroCardsByEffect(card.refreshHeroType);
|
||||
if (!success) oops.gui.toast("当前卡池无符合条件的英雄卡");
|
||||
}
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "use special card", {
|
||||
@@ -731,20 +621,8 @@ export class MissionCardComp extends CCComp {
|
||||
const cards = this.buildSkillDrawCards();
|
||||
this.dispatchCardsToSkillSlots(cards);
|
||||
}
|
||||
// /** 升级按钮按下反馈 */
|
||||
// private onUpgradeTouchStart() {
|
||||
// this.playButtonPressAnim(this.cards_up);
|
||||
// }
|
||||
// /** 升级按钮释放 → 执行升级逻辑 */
|
||||
// private onUpgradeTouchEnd() {
|
||||
// this.playButtonClickAnim(this.cards_up, () => this.onClickUpgrade());
|
||||
// }
|
||||
// /** 升级按钮取消 → 恢复缩放 */
|
||||
// private onUpgradeTouchCancel() {
|
||||
// this.playButtonResetAnim(this.cards_up);
|
||||
// }
|
||||
|
||||
/** 将四个卡槽节点映射为 CardComp,形成固定顺序控制数组 */
|
||||
/** 将三个卡槽节点映射为 CardComp,形成固定顺序控制数组 */
|
||||
private cacheCardComps() {
|
||||
if (this.card4) {
|
||||
this.card4.active = false;
|
||||
@@ -760,16 +638,15 @@ export class MissionCardComp extends CCComp {
|
||||
.filter((comp): comp is SCardComp => !!comp);
|
||||
}
|
||||
|
||||
// ======================== 核心业务:抽卡 & 升级 ========================
|
||||
// ======================== 核心业务:抽卡 ========================
|
||||
|
||||
/**
|
||||
* 抽卡按钮核心逻辑:
|
||||
* 1. 检查金币是否足够 → 不够则 toast 提示。
|
||||
* 2. 扣除费用、播放金币动画。
|
||||
* 3. 重新布局槽位 → 从卡池构建 4 张卡 → 分发到槽位。
|
||||
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位。
|
||||
*/
|
||||
private onClickDraw() {
|
||||
// 战斗阶段和倒计时阶段均允许刷新抽卡
|
||||
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
const success = MissionEconomy.executeRefresh(this.refreshCost);
|
||||
if (!success) {
|
||||
@@ -783,7 +660,6 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "click draw", {
|
||||
poolLv: this.poolLv,
|
||||
cost,
|
||||
leftCoin: MissionEconomy.getCoin()
|
||||
});
|
||||
@@ -792,35 +668,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.dispatchCardsToSlots(cards);
|
||||
}
|
||||
|
||||
// /** 升级按钮:仅提升卡池等级,卡槽是否更新由下一次抽卡触发 */
|
||||
// private onClickUpgrade() {
|
||||
// if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
|
||||
// mLogger.log(this.debugMode, "MissionCardComp", "pool already max", this.poolLv);
|
||||
// return;
|
||||
// }
|
||||
// const cost = this.getUpgradeCost(this.poolLv);
|
||||
// const currentCoin = this.getMissionCoin();
|
||||
// if (currentCoin < cost) {
|
||||
// oops.gui.toast(`金币不足,升级需要${cost}`);
|
||||
// this.updateCoinAndCostUI();
|
||||
// mLogger.log(this.debugMode, "MissionCardComp", "pool upgrade coin not enough", {
|
||||
// poolLv: this.poolLv,
|
||||
// currentCoin,
|
||||
// cost
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// this.setMissionCoin(currentCoin - cost);
|
||||
// this.poolLv += 1;
|
||||
// this.playCoinChangeAnim(false);
|
||||
// this.updateCoinAndCostUI();
|
||||
// mLogger.log(this.debugMode, "MissionCardComp", "pool level up", {
|
||||
// poolLv: this.poolLv,
|
||||
// cost,
|
||||
// leftCoin: this.getMissionCoin()
|
||||
// });
|
||||
// }
|
||||
|
||||
// ======================== 阶段切换 ========================
|
||||
|
||||
/** 缓存卡牌面板的基准缩放值(从场景初始状态读取,仅缓存一次) */
|
||||
@@ -853,7 +700,7 @@ export class MissionCardComp extends CCComp {
|
||||
private enterBattlePhase() {
|
||||
if (!this.cards_node || !this.cards_node.isValid) return;
|
||||
this.initCardsPanelPos();
|
||||
// 战斗阶段允许抽卡:nobg 按"金币是否足够"判断,而非强制置灰
|
||||
// 战斗阶段允许抽卡:nobg 按"金币是否足够"判断
|
||||
if (this.cards_chou && this.cards_chou.isValid) {
|
||||
const nobg = this.cards_chou.getChildByName("nobg");
|
||||
if (nobg) {
|
||||
@@ -862,30 +709,140 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/** 构建本次抽卡结果,保证最终可分发3条数据 */
|
||||
/**
|
||||
* 构建本次抽卡结果,保证最终可分发 3 条数据。
|
||||
*
|
||||
* 抽卡池构成:
|
||||
* - 动态升级卡:扫描场上存活英雄,每个 UUID 至多生成一张升级卡
|
||||
* (已达到 HERO_MAX_LV 的英雄不出卡)。同 UUID 只出一张,避免重复。
|
||||
* - 基础英雄卡:所有英雄的 lv1 卡。
|
||||
* - 刷新功能卡:SpecialRefresh 卡。
|
||||
*
|
||||
* 三类卡按 weight 权重混合抽 3 张,且升级卡之间 unique 去重。
|
||||
*
|
||||
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,
|
||||
* 不再抽取基础英雄卡和刷新卡,只抽取场上已有英雄的升级卡
|
||||
* (若全部已满级则降级回混合池,避免卡池为空)。
|
||||
*/
|
||||
private buildDrawCards(): CardConfig[] {
|
||||
const targetType = [CardType.Hero, CardType.SpecialRefresh];
|
||||
const cards = getCardsByLv(this.poolLv, targetType);
|
||||
const upgradeCards = this.buildHeroUpgradeCards();
|
||||
const aliveHeroCount = this.getAliveHeroCount();
|
||||
const heroMax = this.getMissionHeroMaxNum();
|
||||
|
||||
/** 正常情况下直接取前3 */
|
||||
if (cards.length >= 3) return cards.slice(0, 3);
|
||||
/** 兜底:当返回不足3张时循环补齐,保证分发不缺位 */
|
||||
const filled = [...cards];
|
||||
// 英雄已满员且有可升级的英雄 → 只出升级卡
|
||||
if (aliveHeroCount >= heroMax && upgradeCards.length > 0) {
|
||||
const picked = this.pickMixedCards(upgradeCards, 3, upgradeCards);
|
||||
if (picked.length >= 3) return picked.slice(0, 3);
|
||||
/** 兜底:不足 3 张时循环补齐 */
|
||||
const filled = [...picked];
|
||||
while (filled.length < 3) {
|
||||
filled.push(upgradeCards[filled.length % upgradeCards.length]);
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
|
||||
const refreshCards: CardConfig[] = Object.values(SpecialRefreshCardList);
|
||||
|
||||
const mixedPool: CardConfig[] = [...upgradeCards, ...heroCards, ...refreshCards];
|
||||
if (mixedPool.length === 0) return [];
|
||||
|
||||
const picked = this.pickMixedCards(mixedPool, 3, upgradeCards);
|
||||
if (picked.length >= 3) return picked.slice(0, 3);
|
||||
|
||||
/** 兜底:不足 3 张时循环补齐 */
|
||||
const filled = [...picked];
|
||||
while (filled.length < 3) {
|
||||
const fallback = getCardsByLv(this.poolLv, targetType);
|
||||
if (fallback.length === 0) break;
|
||||
filled.push(fallback[filled.length % fallback.length]);
|
||||
if (mixedPool.length === 0) break;
|
||||
filled.push(mixedPool[filled.length % mixedPool.length]);
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从混合池中按权重抽取 n 张卡。
|
||||
* 升级卡之间强制 unique(同一个 target_hero_uuid 只能出现一次)。
|
||||
*/
|
||||
private pickMixedCards(pool: CardConfig[], count: number, upgradeCards: CardConfig[]): CardConfig[] {
|
||||
if (pool.length === 0 || count <= 0) return [];
|
||||
const selected: CardConfig[] = [];
|
||||
const usedUpgradeTargets = new Set<number>();
|
||||
|
||||
while (selected.length < count) {
|
||||
const available = pool.filter(c => {
|
||||
if (c.type === CardType.SpecialUpgrade && c.target_hero_uuid) {
|
||||
return !usedUpgradeTargets.has(c.target_hero_uuid);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (available.length === 0) break;
|
||||
const pick = this.weightedPick(available);
|
||||
if (!pick) break;
|
||||
selected.push(pick);
|
||||
if (pick.type === CardType.SpecialUpgrade && pick.target_hero_uuid) {
|
||||
usedUpgradeTargets.add(pick.target_hero_uuid);
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
/** 单次按权重抽取一张卡 */
|
||||
private weightedPick(cards: CardConfig[]): CardConfig | null {
|
||||
if (cards.length === 0) return null;
|
||||
const totalWeight = cards.reduce((total, card) => total + (card.weight ?? 0), 0);
|
||||
let random = Math.random() * totalWeight;
|
||||
for (const card of cards) {
|
||||
random -= (card.weight ?? 0);
|
||||
if (random <= 0) return card;
|
||||
}
|
||||
return cards[cards.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描场上存活英雄,为每个 UUID 至多生成一张升级卡。
|
||||
* - 已达 HERO_MAX_LV 的英雄不出卡。
|
||||
* - 同 UUID 多个英雄只生成一张(取等级最低的那个作为升级目标)。
|
||||
* - cost = 模板 cost + (当前等级 - 1) * BASE_COST,等级越高升级越贵。
|
||||
*/
|
||||
private buildHeroUpgradeCards(): CardConfig[] {
|
||||
const template = SpecialUpgradeCardList[7001];
|
||||
if (!template) return [];
|
||||
|
||||
const actors = this.queryAliveHeroActors();
|
||||
if (actors.length === 0) return [];
|
||||
|
||||
// 同 UUID 取等级最低的英雄
|
||||
const heroMap = new Map<number, { uuid: number, lv: number }>();
|
||||
for (const actor of actors) {
|
||||
const uuid = actor.model.hero_uuid;
|
||||
const lv = actor.model.lv;
|
||||
const existing = heroMap.get(uuid);
|
||||
if (!existing || lv < existing.lv) {
|
||||
heroMap.set(uuid, { uuid, lv });
|
||||
}
|
||||
}
|
||||
|
||||
const result: CardConfig[] = [];
|
||||
heroMap.forEach(({ uuid, lv }) => {
|
||||
if (lv >= FightSet.HERO_MAX_LV) return; // 已达上限不再出升级卡
|
||||
const dynamicCost = template.cost + (lv - 1) * FightSet.BASE_COST;
|
||||
result.push({
|
||||
...template,
|
||||
cost: dynamicCost,
|
||||
weight: template.weight,
|
||||
target_hero_uuid: uuid,
|
||||
hero_lv: lv + 1,
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private buildSkillDrawCards(): CardConfig[] {
|
||||
const targetType = CardType.Skill;
|
||||
const currentWave = this.getCurrentWave();
|
||||
// 使用明确规则的 drawCardsByRule,指定只要 3 张技能卡,并且过滤对应 wave
|
||||
const cards = drawCardsByRule(this.poolLv, {
|
||||
const cards = drawCardsByRule(1, {
|
||||
count: 3,
|
||||
type: targetType,
|
||||
type: CardType.Skill,
|
||||
wave: currentWave,
|
||||
unique: true // 保证技能牌不重复
|
||||
});
|
||||
@@ -893,9 +850,9 @@ export class MissionCardComp extends CCComp {
|
||||
if (cards.length >= 3) return cards.slice(0, 3);
|
||||
const filled = [...cards];
|
||||
while (filled.length < 3) {
|
||||
const fallback = drawCardsByRule(this.poolLv, {
|
||||
const fallback = drawCardsByRule(1, {
|
||||
count: 3,
|
||||
type: targetType,
|
||||
type: CardType.Skill,
|
||||
wave: currentWave,
|
||||
unique: true
|
||||
});
|
||||
@@ -912,12 +869,11 @@ export class MissionCardComp extends CCComp {
|
||||
return filled;
|
||||
}
|
||||
|
||||
private tryRefreshHeroCards(heroType?: HType, targetPoolLv?: number): boolean {
|
||||
const cards = drawCardsByRule(this.poolLv, {
|
||||
private tryRefreshHeroCards(heroType?: HType): boolean {
|
||||
const cards = drawCardsByRule(1, {
|
||||
count: 3,
|
||||
type: CardType.Hero,
|
||||
heroType,
|
||||
targetPoolLv
|
||||
});
|
||||
if (cards.length <= 0) return false;
|
||||
this.layoutCardSlots();
|
||||
@@ -925,10 +881,9 @@ export class MissionCardComp extends CCComp {
|
||||
return true;
|
||||
}
|
||||
|
||||
private tryRefreshHeroCardsByEffect(refreshHeroType: SpecialRefreshHeroType, refreshLv: number): boolean {
|
||||
private tryRefreshHeroCardsByEffect(refreshHeroType: SpecialRefreshHeroType): boolean {
|
||||
const heroType = this.resolveRefreshHeroType(refreshHeroType);
|
||||
const targetPoolLv = refreshLv > 0 ? refreshLv : undefined;
|
||||
return this.tryRefreshHeroCards(heroType, targetPoolLv);
|
||||
return this.tryRefreshHeroCards(heroType);
|
||||
}
|
||||
|
||||
private resolveRefreshHeroType(refreshHeroType: SpecialRefreshHeroType): HType | undefined {
|
||||
@@ -937,7 +892,7 @@ export class MissionCardComp extends CCComp {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** 全量分发给4槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */
|
||||
/** 全量分发给 3 槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */
|
||||
private dispatchCardsToSlots(cards: CardConfig[]) {
|
||||
if (!this.cardComps) return;
|
||||
for (let i = 0; i < this.cardComps.length; i++) {
|
||||
@@ -952,7 +907,7 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/** 系统清空4槽(用于任务切换) */
|
||||
/** 系统清空 3 槽(用于任务切换) */
|
||||
private clearAllCards() {
|
||||
if (!this.cardComps) return;
|
||||
this.cardComps.forEach(comp => {
|
||||
@@ -1001,77 +956,10 @@ export class MissionCardComp extends CCComp {
|
||||
Tween.stopAllByTarget(node);
|
||||
node.setScale(this.buttonNormalScale, this.buttonNormalScale, 1);
|
||||
}
|
||||
private canUpPool() {
|
||||
if (this.poolLv >= CARD_POOL_MAX_LEVEL) return false;
|
||||
const currentCoin = MissionEconomy.getCoin();
|
||||
return currentCoin >= this.getUpgradeCost(this.poolLv);
|
||||
}
|
||||
|
||||
private canDrawCards() {
|
||||
return MissionEconomy.getCoin() >= MissionEconomy.getRefreshCost(this.refreshCost);
|
||||
}
|
||||
/** 更新升级按钮上的等级文案,反馈当前卡池层级 */
|
||||
private updatePoolLvUI() {
|
||||
if (this.pool_lv_node) {
|
||||
this.pool_lv_node.active = true;
|
||||
const lv = Math.max(CARD_POOL_INIT_LEVEL, Math.min(CARD_POOL_MAX_LEVEL, Math.floor(this.poolLv)));
|
||||
const lvNode = this.pool_lv_node.getChildByName("lv");
|
||||
if (lvNode) {
|
||||
const label = lvNode.getComponent(Label);
|
||||
if (label) {
|
||||
label.string = `lv.${lv}`;
|
||||
}
|
||||
}
|
||||
|
||||
const nextNode = this.pool_lv_node.getChildByName("next");
|
||||
if (nextNode) {
|
||||
const nextLabel = nextNode.getComponent(Label);
|
||||
if (nextLabel) {
|
||||
if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
|
||||
nextLabel.string = `已满级`;
|
||||
} else {
|
||||
// 优先取 MissionComp 运行时配置,缺失时回退到全局常量
|
||||
let upgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
|
||||
ecs.query(ecs.allOf(MissionComp)).forEach((entity) => {
|
||||
const mission = entity.get(MissionComp);
|
||||
if (mission && mission.cardPoolUpgradeWaves && mission.cardPoolUpgradeWaves.length > 0) {
|
||||
upgradeWaves = mission.cardPoolUpgradeWaves;
|
||||
}
|
||||
});
|
||||
|
||||
// 已完成的升级次数 = 当前等级 - 初始等级
|
||||
// 例:poolLv=2(INIT=1)→ 已升 1 次 → 下一升级对应 upgradeWaves[1]
|
||||
const upgradedCount = Math.max(0, Math.floor(this.poolLv) - CARD_POOL_INIT_LEVEL);
|
||||
const currentWave = this.getCurrentWave();
|
||||
|
||||
if (upgradedCount >= upgradeWaves.length) {
|
||||
// 配置已耗尽但等级未到上限(配置缺陷)
|
||||
nextLabel.string = `已满级`;
|
||||
} else {
|
||||
const nextWave = upgradeWaves[upgradedCount];
|
||||
if (nextWave > currentWave) {
|
||||
const remain = nextWave - currentWave;
|
||||
nextLabel.string = `${remain} 回合后升级`;
|
||||
} else if (nextWave === currentWave) {
|
||||
// 当前波次正好是升级波次(事件可能即将触发或刚刚触发)
|
||||
nextLabel.string = `本回合升级`;
|
||||
} else {
|
||||
// nextWave < currentWave:异常状态,升级事件未按时触发
|
||||
nextLabel.string = `即将升级`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const peak = 1.2
|
||||
this.playHeroNumNodePop(this.pool_lv_node, peak);
|
||||
}
|
||||
mLogger.log(this.debugMode, "MissionCardComp", "pool lv ui update", {
|
||||
poolLv: this.poolLv,
|
||||
cost: this.getUpgradeCost(this.poolLv)
|
||||
});
|
||||
}
|
||||
|
||||
private updateDrawCostUI() {
|
||||
// 战斗阶段也允许抽卡,nobg 统一按"金币是否足够"判断
|
||||
@@ -1101,7 +989,6 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
private updateCoinAndCostUI() {
|
||||
this.updatePoolLvUI();
|
||||
this.updateDrawCostUI();
|
||||
}
|
||||
|
||||
@@ -1116,13 +1003,6 @@ export class MissionCardComp extends CCComp {
|
||||
this.playHeroNumNodePop(num, peak);
|
||||
}
|
||||
|
||||
private getUpgradeCost(lv: number): number {
|
||||
const baseCost = Math.max(0, Math.floor(CardsUpSet[lv] ?? 0));
|
||||
const completedWave = Math.max(0, this.getCurrentWave() - 1);
|
||||
const discount = Math.max(0, Math.floor(CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE)) * completedWave;
|
||||
return Math.max(0, baseCost - discount);
|
||||
}
|
||||
|
||||
public setHeroMaxCount(max: number) {
|
||||
const missionData = this.getMissionData();
|
||||
if (!missionData) return;
|
||||
@@ -1171,28 +1051,42 @@ export class MissionCardComp extends CCComp {
|
||||
return actors;
|
||||
}
|
||||
|
||||
private tryUpgradeOneHero(currentLv: number, targetLv: number): boolean {
|
||||
const fromLv = Math.max(1, Math.floor(currentLv));
|
||||
const toLv = Math.max(1, Math.floor(targetLv));
|
||||
if (toLv <= fromLv) return false;
|
||||
const candidates = this.queryAliveHeroActors().filter(item => item.model.lv === fromLv);
|
||||
/**
|
||||
* 按英雄 UUID 精确升级一个存活英雄。
|
||||
* 若同 UUID 多个英雄,取等级最低且未达上限的;若都达上限则失败。
|
||||
*
|
||||
* @param heroUuid 要升级的英雄 UUID
|
||||
* @returns true = 升级成功
|
||||
*/
|
||||
private tryUpgradeHeroByUuid(heroUuid: number): boolean {
|
||||
if (!heroUuid) return false;
|
||||
const candidates = this.queryAliveHeroActors().filter(item =>
|
||||
item.model.hero_uuid === heroUuid && item.model.lv < FightSet.HERO_MAX_LV
|
||||
);
|
||||
if (candidates.length === 0) return false;
|
||||
const target = candidates[Math.floor(Math.random() * candidates.length)];
|
||||
this.applyHeroLevel(target.model, toLv);
|
||||
// 取等级最低的(先升级低等级英雄)
|
||||
candidates.sort((a, b) => a.model.lv - b.model.lv);
|
||||
const target = candidates[0];
|
||||
const nextLv = Math.min(FightSet.HERO_MAX_LV, target.model.lv + 1);
|
||||
this.applyHeroLevel(target.model, nextLv);
|
||||
if (target.view) {
|
||||
target.view.playBuff("buff_lvup");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用英雄等级变化(属性按 HERO_LV_MULTIPLIER 重新计算)。
|
||||
* 沿用旧版本 SkillLvUp 的技能等级映射规则。
|
||||
*/
|
||||
private applyHeroLevel(model: HeroAttrsComp, targetLv: number) {
|
||||
const hero = HeroInfo[model.hero_uuid];
|
||||
if (!hero) return;
|
||||
const nextLv = Math.max(1, Math.min(3, Math.floor(targetLv)));
|
||||
const nextLv = Math.max(1, Math.min(FightSet.HERO_MAX_LV, Math.floor(targetLv)));
|
||||
const hpRate = model.hp_max > 0 ? model.hp / model.hp_max : 1;
|
||||
model.lv = nextLv;
|
||||
model.ap = hero.ap * nextLv;
|
||||
model.hp_max = hero.hp * nextLv;
|
||||
model.ap = hero.ap * Math.pow(FightSet.HERO_LV_MULTIPLIER, nextLv - 1);
|
||||
model.hp_max = hero.hp * Math.pow(FightSet.HERO_LV_MULTIPLIER, nextLv - 1);
|
||||
model.hp = Math.max(1, Math.floor(model.hp_max * Math.max(0, Math.min(1, hpRate))));
|
||||
model.skills = {};
|
||||
for (const key in hero.skills) {
|
||||
@@ -1266,17 +1160,12 @@ export class MissionCardComp extends CCComp {
|
||||
return Math.max(0, Math.floor(missionData?.hero_num ?? 0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private getCurrentWave(): number {
|
||||
const missionData = this.getMissionData();
|
||||
return Math.max(1, Math.floor(missionData?.level ?? 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private getMissionHeroMaxNum(): number {
|
||||
|
||||
return FightSet.HERO_MAX_NUM
|
||||
}
|
||||
|
||||
@@ -1290,7 +1179,6 @@ export class MissionCardComp extends CCComp {
|
||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||
reset() {
|
||||
this.resetButtonScale(this.cards_chou);
|
||||
// this.resetButtonScale(this.cards_up);
|
||||
|
||||
// 关键:在 reset/销毁 时将 Map 置空,彻底切断引用
|
||||
this.cardComps = [] as any;
|
||||
|
||||
@@ -39,7 +39,7 @@ import { HeroViewComp } from "../hero/HeroViewComp";
|
||||
import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
|
||||
import { UIID } from "../common/config/GameUIConfig";
|
||||
import { SkillView } from "../skill/SkillView";
|
||||
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES } from "../common/config/GameSet";
|
||||
import { FacSet, FightSet } from "../common/config/GameSet";
|
||||
import { HeroInfo } from "../common/config/heroSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { Monster } from "../hero/Mon";
|
||||
@@ -86,8 +86,6 @@ export class MissionComp extends CCComp {
|
||||
private maxMonsterCount: number = 80;
|
||||
/** 怪物数量恢复阈值(降至此值以下恢复刷怪) */
|
||||
private resumeMonsterCount: number = 45;
|
||||
/** 卡池升级波次配置(默认值来自 GameSet.CARD_POOL_UPGRADE_WAVES,保持全局统一) */
|
||||
public cardPoolUpgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
|
||||
|
||||
// ======================== 编辑器绑定节点 ========================
|
||||
|
||||
@@ -829,22 +827,6 @@ export class MissionComp extends CCComp {
|
||||
this.lastTimeSecond = -1;
|
||||
this.clearTime = 0;
|
||||
this.update_time();
|
||||
|
||||
// 检查并推送卡池升级事件
|
||||
this.checkCardPoolUpgrade(wave);
|
||||
}
|
||||
|
||||
/** 检查是否达到卡池升级波次,并推送升级事件 */
|
||||
private checkCardPoolUpgrade(wave: number) {
|
||||
if (!this.cardPoolUpgradeWaves || this.cardPoolUpgradeWaves.length === 0) return;
|
||||
const upgradeIndex = this.cardPoolUpgradeWaves.indexOf(wave);
|
||||
if (upgradeIndex !== -1) {
|
||||
// 根据配置的索引,计算目标等级(初始等级 + index + 1)
|
||||
// 例如 index=0,对应等级为2;index=1,对应等级为3
|
||||
const targetLv = upgradeIndex + 2;
|
||||
oops.message.dispatchEvent(GameEvent.CardPoolUpgrade, { wave, targetLv });
|
||||
mLogger.log(this.debugMode, 'MissionComp', "card pool upgrade event pushed", { wave, targetLv });
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 怪物数量管理 ========================
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
/**
|
||||
* @file MissionHeroComp.ts
|
||||
* @description 英雄召唤与合成管理组件(逻辑层 + 视图层)
|
||||
* @description 英雄召唤管理组件(逻辑层 + 视图层)
|
||||
*
|
||||
* 职责:
|
||||
* 1. 处理 **英雄召唤**:接收 CallHero 事件 → 通过串行队列执行召唤。
|
||||
* 2. 处理 **英雄合成**:检测同 UUID 同等级英雄是否达到合成条件 →
|
||||
* 执行合成动画 → 销毁素材 → 生成高一级英雄。
|
||||
* 3. 支持 **链式合成**:合成完成后自动检测更高等级是否也满足合成条件。
|
||||
* 4. 管理英雄的出生点和掉落动画。
|
||||
* 2. 管理英雄的出生点和掉落动画。
|
||||
*
|
||||
* 关键设计:
|
||||
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**,
|
||||
* 避免同帧并发导致合成判断错误。
|
||||
* - handleSingleSummon() 在每次召唤后检测是否触发合成。
|
||||
* - mergeGroupHeroes() 执行完整合成流程:
|
||||
* 聚合属性 → 向出生点汇聚动画 → 爆点特效 → 生成高级英雄。
|
||||
* - merge_need_count 控制合成所需数量(2 合 1 或 3 合 1)。
|
||||
* - merge_max_lv 控制合成上限等级。
|
||||
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**。
|
||||
* - handleSingleSummon() 仅负责生成英雄;英雄升级由升级卡系统(MissionCardComp)独立处理。
|
||||
*
|
||||
* 历史:
|
||||
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除。
|
||||
* 英雄等级提升现在仅通过升级卡(SpecialUpgrade)实现。
|
||||
*
|
||||
* 依赖:
|
||||
* - Hero(hero/Hero.ts)—— 英雄 ECS 实体类
|
||||
* - HeroAttrsComp —— 英雄属性组件
|
||||
* - HeroInfo / HeroPos / HType(heroSet)—— 英雄静态配置
|
||||
* - FightSet —— 战斗常量(MERGE_NEED / MERGE_MAX)
|
||||
* - oneCom —— 一次性特效组件(控制爆点特效生命周期)
|
||||
* - FightSet —— 战斗常量
|
||||
*/
|
||||
import { _decorator, instantiate, Prefab, v3, Vec3, BoxCollider2D } from "cc";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
@@ -36,17 +31,16 @@ import { HeroInfo, HeroPos, HType } from "../common/config/heroSet";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { FacSet, FightSet, BoxSet } from "../common/config/GameSet";
|
||||
import { oneCom } from "../skill/oncend";
|
||||
import { HeroViewComp } from "../hero/HeroViewComp";
|
||||
import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet";
|
||||
import { MoveComp } from "../hero/MoveComp";
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/**
|
||||
* MissionHeroComp —— 英雄召唤与合成管理器
|
||||
* MissionHeroComp —— 英雄召唤管理器
|
||||
*
|
||||
* 管理英雄的召唤请求队列、出生动画和合成系统。
|
||||
* 合成支持 2 合 1 或 3 合 1,且可链式合成至上限等级。
|
||||
* 管理英雄的召唤请求队列和出生动画。
|
||||
* 英雄升级由 MissionCardComp 的升级卡系统统一处理。
|
||||
*/
|
||||
@ccclass('MissionHeroComp')
|
||||
@ecs.register('MissionHeroComp', false)
|
||||
@@ -76,15 +70,9 @@ export class MissionHeroComp extends CCComp {
|
||||
current_hero_uuid:number=0
|
||||
/** 当前英雄数量缓存 */
|
||||
current_hero_num:number=-1
|
||||
/** 合成规则:需要几个同级英雄才能合成(2 或 3) */
|
||||
merge_need_count:number=FightSet.MERGE_NEED
|
||||
/** 允许合成的最高等级(合成产物不超过此等级) */
|
||||
merge_max_lv:number=FightSet.MERGE_MAX
|
||||
/** 是否正在执行一次合成流程(防止并发) */
|
||||
is_merging:boolean=false
|
||||
/** 是否正在消费召唤队列(防止并发) */
|
||||
is_processing_queue:boolean=false
|
||||
/** 召唤请求队列:保证召唤与合成按顺序串行执行 */
|
||||
/** 召唤请求队列:保证召唤按顺序串行执行 */
|
||||
summon_queue:{ uuid: number; hero_lv: number; pool_lv: number }[]=[]
|
||||
/** 预留英雄列表 */
|
||||
heros:any=[]
|
||||
@@ -134,8 +122,7 @@ export class MissionHeroComp extends CCComp {
|
||||
view.alive();
|
||||
const posIndex = this.pickPositionIndexForHero([hero.eid]);
|
||||
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
|
||||
// 不再直接设置位置,而是播放下落入场动画
|
||||
// 计算出出生点(空中)
|
||||
// 计算出生点(空中)
|
||||
const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
||||
view.node.setPosition(spawnPos);
|
||||
model.posIndex = posIndex;
|
||||
@@ -172,7 +159,7 @@ export class MissionHeroComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 动态分配英雄上场的位置
|
||||
* @param excludeEids 排除计算的实体ID数组(避免复活或合成时把自己算成占据的位置)
|
||||
* @param excludeEids 排除计算的实体ID数组(避免复活时把自己算成占据的位置)
|
||||
*/
|
||||
private pickPositionIndexForHero(excludeEids: number[] = []): number {
|
||||
const heroes = this.getAllHeroes().filter(h => {
|
||||
@@ -205,11 +192,10 @@ export class MissionHeroComp extends CCComp {
|
||||
*
|
||||
* @param uuid 英雄 UUID
|
||||
* @param hero_lv 英雄等级
|
||||
* @param pool_lv 卡池等级
|
||||
* @param pool_lv 卡池等级(历史遗留,新机制下不再使用)
|
||||
* @returns 创建的 Hero 实体
|
||||
*/
|
||||
private addHero(uuid:number=1001,hero_lv:number=1, pool_lv:number=1) {
|
||||
console.log("addHero uuid:",uuid)
|
||||
let hero = ecs.getEntity<Hero>(Hero);
|
||||
let scale = 1
|
||||
const posIndex = this.pickPositionIndexForHero();
|
||||
@@ -229,56 +215,6 @@ export class MissionHeroComp extends CCComp {
|
||||
return hero;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 生成合成后的高级英雄,并覆盖为聚合后的属性。
|
||||
*
|
||||
* @param uuid 英雄 UUID
|
||||
* @param hero_lv 合成后等级
|
||||
* @param pool_lv 卡池等级
|
||||
* @param ap 聚合后攻击力
|
||||
* @param hp_max 聚合后最大生命值
|
||||
* @param targetPos 指定生成位置
|
||||
* @returns 实际生成的英雄等级
|
||||
*/
|
||||
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number, targetPosIndex?: number, targetPos?: Vec3): number {
|
||||
console.log("addMergedHero uuid:",uuid)
|
||||
let hero = ecs.getEntity<Hero>(Hero);
|
||||
let scale = 1
|
||||
|
||||
let posIndex = targetPosIndex;
|
||||
let landingPos = targetPos;
|
||||
if (posIndex === undefined || posIndex < 0 || !landingPos) {
|
||||
posIndex = this.pickPositionIndexForHero();
|
||||
landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
|
||||
}
|
||||
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
||||
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv,posIndex);
|
||||
|
||||
// 召唤完成后,派发事件以更新英雄面板
|
||||
const model = hero.get(HeroAttrsComp);
|
||||
if (model) {
|
||||
model.ap = Math.max(0, ap);
|
||||
model.hp_max = Math.max(1, hp_max);
|
||||
model.hp = model.hp_max;
|
||||
model.dirty_hp = true;
|
||||
|
||||
// 获取视图组件触发升级特效(包含描边更新)
|
||||
const view = hero.get(HeroViewComp);
|
||||
if (view && typeof view['lv_up'] === 'function') {
|
||||
view['lv_up']();
|
||||
}
|
||||
|
||||
oops.message.dispatchEvent(GameEvent.MasterCalled, {
|
||||
eid: hero.eid,
|
||||
model: model
|
||||
});
|
||||
return model.lv;
|
||||
}
|
||||
return hero_lv;
|
||||
}
|
||||
|
||||
// ======================== 英雄查询 ========================
|
||||
|
||||
/** 获取当前全部友方英雄 ECS 实体列表(包括存活和墓地) */
|
||||
@@ -293,62 +229,6 @@ export class MissionHeroComp extends CCComp {
|
||||
return heroes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从存活英雄中挑选可参与本次合成的英雄组。
|
||||
*
|
||||
* @param aliveHeroes 存活英雄列表
|
||||
* @param uuid 目标英雄 UUID
|
||||
* @param hero_lv 目标等级
|
||||
* @param needCount 合成需要数量
|
||||
* @returns 匹配的英雄数组(长度 = needCount 或不足)
|
||||
*/
|
||||
private pickMergeHeroes(aliveHeroes: Hero[], uuid: number, hero_lv: number, needCount: number = 3): Hero[] {
|
||||
const mergeHeroes: Hero[] = [];
|
||||
for (let i = 0; i < aliveHeroes.length; i++) {
|
||||
const model = aliveHeroes[i].get(HeroAttrsComp);
|
||||
if (!model) continue;
|
||||
if (model.hero_uuid !== uuid) continue;
|
||||
if (model.lv !== hero_lv) continue;
|
||||
mergeHeroes.push(aliveHeroes[i]);
|
||||
if (mergeHeroes.length === needCount) break;
|
||||
}
|
||||
return mergeHeroes;
|
||||
}
|
||||
|
||||
/** 统计满足同 UUID 同等级的可合成英雄数量 */
|
||||
private countMergeHeroes(aliveHeroes: Hero[], uuid: number, hero_lv: number): number {
|
||||
let count = 0;
|
||||
for (let i = 0; i < aliveHeroes.length; i++) {
|
||||
const model = aliveHeroes[i].get(HeroAttrsComp);
|
||||
if (!model) continue;
|
||||
if (model.hero_uuid !== uuid) continue;
|
||||
if (model.lv !== hero_lv) continue;
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ======================== 合成规则 ========================
|
||||
|
||||
/**
|
||||
* 读取合成所需数量(仅支持 2 或 3)。
|
||||
* 由 FightSet.MERGE_NEED 配置。
|
||||
*/
|
||||
private getMergeNeedCount(): number {
|
||||
return this.merge_need_count === 2 ? 2 : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断该等级是否还能继续向上合成。
|
||||
* @param hero_lv 当前等级
|
||||
* @returns true = 可以合成(未达上限)
|
||||
*/
|
||||
private canMergeLevel(hero_lv: number): boolean {
|
||||
return hero_lv < Math.max(1, this.merge_max_lv);
|
||||
}
|
||||
|
||||
// ======================== 召唤队列 ========================
|
||||
|
||||
/**
|
||||
@@ -371,159 +251,16 @@ export class MissionHeroComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单次召唤:
|
||||
* 1. 生成英雄。
|
||||
* 2. 检测是否满足合成条件。
|
||||
* 3. 满足则执行合成 + 链式合成。
|
||||
* 处理单次召唤:仅生成英雄,不再触发合成。
|
||||
*
|
||||
* @param uuid 英雄 UUID
|
||||
* @param hero_lv 英雄等级
|
||||
* @param pool_lv 卡池等级
|
||||
* @param pool_lv 卡池等级(历史遗留)
|
||||
*/
|
||||
private async handleSingleSummon(uuid: number, hero_lv: number, pool_lv: number = 1) {
|
||||
this.addHero(uuid, hero_lv, pool_lv);
|
||||
if (!this.canMergeLevel(hero_lv)) return;
|
||||
const needCount = this.getMergeNeedCount();
|
||||
const aliveHeroes = this.getAllHeroes();
|
||||
const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, hero_lv, needCount);
|
||||
if (mergeHeroes.length !== needCount) return;
|
||||
this.is_merging = true;
|
||||
try {
|
||||
const mergedLv = await this.mergeGroupHeroes(mergeHeroes, uuid, hero_lv, pool_lv);
|
||||
await this.tryChainMerge(uuid, mergedLv, pool_lv);
|
||||
} finally {
|
||||
this.is_merging = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 合成动画 ========================
|
||||
|
||||
/**
|
||||
* 将一组合成素材英雄向出生点汇聚并销毁。
|
||||
* 所有素材动画完成后 Promise resolve。
|
||||
*
|
||||
* @param mergeHeroes 合成素材英雄数组
|
||||
* @param spawnPos 汇聚目标位置
|
||||
*/
|
||||
private mergeDestroyAtBirth(mergeHeroes: Hero[], spawnPos: Vec3): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
let doneCount = 0;
|
||||
const total = mergeHeroes.length;
|
||||
if (total <= 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const onDone = () => {
|
||||
doneCount += 1;
|
||||
if (doneCount >= total) {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
for (let i = 0; i < mergeHeroes.length; i++) {
|
||||
mergeHeroes[i].mergeToBirthAndDestroy(spawnPos, onDone);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放合成爆点特效(使用 oneCom 控制生命周期)。
|
||||
* 延迟 0.4 秒后 resolve。
|
||||
*
|
||||
* @param worldPos 特效播放位置
|
||||
*/
|
||||
private playMergeBoomFx(worldPos: Vec3): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const scene = smc.map?.MapView?.scene;
|
||||
const layer = scene?.entityLayer?.node;
|
||||
if (!layer || !layer.isValid) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const prefab: Prefab = oops.res.get("game/skill/end/dead", Prefab)!;
|
||||
if (!prefab) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const fx = instantiate(prefab);
|
||||
if (!fx || !fx.isValid) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
fx.parent = layer;
|
||||
fx.setPosition(worldPos);
|
||||
fx.getComponent(oneCom) || fx.addComponent(oneCom);
|
||||
this.scheduleOnce(() => resolve(), 0.4);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一次完整合成流程:
|
||||
* 1. 聚合素材的 AP 和 HP。
|
||||
* 2. 将素材向出生点汇聚并销毁。
|
||||
* 3. 播放爆点特效。
|
||||
* 4. 生成高一级英雄(属性为聚合值)。
|
||||
*
|
||||
* @param mergeHeroes 合成素材
|
||||
* @param uuid 英雄 UUID
|
||||
* @param hero_lv 素材等级
|
||||
* @param pool_lv 卡池等级
|
||||
* @returns 合成产物的实际等级
|
||||
*/
|
||||
private async mergeGroupHeroes(mergeHeroes: Hero[], uuid: number, hero_lv: number, pool_lv: number): Promise<number> {
|
||||
// 聚合属性
|
||||
let sumAp = 0;
|
||||
let sumHpMax = 0;
|
||||
const mergeEids = [];
|
||||
for (let i = 0; i < mergeHeroes.length; i++) {
|
||||
const model = mergeHeroes[i].get(HeroAttrsComp);
|
||||
mergeEids.push(mergeHeroes[i].eid);
|
||||
if (!model) continue;
|
||||
sumAp += model.ap;
|
||||
sumHpMax += model.hp_max;
|
||||
}
|
||||
|
||||
const posIndex = this.pickPositionIndexForHero(mergeEids);
|
||||
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
|
||||
const spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
|
||||
|
||||
// 汇聚 → 特效 → 生成
|
||||
await this.mergeDestroyAtBirth(mergeHeroes, spawnPos);
|
||||
await this.playMergeBoomFx(spawnPos);
|
||||
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax, posIndex, landingPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* 链式合成:合成完成后继续检测更高等级是否也满足条件。
|
||||
* 最多循环 20 次作为安全上限。
|
||||
*
|
||||
* @param uuid 英雄 UUID
|
||||
* @param startLv 起始检测等级
|
||||
* @param pool_lv 卡池等级
|
||||
*/
|
||||
private async tryChainMerge(uuid: number, startLv: number, pool_lv: number) {
|
||||
let checkLv = Math.max(1, startLv);
|
||||
const needCount = this.getMergeNeedCount();
|
||||
let guard = 0;
|
||||
while (guard < 20) {
|
||||
guard += 1;
|
||||
if (!this.canMergeLevel(checkLv)) {
|
||||
break;
|
||||
}
|
||||
const aliveHeroes = this.getAllHeroes();
|
||||
const sameCount = this.countMergeHeroes(aliveHeroes, uuid, checkLv);
|
||||
if (sameCount < needCount) {
|
||||
break;
|
||||
}
|
||||
const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, checkLv, needCount);
|
||||
if (mergeHeroes.length < needCount) {
|
||||
break;
|
||||
}
|
||||
checkLv = await this.mergeGroupHeroes(mergeHeroes, uuid, checkLv, pool_lv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** ECS 组件移除时触发(当前不销毁节点,保留引用) */
|
||||
reset() {
|
||||
|
||||
Reference in New Issue
Block a user