refactor(missionCard): 重构抽卡逻辑,新增无重复抽取工具方法

1.  抽卡逻辑抽离为pickUniqueHeroCards私有方法,统一处理无重复抽取规则
2.  优化buildDrawCards和tryRefreshHeroCards的抽卡实现,确保同组内和场上英雄不重复
3.  调整刷新次数UI的文本显示格式,移除固定前后缀文案
4.  移除废弃的drawCardsByRule调用,统一使用自有抽卡逻辑
This commit is contained in:
panFD
2026-08-20 00:10:44 +08:00
parent 79746409ef
commit c1286173f7

View File

@@ -19,7 +19,8 @@
* 关键设计:
* - 3 个 CHeroComp 由 cardPrefab 动态实例化生成,通过 cacheCardComps() 映射为有序数组 cardComps[]
* 之后所有分发、清空操作均通过此数组进行。
* - buildDrawCards() 从基础英雄卡池按权重抽取 3 张,不足时循环补齐。
* - buildDrawCards() 从基础英雄卡池按权重抽取 3 张,同组内英雄绝不重复,
* 也不与场上存活英雄撞车(池不足时回退全量池,仍不重复补位)。
* - 英雄上限校验onUseHeroCard采用 guard/cancel 模式:
* CHeroComp 发出 UseHeroCard 事件并传入 guard 对象,
* 本组件可通过 guard.cancel=true 阻止使用。
@@ -41,7 +42,7 @@ import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, Tween, twe
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 { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCardList, SpecialRefreshHeroType } from "../common/config/CardSet";
import { CardConfig, CardPoolList, CardType, SpecialRefreshCardList, SpecialRefreshHeroType } from "../common/config/CardSet";
import { drawEquipCards } from "../common/config/EquipSet";
import { drawItemCards } from "../common/config/ICardSet";
import { drawSkillCards } from "../common/config/SCardSet";
@@ -1802,9 +1803,9 @@ export class MissionCardComp extends CCComp {
* 构建本次抽卡结果,返回 3 张英雄卡。
*
* 规则:
* 1. 从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复。
* 1. 从基础英雄卡池按权重抽取,同一次抽卡内英雄不重复(三选一硬性规则)
* 2. 过滤已上场英雄(卡池不足 3 张时自动回退到未过滤池,保证能抽满)。
* 3. 不足 3 张时循环补齐(允许重复)
* 3. 候选池唯一英雄不足 3 个时返回实际数量,也绝不重复补位
*
* 注:升级卡机制已移除,英雄卡池只出普通英雄卡。
*/
@@ -1817,24 +1818,7 @@ export class MissionCardComp extends CCComp {
const filtered = allHeroCards.filter(c => !onFieldUuids.has(c.uuid));
const availableHeroCards = filtered.length >= 3 ? filtered : allHeroCards;
const result: CardConfig[] = [];
const usedUuids = new Set<number>();
// 按权重抽取,同一次抽卡内英雄不重复
while (result.length < 3) {
const pool = availableHeroCards.filter(c => !usedUuids.has(c.uuid));
if (pool.length === 0) break;
const pick = this.weightedPick(pool);
if (!pick) break;
result.push(pick);
usedUuids.add(pick.uuid);
}
// 兜底:不足 3 张时循环补齐(允许重复)
while (result.length < 3) {
result.push(availableHeroCards[result.length % availableHeroCards.length]);
}
return result.slice(0, 3);
return this.pickUniqueHeroCards(availableHeroCards, 3);
}
/** 单次按权重抽取一张卡 */
@@ -1849,20 +1833,48 @@ export class MissionCardComp extends CCComp {
return cards[cards.length - 1];
}
/**
* 从候选池按权重抽取 count 张互不重复的英雄卡。
* Why: 三选一不允许出现一样的英雄——池内唯一英雄不足 count 时
* 返回实际抽到的数量,也绝不以重复卡补位。
* @param pool 已完成上场过滤/类型过滤的候选池
* @param count 目标张数
* @returns 不重复的卡配置数组0 ~ count 张)
*/
private pickUniqueHeroCards(pool: CardConfig[], count: number): CardConfig[] {
const result: CardConfig[] = [];
const usedUuids = new Set<number>();
while (result.length < count) {
const rest = pool.filter(c => !usedUuids.has(c.uuid));
if (rest.length === 0) break;
const pick = this.weightedPick(rest);
if (!pick) break;
result.push(pick);
usedUuids.add(pick.uuid);
}
return result;
}
/**
* 刷新卡效果:按英雄类型重新抽取 3 张英雄卡。
* 先过滤(类型 + 已上场)再抽取,保证三选一内不重复且尽量不与场上英雄撞车;
* 子池不足 3 张时回退到该类型的全量池,仍抽不齐则如实返回(不重复补位)。
*/
private tryRefreshHeroCards(heroType?: HType): boolean {
let cards = drawCardsByRule({
count: 3,
type: CardType.Hero,
heroType,
unique: true, // 保证一次刷新内的英雄卡不重复
});
// 过滤已上场英雄;若过滤后为空则回退到未过滤池,避免无可抽卡
let pool = CardPoolList.filter(c => c.type === CardType.Hero);
if (heroType !== undefined) {
pool = pool.filter(c => HeroInfo[c.uuid]?.type === heroType);
}
if (pool.length === 0) return false;
const onFieldUuids = this.getAliveHeroUuidSet();
const filtered = cards.filter(c => !onFieldUuids.has(c.uuid));
if (filtered.length > 0) cards = filtered;
const filtered = pool.filter(c => !onFieldUuids.has(c.uuid));
const available = filtered.length >= 3 ? filtered : pool;
const cards = this.pickUniqueHeroCards(available, 3);
if (cards.length <= 0) return false;
this.layoutCardSlots();
this.dispatchCardsToSlots(cards.slice(0, 3));
this.dispatchCardsToSlots(cards);
return true;
}
@@ -2048,10 +2060,10 @@ export class MissionCardComp extends CCComp {
this.updateDrawCostUI();
}
/** 刷新选择英雄次数提示 Label"第X次"),未绑定或节点失效时静默跳过 */
/** 刷新选择英雄次数提示 Label仅数字,前后缀文字由 UI 提供),未绑定或节点失效时静默跳过 */
private updatePickCountLabel() {
if (this.pickCountLabel && this.pickCountLabel.isValid) {
this.pickCountLabel.string = `${this.currentPickIndex}`;
this.pickCountLabel.string = `${this.currentPickIndex}`;
}
}