feat: 拆分特殊卡类型并实现升级和刷新功能

- 将 CardType.Special 拆分为 SpecialUpgrade 和 SpecialRefresh
- 新增特殊卡使用逻辑:升级功能卡可随机升级场上英雄,刷新功能卡可筛选卡池
- 添加 drawCardsByRule 函数支持按类型、英雄类型和等级抽取卡牌
- 在 MissionCardComp 中处理特殊卡使用事件并更新UI
This commit is contained in:
walkpan
2026-03-29 21:16:03 +08:00
parent 7de9d6b3e3
commit d6c2ba6534
4 changed files with 185 additions and 27 deletions

View File

@@ -1,10 +1,12 @@
import * as exp from "constants"
import { HeroInfo, HType } from "./heroSet"
/** 卡牌大类定义 */
export enum CardType {
Hero = 1,
Skill = 2,
Special = 3,
SpecialUpgrade = 3,
SpecialRefresh = 4,
}
/** 卡池等级定义 */
@@ -89,41 +91,58 @@ export const CardPoolList: CardConfig[] = [
{ uuid: 5304, type: CardType.Hero, cost: 3, weight: 25, lv: 6, hero_lv: 1 },
{ uuid: 7001, type: CardType.Special, cost: 1, weight: 20, lv: 1 },
{ uuid: 7001, type: CardType.SpecialUpgrade, cost: 6, weight: 16, lv: 1 },
{ uuid: 7002, type: CardType.SpecialUpgrade, cost: 6, weight: 14, lv: 2 },
{ uuid: 7101, type: CardType.SpecialRefresh, cost: 4, weight: 14, lv: 1 },
{ uuid: 7102, type: CardType.SpecialRefresh, cost: 4, weight: 14, lv: 1 },
{ uuid: 7103, type: CardType.SpecialRefresh, cost: 5, weight: 12, lv: 2 },
]
/** 功能卡效果类型 */
export enum SpecialEffectType {
DrawHero = 1,
RepeatNextUse = 2,
export enum SpecialRefreshHeroType {
Any = 0,
Melee = 1,
Ranged = 2,
}
/** 功能卡效果参数 */
export interface SpecialCardEffect {
type: SpecialEffectType
drawHeroCount?: number
drawHeroLv?: CardKind
repeatNextUseTimes?: number
}
/** 功能卡完整配置 */
export interface SpecialCardConfig extends CardConfig {
/** 升级功能卡完整配置 */
export interface SpecialUpgradeCardConfig extends CardConfig {
name: string
info: string
effect: SpecialCardEffect
currentLv: number
targetLv: number
}
/** 刷新功能卡完整配置 */
export interface SpecialRefreshCardConfig extends CardConfig {
name: string
info: string
refreshLv: number
refreshHeroType: SpecialRefreshHeroType
}
/** 功能卡定义表 */
export const SpecialCardList: Record<number, SpecialCardConfig> = {
7001: { uuid: 7001,type: CardType.Special,cost: 6,weight: 20,lv: CardKind.LV1,name:"哈哈",info: "抽取4张等级3的英雄",
effect: {type: SpecialEffectType.DrawHero,drawHeroCount: 4,drawHeroLv: CardKind.LV3,},
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
7001: { uuid: 7001,type: CardType.SpecialUpgrade,cost: 6,weight: 16,lv: CardKind.LV1,name:"战术晋升",info: "升级场上随机1个1级英雄到2级",
currentLv: 1, targetLv: 2,
},
7002: { uuid: 7002,type: CardType.Special,cost: 5,weight: 20,lv: CardKind.LV2,name:"哈哈哈", info: "重复使用下一张卡1次",
effect: {type: SpecialEffectType.RepeatNextUse,repeatNextUseTimes: 1,},
7002: { uuid: 7002,type: CardType.SpecialUpgrade,cost: 6,weight: 14,lv: CardKind.LV2,name:"进阶战术",info: "升级场上随机1个2级英雄到3级",
currentLv: 2, targetLv: 3,
},
}
export const SpecialRefreshCardList: Record<number, SpecialRefreshCardConfig> = {
7101: { uuid: 7101,type: CardType.SpecialRefresh,cost: 4,weight: 14,lv: CardKind.LV1,name:"近战征召",info: "刷新卡池,都是近战英雄",
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Melee,
},
7102: { uuid: 7102,type: CardType.SpecialRefresh,cost: 4,weight: 14,lv: CardKind.LV1,name:"远程征召",info: "刷新卡池,都是远程英雄",
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Ranged,
},
7103: { uuid: 7103,type: CardType.SpecialRefresh,cost: 5,weight: 12,lv: CardKind.LV2,name:"精英筛选",info: "刷新卡池都是3级卡池等级英雄",
refreshLv: 3, refreshHeroType: SpecialRefreshHeroType.Any,
},
}
@@ -193,3 +212,33 @@ export const getCardsByLv = (
const others = pickCards(otherPool, 2)
return [...heroes, ...others]
}
export const drawCardsByRule = (
lv: number,
options: {
count?: number
onlyCurrentLv?: boolean
type?: CardType | CardType[]
heroType?: HType
heroLv?: number
} = {}
): CardConfig[] => {
const count = Math.max(0, Math.floor(options.count ?? 4))
const onlyCurrentLv = options.onlyCurrentLv ?? false
let pool = getCardPoolByLv(lv, onlyCurrentLv)
if (options.type !== undefined) {
const typeSet = normalizeTypeFilter(options.type)
pool = pool.filter(card => typeSet.has(card.type))
}
if (options.heroType !== undefined || options.heroLv !== undefined) {
pool = pool.filter(card => {
if (card.type !== CardType.Hero) return false
const hero = HeroInfo[card.uuid]
if (!hero) return false
if (options.heroType !== undefined && hero.type !== options.heroType) return false
if (options.heroLv !== undefined && card.hero_lv !== options.heroLv) return false
return true
})
}
return pickCards(pool, count)
}

View File

@@ -2,7 +2,7 @@ import { mLogger } from "../common/Logger";
import { _decorator, Animation, AnimationClip, EventTouch, Label, Node, NodeEventType, Sprite, SpriteAtlas, Tween, tween, UIOpacity, Vec3, resources } 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 { CardConfig, CardType, SpecialCardList } from "../common/config/CardSet";
import { CardConfig, CardType, SpecialRefreshCardList, SpecialUpgradeCardList } from "../common/config/CardSet";
import { CardUseComp } from "./CardUseComp";
import { HeroInfo } from "../common/config/heroSet";
import { SkillSet } from "../common/config/SkillSet";
@@ -367,10 +367,13 @@ export class CardComp extends CCComp {
this.info_node.getChildByName("ap").getChildByName("val").getComponent(Label).string = `${HeroInfo[this.card_uuid].ap*this.cardData.hero_lv}`;
this.info_node.getChildByName("hp").getChildByName("val").getComponent(Label).string = `${HeroInfo[this.card_uuid].hp*this.cardData.hero_lv}`;
}else{
this.setLabel(this.name_node, `${SpecialCardList[this.card_uuid].name}Lv.${this.cardData.lv}`);
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]
: SpecialRefreshCardList[this.card_uuid];
this.setLabel(this.name_node, `${specialCard?.name || ""}Lv.${this.cardData.lv}`);
this.info_node.active = false;
this.oinfo_node.active = true;
this.oinfo_node.getChildByName("info").getComponent(Label).string = `${SpecialCardList[this.card_uuid].info}`;
this.oinfo_node.getChildByName("info").getComponent(Label).string = `${specialCard?.info || ""}`;
}
this.setLabel(this.cost_node, `${this.card_cost}`);

View File

@@ -52,7 +52,9 @@ export class CardUseComp extends CCComp {
return "hero";
case CardType.Skill:
return "skill";
case CardType.Special:
case CardType.SpecialUpgrade:
case CardType.SpecialRefresh:
oops.message.dispatchEvent(GameEvent.UseSpecialCard, used);
return "special";
default:
return "unknown";

View File

@@ -3,12 +3,15 @@ import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, SpriteAtla
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, CardsUpSet, getCardsByLv } from "../common/config/CardSet";
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 { CardComp } from "./CardComp";
import { oops } from "db://oops-framework/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { HInfoComp } from "./HInfoComp";
import { smc } from "../common/SingletonModuleComp";
import { HeroInfo, HType } from "../common/config/heroSet";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FacSet } from "../common/config/GameSet";
const { ccclass, property } = _decorator;
@@ -152,6 +155,7 @@ export class MissionCardComp extends CCComp {
oops.message.on(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.on(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.on(GameEvent.UseHeroCard, this.onUseHeroCard, this);
oops.message.on(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
/** 按钮事件:抽卡与卡池升级 */
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
@@ -193,6 +197,7 @@ export class MissionCardComp extends CCComp {
oops.message.off(GameEvent.MasterCalled, this.onMasterCalled, this);
oops.message.off(GameEvent.HeroDead, this.onHeroDead, this);
oops.message.off(GameEvent.UseHeroCard, this.onUseHeroCard, this);
oops.message.off(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
this.cards_chou?.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_chou?.off(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_chou?.off(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
@@ -231,6 +236,30 @@ export class MissionCardComp extends CCComp {
}
}
private onUseSpecialCard(event: string, args: any) {
const payload = args ?? event;
const uuid = Number(payload?.uuid ?? 0);
const type = Number(payload?.type ?? 0) as CardType;
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}级的英雄`);
} else if (type === CardType.SpecialRefresh) {
const card = SpecialRefreshCardList[uuid];
if (!card) return;
success = this.tryRefreshHeroCardsByEffect(card.refreshHeroType, card.refreshLv);
if (!success) oops.gui.toast("当前卡池无符合条件的英雄卡");
}
mLogger.log(this.debugMode, "MissionCardComp", "use special card", {
uuid,
type,
success
});
}
private onDrawTouchStart() {
this.playButtonPressAnim(this.cards_chou);
}
@@ -365,6 +394,31 @@ export class MissionCardComp extends CCComp {
return filled;
}
private tryRefreshHeroCards(heroType?: HType, heroLv?: number): boolean {
const cards = drawCardsByRule(this.poolLv, {
count: 4,
type: CardType.Hero,
heroType,
heroLv
});
if (cards.length <= 0) return false;
this.layoutCardSlots();
this.dispatchCardsToSlots(cards.slice(0, 4));
return true;
}
private tryRefreshHeroCardsByEffect(refreshHeroType: SpecialRefreshHeroType, refreshLv: number): boolean {
const heroType = this.resolveRefreshHeroType(refreshHeroType);
const heroLv = refreshLv > 0 ? refreshLv : undefined;
return this.tryRefreshHeroCards(heroType, heroLv);
}
private resolveRefreshHeroType(refreshHeroType: SpecialRefreshHeroType): HType | undefined {
if (refreshHeroType === SpecialRefreshHeroType.Melee) return HType.Melee;
if (refreshHeroType === SpecialRefreshHeroType.Ranged) return HType.Long;
return undefined;
}
/** 全量分发给4槽每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */
private dispatchCardsToSlots(cards: CardConfig[]) {
for (let i = 0; i < this.cardComps.length; i++) {
@@ -636,6 +690,56 @@ export class MissionCardComp extends CCComp {
return count;
}
private queryAliveHeroActors(): Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> {
const actors: Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> = [];
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp);
if (!model) return;
if (model.fac !== FacSet.HERO) return;
if (model.is_dead) return;
const view = entity.get(HeroViewComp);
actors.push({ model, view });
});
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);
if (candidates.length === 0) return false;
const target = candidates[Math.floor(Math.random() * candidates.length)];
this.applyHeroLevel(target.model, toLv);
if (target.view) {
target.view.palayBuff("buff_lvup");
}
return true;
}
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 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.hp = Math.max(1, Math.floor(model.hp_max * Math.max(0, Math.min(1, hpRate))));
model.skills = {};
for (const key in hero.skills) {
const skill = hero.skills[key];
if (!skill) continue;
model.skills[skill.uuid] = { ...skill, lv: Math.max(0, skill.lv + nextLv - 2), ccd: 0 };
}
model.updateSkillDistanceCache();
model.dirty_hp = true;
oops.message.dispatchEvent(GameEvent.HeroLvUp, {
uuid: model.hero_uuid,
lv: nextLv
});
}
private updateHeroNumUI(animate: boolean, isIncrease: boolean) {
this.syncMissionHeroData();
if (!animate || !isIncrease) return;