From c1286173f79d2ac28cf4d0f98baa493de7193e10 Mon Sep 17 00:00:00 2001 From: panFD Date: Thu, 20 Aug 2026 00:10:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor(missionCard):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=8A=BD=E5=8D=A1=E9=80=BB=E8=BE=91=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=97=A0=E9=87=8D=E5=A4=8D=E6=8A=BD=E5=8F=96=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 抽卡逻辑抽离为pickUniqueHeroCards私有方法,统一处理无重复抽取规则 2. 优化buildDrawCards和tryRefreshHeroCards的抽卡实现,确保同组内和场上英雄不重复 3. 调整刷新次数UI的文本显示格式,移除固定前后缀文案 4. 移除废弃的drawCardsByRule调用,统一使用自有抽卡逻辑 --- assets/script/game/map/MissionCardComp.ts | 80 +++++++++++++---------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 830cd8e4..810ec04e 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -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(); - - // 按权重抽取,同一次抽卡内英雄不重复 - 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(); + 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}`; } }