@@ -40,12 +40,12 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
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 { drawSkillCards } from "../common/config/SCardSet" ;
import { drawEquipCards } from "../common/config/EquipSet" ;
import { drawItemCards } from "../common/config/ICardSet" ;
import { drawSkillCards } from "../common/config/SCardSet" ;
import { CHeroComp } from "./CHeroComp" ;
import { SCardComp } from "./SCardComp" ;
import { EquipListComp } from "./EquipListComp" ;
import { SCardComp } from "./SCardComp" ;
import { oops } from "db://oops-framework/core/Oops" ;
import { HeroAttrsComp } from "../hero/HeroAttrsComp" ;
import { smc } from "../common/SingletonModuleComp" ;
@@ -131,7 +131,7 @@ export class MissionCardComp extends CCComp {
@property ( Node )
skillBoxNode : Node = null !
@property ( Prefab )
skill Prefab : Prefab = null !
sCard Prefab : Prefab = null !
/** 药品面板显示按钮 */
@property ( Node )
@@ -155,27 +155,9 @@ export class MissionCardComp extends CCComp {
/** 英雄数量显示节点(含 icon + num 子节点) */
@property ( Node )
hero_num_node : Node = null !
/** 技能卡牌三选一弹窗节点 */
@property ( Node )
skill_card_node : Node = null !
/**技能卡槽1节点 */
@property ( Node )
skill_card1 : Node = null !
/**技能卡槽2节点 */
@property ( Node )
skill_card2 : Node = null !
/**技能卡槽3节点 */
@property ( Node )
skill_card3 : Node = null !
/**技能刷新按钮节点 */
@property ( Node )
skill_refresh : Node = null !
/**技能广告刷新按钮节点 */
@property ( Node )
skill_ad_refresh : Node = null !
/**可用刷新数显示节点 */
@property ( Node )
skill_refresh_num_node : Node = null !
/** 装备刷新按钮节点 */
@property ( Node )
@@ -188,8 +170,12 @@ export class MissionCardComp extends CCComp {
/** 三个槽位对应的 CHeroComp 控制器缓存(有序数组) */
private cardComps : CHeroComp [ ] = [ ] ;
/** 装备卡槽控制器缓存 */
private equipCardComps : EquipListComp [ ] = [ ] ;
/** 技能卡槽控制器缓存 */
private skillCardComps : SCardComp [ ] = [ ] ;
/** 药品卡槽控制器缓存 */
private itemCardComps : ItemListComp [ ] = [ ] ;
/** 是否已召唤过英雄(用于控制其他面板按钮是否可点击) */
private hasCalledHero : boolean = false ;
/** 已购买装备的 UUID 集合(跨波次保持,防止重复购买) */
@@ -319,6 +305,9 @@ export class MissionCardComp extends CCComp {
this . purchasedEquipUuids . clear ( ) ;
this . populateEquipments ( ) ;
// 填充技能卡槽( 英雄三槽模式: sCardPrefab 实例化 3 个槽位)
this . populateSkillCards ( ) ;
// 重置商品购买记录并填充商品商店
this . purchasedItemUuids . clear ( ) ;
this . populateShopItems ( ) ;
@@ -327,16 +316,14 @@ export class MissionCardComp extends CCComp {
this . hasCalledHero = false ;
this . updatePanelButtonsInteractable ( ) ;
// 首次进入准备阶段自动抽取一次技能卡,后续刷新只能通过技能刷新按钮触发
this . initSkillCardsOnce ( ) ;
mLogger . log ( this . debugMode , "MissionCardComp" , "mission start" ) ;
}
/** 任务结束:清空 3 槽 + 装备商店 + 英雄面板并隐藏整个节点 */
/** 任务结束:清空 3 槽 + 装备商店 + 技能卡槽 + 英雄面板并隐藏整个节点 */
onMissionEnd() {
this . clearAllCards ( ) ;
this . clearEquipments ( ) ;
this . clearSkillCards ( ) ;
this . clearShopItems ( ) ;
if ( this . node && this . node . isValid ) {
this . node . active = false ;
@@ -380,7 +367,6 @@ export class MissionCardComp extends CCComp {
oops . message . on ( GameEvent . HeroDead , this . onHeroDead , this ) ;
oops . message . on ( GameEvent . HeroSell , this . onHeroSell , this ) ;
oops . message . on ( GameEvent . UseHeroCard , this . onUseHeroCard , this ) ;
oops . message . on ( GameEvent . UseSkillCard , this . onUseSkillCard , this ) ;
// 监听装备购买事件,追踪已购记录
oops . message . on ( GameEvent . UseEquipCard , this . onUseEquipCard , this ) ;
// 监听商品购买事件,追踪已购记录
@@ -411,19 +397,16 @@ export class MissionCardComp extends CCComp {
/** 药品面板显示按钮 */
this . showShop ? . on ( NodeEventType . TOUCH_END , this . onShowShopClick , this ) ;
/** 技能卡刷新按钮 */
this . skill_refresh ? . on ( NodeEventType . TOUCH_START , this . onSkillDrawTouchStart , this ) ;
this . skill_refresh ? . on ( NodeEventType . TOUCH_END , this . onSkillDrawTouchEnd , this ) ;
this . skill_refresh ? . on ( NodeEventType . TOUCH_CANCEL , this . onSkillDrawTouchCancel , this ) ;
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 . equip_refresh ? . on ( NodeEventType . TOUCH_START , this . onEquipDrawTouchStart , this ) ;
this . equip_refresh ? . on ( NodeEventType . TOUCH_END , this . onEquipDrawTouchEnd , this ) ;
this . equip_refresh ? . on ( NodeEventType . TOUCH_CANCEL , this . onEquipDrawTouchCancel , this ) ;
/** 技能刷新按钮 */
this . skill_refresh ? . on ( NodeEventType . TOUCH_START , this . onSkillDrawTouchStart , this ) ;
this . skill_refresh ? . on ( NodeEventType . TOUCH_END , this . onSkillDrawTouchEnd , this ) ;
this . skill_refresh ? . on ( NodeEventType . TOUCH_CANCEL , this . onSkillDrawTouchCancel , this ) ;
/** 药品刷新按钮 */
this . shop_refresh ? . on ( NodeEventType . TOUCH_START , this . onShopDrawTouchStart , this ) ;
this . shop_refresh ? . on ( NodeEventType . TOUCH_END , this . onShopDrawTouchEnd , this ) ;
@@ -502,54 +485,6 @@ export class MissionCardComp extends CCComp {
this . updateHeroNumUI ( true , true ) ;
}
/**
* 技能卡入场动画:每个卡槽从场景基准位置下方 80px 处升起,
* 终止位置 = 场景编辑器位置(保持初始不做位移的约束),
* 多张依次错开 staggerDelay 形成"依次飞入"效果。
*/
private readonly skillEnterOffsetY : number = - 80 ;
private readonly skillEnterDuration : number = 0.25 ;
private readonly skillEnterStagger : number = 0.08 ;
private playSkillCardEnterAnim() {
if ( ! this . skillCardComps || this . skillCardComps . length === 0 ) return ;
for ( let i = 0 ; i < this . skillCardComps . length ; i ++ ) {
const comp = this . skillCardComps [ i ] ;
if ( ! comp ) continue ;
const node = comp . node ;
if ( ! node || ! node . isValid ) continue ;
// 缓存场景编辑器位置作为动画终止点(初始不做位移)
const basePos = node . getPosition ( ) ;
// 起始位置 = 基准位置下移 80px
node . setPosition ( basePos . x , basePos . y + this . skillEnterOffsetY , basePos . z ) ;
Tween . stopAllByTarget ( node ) ;
tween ( node )
. delay ( i * this . skillEnterStagger )
. to ( this . skillEnterDuration , { position : basePos } , { easing : 'quadOut' } )
. start ( ) ;
}
}
/**
* 首次进入准备阶段时自动抽取一次技能卡:
* - 仅在任务开始时调用一次。
* - 后续抽卡只能通过技能刷新按钮触发。
* - 面板轮播模式下技能面板始终 active, 仅通过滑动切换可见性。
*/
private initSkillCardsOnce() {
if ( this . skillCardComps . length === 0 ) {
this . cacheCardComps ( ) ;
}
const cards = this . buildSkillDrawCards ( ) ;
this . dispatchCardsToSkillSlots ( cards ) ;
this . playSkillCardEnterAnim ( ) ;
// 首次弹出技能三选一的时候弹出guide2
if ( ! smc . finish_guides . includes ( 2 ) ) {
oops . gui . open ( UIID . Guide2 ) ;
}
}
/**
* 技能面板按钮回调:滑动到技能面板(index=2)。
*/
@@ -569,9 +504,9 @@ export class MissionCardComp extends CCComp {
}
/**
* 填充商品列表:
* 填充商品列表(槽位复用模式) :
* 从 ICardSet 按权重抽取 3 张商品卡,
* 实例化 itemPrefab 到 shopBoxNode ,
* 优先复用现有槽位( shopBoxNode 子节点),不足再实例化新槽位 ,
* 每个商品项由 ItemListComp 渲染并处理购买。
*
* 商品为一次性 buff 技能( Instant 触发, t_times=1) ,
@@ -580,25 +515,32 @@ export class MissionCardComp extends CCComp {
private populateShopItems() {
if ( ! this . shopBoxNode || ! this . itemPrefab ) return ;
// 清空旧列表
this . shopBoxNode . removeAllChildren ( ) ;
// 从商品卡池按权重抽取 3 张
const items = drawItemCards ( 3 ) ;
for ( const item of items ) {
const node = instantiate ( this . itemPrefab ) ;
this . shopBoxNode . addChild ( node ) ;
const comp = node . getComponent ( ItemListComp ) || node . addComponent ( ItemListComp ) ;
comp . applyCardData ( item ) ;
const targetCount = items . length ;
// 已购买的商品标记为已购
if ( this . purchasedItemUuids . has ( item . uuid ) ) {
comp . setPurchased ( ) ;
// 确保槽位数量足够(复用 + 补充实例化)
this . ensureItemSlots ( targetCount ) ;
// 分发数据到槽位
for ( let i = 0 ; i < this . itemCardComps . length ; i ++ ) {
const comp = this . itemCardComps [ i ] ;
if ( ! comp ) continue ;
if ( i < targetCount ) {
comp . applyCardData ( items [ i ] ) ;
// 已购买的商品标记为已购
if ( this . purchasedItemUuids . has ( items [ i ] . uuid ) ) {
comp . setPurchased ( ) ;
}
} else {
// 多余槽位清空
comp . applyCardData ( null as unknown as CardConfig ) ;
}
}
// 根据物品数量设置列表面板高度
this . resizeListBox ( this . shopBoxNode , items . length ) ;
this . resizeListBox ( this . shopBoxNode , targetCount ) ;
// 默认显示商店面板
if ( this . shopPanNode ) {
@@ -606,45 +548,39 @@ export class MissionCardComp extends CCComp {
}
mLogger . log ( this . debugMode , "MissionCardComp" , "populate shop items" , {
count : items.length
count : items.length ,
slotCount : this.itemCardComps.length
} ) ;
}
/** 清空商品列表 */
private clearShopItems() {
if ( this . shopBoxNode && this . shopBoxNode . isValid ) {
this . shopBoxNode . removeAllChildren ( ) ;
/**
* 确保药品槽位数量足够。
* 优先复用现有槽位,不足时从 itemPrefab 实例化补充。
*/
private ensureItemSlots ( targetCount : number ) {
if ( ! this . shopBoxNode || ! this . itemPrefab ) return ;
// 刷新缓存
this . cacheItemCardComps ( ) ;
// 补充实例化不足的槽位
while ( this . itemCardComps . length < targetCount ) {
const node = instantiate ( this . itemPrefab ) ;
this . shopBoxNode . addChild ( node ) ;
const comp = node . getComponent ( ItemListComp ) || node . addComponent ( ItemListComp ) ;
this . itemCardComps . push ( comp ) ;
}
}
private dispatchCardsToSkillSlots ( cards : CardConfig [ ] ) {
if ( ! this . skillCardComps ) return ;
for ( let i = 0 ; i < this . skill CardComps. length ; i ++ ) {
if ( this . skillCardC omps [ i ] ) {
this . skillCardComps [ i ] . applyCardData ( cards [ i ] ? ? null ) ;
/** 清空商品列表(复用槽位,仅清空数据) */
private clearShopItems() {
for ( const comp of this . item CardComps) {
if ( comp && comp . node && c omp. node . isValid ) {
comp . applyCardData ( null as unknown as CardConfig ) ;
}
}
}
private onUseSkillCard ( event : string , args : any ) {
// 购买技能卡后不再关闭弹窗:技能卡池与英雄卡池保持一致,
// 由 onCardUsed 立即向被使用的槽位补充一张新技能卡。
// 弹窗由外部(按钮/阶段切换)控制显隐。
// 修复:独立判断 guide2 的关闭 和 guide3 的开启
if ( ! smc . finish_guides . includes ( 2 ) ) {
smc . finish_guides . push ( 2 ) ;
oops . gui . remove ( UIID . Guide2 ) ;
}
if ( ! smc . finish_guides . includes ( 3 ) ) {
oops . gui . open ( UIID . Guide3 ) ;
}
// 驻场技能可能影响刷新费用(如"刷新优惠"),延迟到下一帧刷新费用 UI
this . scheduleOnce ( ( ) = > this . updateCoinAndCostUI ( ) , 0 ) ;
}
/** 装备购买事件回调:记录已购 UUID, 防止重复购买 */
private onUseEquipCard ( event : string , args : any ) {
const usedCard = args as CardConfig ;
@@ -668,7 +604,6 @@ export class MissionCardComp extends CCComp {
oops . message . off ( GameEvent . HeroDead , this . onHeroDead , this ) ;
oops . message . off ( GameEvent . HeroSell , this . onHeroSell , this ) ;
oops . message . off ( GameEvent . UseHeroCard , this . onUseHeroCard , this ) ;
oops . message . off ( GameEvent . UseSkillCard , this . onUseSkillCard , this ) ;
oops . message . off ( GameEvent . UseEquipCard , this . onUseEquipCard , this ) ;
oops . message . off ( GameEvent . UseItemCard , this . onUseItemCard , this ) ;
oops . message . off ( GameEvent . UseSpecialCard , this . onUseSpecialCard , this ) ;
@@ -691,16 +626,6 @@ export class MissionCardComp extends CCComp {
if ( this . showEquips && this . showEquips . isValid ) {
this . showEquips . off ( NodeEventType . TOUCH_END , this . onShowEquipsClick , this ) ;
}
if ( this . skill_refresh && this . skill_refresh . isValid ) {
this . skill_refresh . off ( NodeEventType . TOUCH_START , this . onSkillDrawTouchStart , this ) ;
this . skill_refresh . off ( NodeEventType . TOUCH_END , this . onSkillDrawTouchEnd , this ) ;
this . skill_refresh . off ( NodeEventType . TOUCH_CANCEL , this . onSkillDrawTouchCancel , this ) ;
}
if ( this . skill_ad_refresh && this . skill_ad_refresh . isValid ) {
this . skill_ad_refresh . off ( NodeEventType . TOUCH_START , this . onSkillAdDrawTouchStart , this ) ;
this . skill_ad_refresh . off ( NodeEventType . TOUCH_END , this . onSkillAdDrawTouchEnd , this ) ;
this . skill_ad_refresh . off ( NodeEventType . TOUCH_CANCEL , this . onSkillAdDrawTouchCancel , this ) ;
}
if ( this . showShop && this . showShop . isValid ) {
this . showShop . off ( NodeEventType . TOUCH_END , this . onShowShopClick , this ) ;
}
@@ -709,6 +634,11 @@ export class MissionCardComp extends CCComp {
this . equip_refresh . off ( NodeEventType . TOUCH_END , this . onEquipDrawTouchEnd , this ) ;
this . equip_refresh . off ( NodeEventType . TOUCH_CANCEL , this . onEquipDrawTouchCancel , this ) ;
}
if ( this . skill_refresh && this . skill_refresh . isValid ) {
this . skill_refresh . off ( NodeEventType . TOUCH_START , this . onSkillDrawTouchStart , this ) ;
this . skill_refresh . off ( NodeEventType . TOUCH_END , this . onSkillDrawTouchEnd , this ) ;
this . skill_refresh . off ( NodeEventType . TOUCH_CANCEL , this . onSkillDrawTouchCancel , this ) ;
}
if ( this . shop_refresh && this . shop_refresh . isValid ) {
this . shop_refresh . off ( NodeEventType . TOUCH_START , this . onShopDrawTouchStart , this ) ;
this . shop_refresh . off ( NodeEventType . TOUCH_END , this . onShopDrawTouchEnd , this ) ;
@@ -811,13 +741,14 @@ export class MissionCardComp extends CCComp {
/**
* 单张卡牌被成功使用后的整池刷新回调:
* - payload 为该卡槽组件实例( CHeroComp 或 SCard Comp) 。
* - payload 为该卡槽组件实例( CHeroComp / EquipListComp / ItemList Comp) 。
* - 命中英雄卡槽 → 重新构建 3 张英雄池卡牌并分发到所有英雄槽。
* - 命中技能 卡槽 → 重新构建 3 张技能卡 并分发到所有技能 槽。
* - 命中装备 卡槽 → 重新抽取装备 并分发到所有装备 槽。
* - 命中药品卡槽 → 重新抽取药品并分发到所有药品槽。
* - 未命中任何槽位 → 忽略。
*
* Why: 购买一张卡后立即刷新整个卡池(不扣金币),让玩家持续从新池中挑选;
* 英雄卡池与技能 卡池遵循同一逻辑。
* 所有 卡池遵循同一逻辑。
* SpecialRefresh 自身效果( onUseSpecialCard 中的 tryRefreshHeroCards)
* 已是整池刷新,因此 CHeroComp 对其不派发 CardUsed, 避免双重刷新。
*/
@@ -835,15 +766,32 @@ export class MissionCardComp extends CCComp {
return ;
}
const equipIdx = this . equipCardComps . findIndex ( c = > c === source ) ;
if ( equipIdx >= 0 ) {
this . populateEquipments ( ) ;
mLogger . log ( this . debugMode , "MissionCardComp" , "refresh equip pool after buy" , {
triggerSlot : equipIdx
} ) ;
return ;
}
const skillIdx = this . skillCardComps . findIndex ( c = > c === source ) ;
if ( skillIdx >= 0 ) {
const cards = this . build SkillDraw Cards( ) ;
this . dispatchCardsToSkillSlots ( cards ) ;
this . populate SkillCards( ) ;
mLogger . log ( this . debugMode , "MissionCardComp" , "refresh skill pool after buy" , {
triggerSlot : skillIdx
} ) ;
return ;
}
const itemIdx = this . itemCardComps . findIndex ( c = > c === source ) ;
if ( itemIdx >= 0 ) {
this . populateShopItems ( ) ;
mLogger . log ( this . debugMode , "MissionCardComp" , "refresh item pool after buy" , {
triggerSlot : itemIdx
} ) ;
return ;
}
}
// ======================== 按钮触控回调 ========================
@@ -1030,9 +978,9 @@ export class MissionCardComp extends CCComp {
}
/**
* 填充装备列表:
* 填充装备列表(槽位复用模式) :
* 从 EquipPoolList 按权重抽取 3 张装备卡,
* 实例化 equipPrefab 到 equipsBoxNode ,
* 优先复用现有槽位( EquipsBoxNode 子节点),不足再实例化新槽位 ,
* 每个装备项由 EquipListComp 渲染并处理购买。
*
* 已购买的装备会标记为已购状态(隐藏购买按钮)。
@@ -1040,25 +988,32 @@ export class MissionCardComp extends CCComp {
private populateEquipments() {
if ( ! this . equipsBoxNode || ! this . equipPrefab ) return ;
// 清空旧列表
this . equipsBoxNode . removeAllChildren ( ) ;
// 从装备卡池按权重抽取 3 张
const equips = drawEquipCards ( 3 ) ;
for ( const equip of equips ) {
const node = instantiate ( this . equipPrefab ) ;
this . equipsBoxNode . addChild ( node ) ;
const comp = node . getComponent ( EquipListComp ) || node . addComponent ( EquipListComp ) ;
comp . applyCardData ( equip ) ;
const targetCount = equips . length ;
// 已购买的装备标记为已购
if ( this . purchasedEquipUuids . has ( equip . uuid ) ) {
comp . setPurchased ( ) ;
// 确保槽位数量足够(复用 + 补充实例化)
this . ensureEquipSlots ( targetCount ) ;
// 分发数据到槽位
for ( let i = 0 ; i < this . equipCardComps . length ; i ++ ) {
const comp = this . equipCardComps [ i ] ;
if ( ! comp ) continue ;
if ( i < targetCount ) {
comp . applyCardData ( equips [ i ] ) ;
// 已购买的装备标记为已购
if ( this . purchasedEquipUuids . has ( equips [ i ] . uuid ) ) {
comp . setPurchased ( ) ;
}
} else {
// 多余槽位清空
comp . applyCardData ( null as unknown as CardConfig ) ;
}
}
// 根据物品数量设置列表面板高度
this . resizeListBox ( this . equipsBoxNode , equips . length ) ;
this . resizeListBox ( this . equipsBoxNode , targetCount ) ;
// 默认显示装备面板
if ( this . equipsPanNode ) {
@@ -1066,55 +1021,125 @@ export class MissionCardComp extends CCComp {
}
mLogger . log ( this . debugMode , "MissionCardComp" , "populate equipments" , {
count : equips.length
count : equips.length ,
slotCount : this.equipCardComps.length
} ) ;
}
/** 清空装备列表 */
private clearEquipments() {
if ( this . equipsBoxNode && this . equipsBoxNode . isValid ) {
this . equipsBoxNode . removeAllChildren ( ) ;
/**
* 确保装备槽位数量足够。
* 优先复用现有槽位,不足时从 equipPrefab 实例化补充。
*/
private ensureEquipSlots ( targetCount : number ) {
if ( ! this . equipsBoxNode || ! this . equipPrefab ) return ;
// 刷新缓存
this . cacheEquipCardComps ( ) ;
// 补充实例化不足的槽位
while ( this . equipCardComps . length < targetCount ) {
const node = instantiate ( this . equipPrefab ) ;
this . equipsBoxNode . addChild ( node ) ;
const comp = node . getComponent ( EquipListComp ) || node . addComponent ( EquipListComp ) ;
this . equipCardComps . push ( comp ) ;
}
}
// ======================== 技能抽卡按钮回调 ========================
private onSkillDrawTouchStart () {
this . playButtonPressAnim ( this . skill_refresh ) ;
}
private onSkillDrawTouchEnd() {
oops . audio . playEffect ( "music/button" ) ;
this . playButtonClickAnim ( this . skill_refresh , ( ) = > this . onClickSkillRefresh ( ) ) ;
}
private onSkillDrawTouchCancel() {
this . playButtonResetAnim ( this . skill_refresh ) ;
/** 清空装备列表(复用槽位,仅清空数据) */
private clearEquipments () {
for ( const comp of this . equipCardComps ) {
if ( comp && comp . node && comp . node . isValid ) {
comp . applyCardData ( null as unknown as CardConfig ) ;
}
}
}
private onSkillAdDrawTouchStart() {
this . playButtonPressAnim ( this . skill_ad_refresh ) ;
}
private onSkillAdDrawTouchEnd() {
oops . audio . playEffect ( "music/button" ) ;
this . playButtonClickAnim ( this . skill_ad_refresh , ( ) = > this . onClickSkillAdRefresh ( ) ) ;
}
private onSkillAdDrawTouchCancel() {
this . playButtonResetAnim ( this . skill_ad_refresh ) ;
// ======================== 技能抽卡(英雄三槽模式) ========================
/**
* 填充技能卡槽:
* 从 SkillCardList 按权重抽取 3 张技能卡,
* 优先复用现有槽位( skillBoxNode 子节点),不足再实例化新槽位,
* 每个技能卡项由 SCardComp 渲染并处理购买。
*/
private populateSkillCards() {
if ( ! this . skillBoxNode || ! this . sCardPrefab ) return ;
// 从技能卡池按权重抽取 3 张
const skills = drawSkillCards ( 3 ) ;
const targetCount = skills . length ;
// 确保槽位数量足够(复用 + 补充实例化)
this . ensureSkillSlots ( targetCount ) ;
// 分发数据到槽位
for ( let i = 0 ; i < this . skillCardComps . length ; i ++ ) {
const comp = this . skillCardComps [ i ] ;
if ( ! comp ) continue ;
if ( i < targetCount ) {
comp . applyDrawCard ( skills [ i ] ) ;
} else {
// 多余槽位清空
comp . applyDrawCard ( null ) ;
}
}
// 根据技能数量设置列表面板高度
this . resizeListBox ( this . skillBoxNode , targetCount ) ;
// 默认显示技能面板
if ( this . skillPanNode ) {
this . skillPanNode . active = true ;
}
mLogger . log ( this . debugMode , "MissionCardComp" , "populate skill cards" , {
count : skills.length ,
slotCount : this.skillCardComps.length
} ) ;
}
private onClickSkillRefresh() {
const cost = MissionEconomy . getRefreshCost ( this . refreshCost ) ;
const success = MissionEconomy . executeRefresh ( this . refreshCost ) ;
if ( ! success ) {
this . showSmallTip ( "refresh_coin" ) ;
/**
* 确保技能槽位数量足够。
* 优先复用现有槽位,不足时从 sCardPrefab 实例化补充。
*/
private ensureSkillSlots ( targetCount : number ) {
if ( ! this . skillBoxNode || ! this . sCardPrefab ) return ;
// 刷新缓存
this . cacheSkillCardComps ( ) ;
// 补充实例化不足的槽位
while ( this . skillCardComps . length < targetCount ) {
const node = instantiate ( this . sCardPrefab ) ;
this . skillBoxNode . addChild ( node ) ;
const comp = node . getComponent ( SCardComp ) || node . addComponent ( SCardComp ) ;
this . skillCardComps . push ( comp ) ;
}
}
/** 清空技能卡槽(复用槽位,仅清空数据) */
private clearSkillCards() {
for ( const comp of this . skillCardComps ) {
if ( comp && comp . node && comp . node . isValid ) {
comp . clearBySystem ( ) ;
}
}
}
/**
* 缓存技能卡槽组件引用。
* 技能卡槽在编辑器中通过 sCardPrefab 实例化为 skillBoxNode 的子节点,
* 每个子节点根节点挂载 SCardComp。
*/
private cacheSkillCardComps() {
if ( ! this . skillBoxNode || ! this . skillBoxNode . isValid ) {
this . skillCardComps = [ ] ;
return ;
}
const cards = this . buildSkillDrawCards ( ) ;
this . dispatchCardsToSkillSlots ( c ards ) ;
}
private onClickSkillAdRefresh() {
// TODO: 接入广告 SDK 逻辑,目前先直接刷新
const cards = this . buildSkillDrawCards ( ) ;
this . dispatchCardsToSkillSlots ( cards ) ;
this . skillCardComps = this . skillBoxNode . children
. map ( node = > node . getComponent ( SC ardComp ) )
. filter ( ( comp ) : comp is SCardComp = > ! ! comp ) ;
}
// ======================== 装备/药品刷新按钮回调 ========================
@@ -1141,6 +1166,17 @@ export class MissionCardComp extends CCComp {
this . playButtonResetAnim ( this . shop_refresh ) ;
}
private onSkillDrawTouchStart() {
this . playButtonPressAnim ( this . skill_refresh ) ;
}
private onSkillDrawTouchEnd() {
oops . audio . playEffect ( "music/button" ) ;
this . playButtonClickAnim ( this . skill_refresh , ( ) = > this . onClickSkillRefresh ( ) ) ;
}
private onSkillDrawTouchCancel() {
this . playButtonResetAnim ( this . skill_refresh ) ;
}
/** 装备刷新:扣费后重新抽取 3 张装备展示 */
private onClickEquipRefresh() {
const cost = MissionEconomy . getRefreshCost ( this . refreshCost ) ;
@@ -1163,16 +1199,24 @@ export class MissionCardComp extends CCComp {
this . populateShopItems ( ) ;
}
/** 技能刷新:扣费后重新抽取 3 张技能卡展示 */
private onClickSkillRefresh() {
const success = MissionEconomy . executeRefresh ( this . refreshCost ) ;
if ( ! success ) {
this . showSmallTip ( "refresh_coin" ) ;
return ;
}
this . populateSkillCards ( ) ;
}
/** 将 cardPrefab 实例化为 3 个卡槽并映射为 CHeroComp, 形成固定顺序控制数组( 幂等: 已缓存则不重复实例化) */
private cacheCardComps() {
// 幂等保护:卡槽已存在且节点有效时,仅刷新 skillCardComps 缓存,不重复实例化英雄卡槽
// 幂等保护:卡槽已存在且节点有效时,仅刷新其他 缓存,不重复实例化英雄卡槽
const hasValidHeroComps = this . cardComps . length === 3
&& this . cardComps . every ( c = > c && c . node && c . node . isValid ) ;
if ( hasValidHeroComps ) {
const skillNodes = [ this . skill_card1 , this . skill_card2 , this . skill_card3 ] ;
this . skill CardComps = skillNodes
. map ( node = > node ? . getComponent ( SCardComp ) )
. filter ( ( comp ) : comp is SCardComp = > ! ! comp ) ;
this . cacheEquipCardComps ( ) ;
this . cacheItem CardComps( ) ;
return ;
}
@@ -1210,10 +1254,38 @@ export class MissionCardComp extends CCComp {
} ) ;
}
const skillNodes = [ this . skill_card1 , this . skill_card2 , this . skill_card3 ] ;
this . skill CardComps = skillNodes
. map ( node = > node ? . getComponent ( SCardComp ) )
. filter ( ( comp ) : comp is SCardComp = > ! ! comp ) ;
this . cacheEquipCardComps ( ) ;
this . cacheItem CardComps( ) ;
}
/**
* 缓存装备卡槽组件引用。
* 装备卡槽在编辑器中通过 equipPrefab 实例化为 equipsBoxNode 的子节点,
* 每个子节点根节点挂载 EquipListComp。
*/
private cacheEquipCardComps() {
if ( ! this . equipsBoxNode || ! this . equipsBoxNode . isValid ) {
this . equipCardComps = [ ] ;
return ;
}
this . equipCardComps = this . equipsBoxNode . children
. map ( node = > node . getComponent ( EquipListComp ) )
. filter ( ( comp ) : comp is EquipListComp = > ! ! comp ) ;
}
/**
* 缓存药品卡槽组件引用。
* 药品卡槽在编辑器中通过 itemPrefab 实例化为 shopBoxNode 的子节点,
* 每个子节点根节点挂载 ItemListComp。
*/
private cacheItemCardComps() {
if ( ! this . shopBoxNode || ! this . shopBoxNode . isValid ) {
this . itemCardComps = [ ] ;
return ;
}
this . itemCardComps = this . shopBoxNode . children
. map ( node = > node . getComponent ( ItemListComp ) )
. filter ( ( comp ) : comp is ItemListComp = > ! ! comp ) ;
}
// ======================== 核心业务:抽卡 ========================
@@ -1373,16 +1445,6 @@ export class MissionCardComp extends CCComp {
return cards [ cards . length - 1 ] ;
}
/**
* 构建技能卡抽卡结果,返回 3 张。
*
* 技能卡池长期存在,不再按波次过滤,
* 直接从 SCardSet 的 drawSkillCards 按权重抽取。
*/
private buildSkillDrawCards ( ) : CardConfig [ ] {
return drawSkillCards ( 3 ) ;
}
private tryRefreshHeroCards ( heroType? : HType ) : boolean {
const cards = drawCardsByRule ( 1 , {
count : 3 ,
@@ -1438,11 +1500,6 @@ export class MissionCardComp extends CCComp {
this . cardComps . forEach ( comp = > {
if ( comp ) comp . clearBySystem ( ) ;
} ) ;
if ( this . skillCardComps ) {
this . skillCardComps . forEach ( comp = > {
if ( comp ) comp . applyCardData ( null ) ;
} ) ;
}
}
private layoutCardSlots() {
@@ -1489,18 +1546,6 @@ export class MissionCardComp extends CCComp {
}
}
if ( this . skill_refresh ) {
const nobg = this . skill_refresh . getChildByName ( "nobg" ) ;
if ( nobg ) {
nobg . active = ! this . canDrawCards ( ) ;
}
const coinNode = this . skill_refresh . getChildByName ( "coin" ) ;
const numLabel = coinNode ? . getChildByName ( "num" ) ? . getComponent ( Label ) ;
if ( numLabel ) {
numLabel . string = ` ${ MissionEconomy . getRefreshCost ( this . refreshCost ) } ` ;
}
}
if ( this . equip_refresh ) {
const nobg = this . equip_refresh . getChildByName ( "nobg" ) ;
if ( nobg ) {
@@ -1524,6 +1569,18 @@ export class MissionCardComp extends CCComp {
numLabel . string = ` ${ MissionEconomy . getRefreshCost ( this . refreshCost ) } ` ;
}
}
if ( this . skill_refresh ) {
const nobg = this . skill_refresh . getChildByName ( "nobg" ) ;
if ( nobg ) {
nobg . active = ! this . canDrawCards ( ) ;
}
const coinNode = this . skill_refresh . getChildByName ( "coin" ) ;
const numLabel = coinNode ? . getChildByName ( "num" ) ? . getComponent ( Label ) ;
if ( numLabel ) {
numLabel . string = ` ${ MissionEconomy . getRefreshCost ( this . refreshCost ) } ` ;
}
}
}
private updateCoinAndCostUI() {