refactor(card): 升级动态升级卡的英雄绑定逻辑
将动态升级卡从绑定英雄UUID改为绑定实体EID,适配ECS实体管理模式: 1. 更新CardConfig定义,替换target_hero_uuid为target_hero_eid 2. 新增EID反查UUID的工具方法,适配渲染和交互逻辑 3. 修改抽卡、卡牌点击和使用的全流程逻辑 4. 优化升级卡生成和匹配的稳定性
This commit is contained in:
@@ -110,11 +110,11 @@ export interface CardConfig {
|
||||
trigger_limit?: number;
|
||||
|
||||
/**
|
||||
* 动态升级卡专属:目标英雄 UUID。
|
||||
* 动态升级卡专属:目标英雄实体 eid(运行时唯一)。
|
||||
* 仅当 type === CardType.SpecialUpgrade 且该卡由 buildDrawCards 动态生成时使用。
|
||||
* 使用该卡时精确升级场上对应 UUID 的英雄。
|
||||
* 使用该卡时精确升级场上对应 eid 的英雄实体。
|
||||
*/
|
||||
target_hero_uuid?: number;
|
||||
target_hero_eid?: number;
|
||||
}
|
||||
|
||||
/** 升级卡折扣表(已废弃,保留以兼容历史引用) */
|
||||
@@ -274,7 +274,7 @@ export enum SpecialRefreshHeroType {
|
||||
export interface SpecialUpgradeCardConfig extends CardConfig {
|
||||
name: string
|
||||
info: string
|
||||
/** 模板字段:currentLv=0 表示动态卡(实际值由 target_hero_uuid 对应的英雄决定) */
|
||||
/** 模板字段:currentLv=0 表示动态卡(实际值由 target_hero_eid 对应的英雄决定) */
|
||||
currentLv: number
|
||||
/** 模板字段:targetLv=0 表示"+1 级"(实际目标等级 = 当前等级 + 1) */
|
||||
targetLv: number
|
||||
@@ -292,7 +292,7 @@ export interface SpecialRefreshCardConfig extends CardConfig {
|
||||
* 升级卡模板。
|
||||
* 仅有 7001 一条记录,作为动态升级卡的基础配置(cost/weight/name/info)。
|
||||
* 实际使用时由 MissionCardComp.buildDrawCards 根据场上英雄动态生成 CardConfig,
|
||||
* 通过 target_hero_uuid 字段绑定具体英雄。
|
||||
* 通过 target_hero_eid 字段绑定具体英雄实体。
|
||||
*/
|
||||
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
|
||||
7001: {
|
||||
|
||||
@@ -526,9 +526,12 @@ export class CardComp extends CCComp {
|
||||
this.showCallBtn();
|
||||
oops.message.dispatchEvent(GameEvent.CardSelected, this);
|
||||
// 英雄卡 / 升级卡打开信息面板(点击替代长按)
|
||||
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_uuid;
|
||||
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData?.target_hero_eid;
|
||||
if (this.card_type === CardType.Hero || isUpgradeCard) {
|
||||
const heroUuid = isUpgradeCard ? (this.cardData!.target_hero_uuid as number) : this.card_uuid;
|
||||
// 升级卡通过 eid 反查实体得到 hero_uuid;若实体已不存在则降级为本卡 uuid
|
||||
const heroUuid = isUpgradeCard
|
||||
? (this.queryHeroUuidByEid(this.cardData!.target_hero_eid as number) ?? this.card_uuid)
|
||||
: this.card_uuid;
|
||||
const heroLv = Math.max(1, this.cardData?.hero_lv ?? 1);
|
||||
const poolLv = Math.max(1, this.cardData?.base_pool_lv ?? this.cardData?.pool_lv ?? 1);
|
||||
oops.gui.remove(UIID.HInfo);
|
||||
@@ -536,6 +539,20 @@ export class CardComp extends CCComp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 eid 反查场上英雄实体的 hero_uuid。
|
||||
* 用于动态升级卡渲染:升级卡绑定的是 eid,但显示需要 HeroInfo[uuid]。
|
||||
* @returns 找到则返回 hero_uuid,找不到(实体已销毁等)返回 null
|
||||
*/
|
||||
private queryHeroUuidByEid(eid: number): number | null {
|
||||
if (!eid) return null;
|
||||
const entity = ecs.getEntityByEid(eid);
|
||||
if (!entity) return null;
|
||||
const model = entity.get(HeroAttrsComp);
|
||||
if (!model) return null;
|
||||
return model.hero_uuid ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他卡牌被点击时联动隐藏本卡 call_btn。
|
||||
* @param event 事件名(ListenerFunc 约定的第一个参数)
|
||||
@@ -639,11 +656,14 @@ export class CardComp extends CCComp {
|
||||
|
||||
this.node.setPosition(this.restPosition.x, this.restPosition.y, this.restPosition.z);
|
||||
|
||||
// 渲染参数:动态升级卡(SpecialUpgrade + target_hero_uuid)按英雄卡样式渲染,
|
||||
// 使用 target_hero_uuid 对应的英雄信息(名称/AP/HP/动画),等级显示为目标等级。
|
||||
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData.target_hero_uuid;
|
||||
// 渲染参数:动态升级卡(SpecialUpgrade + target_hero_eid)按英雄卡样式渲染,
|
||||
// 通过 target_hero_eid 反查实体得到 hero_uuid,再用 HeroInfo 渲染名称/AP/HP/动画。
|
||||
const isUpgradeCard = this.card_type === CardType.SpecialUpgrade && !!this.cardData.target_hero_eid;
|
||||
const renderType: CardType = isUpgradeCard ? CardType.Hero : this.card_type;
|
||||
const renderUuid: number = isUpgradeCard ? (this.cardData.target_hero_uuid as number) : this.card_uuid;
|
||||
// 升级卡通过 eid 反查实体得到 hero_uuid;若实体已不存在则降级为本卡 uuid
|
||||
const renderUuid: number = isUpgradeCard
|
||||
? (this.queryHeroUuidByEid(this.cardData.target_hero_eid as number) ?? this.card_uuid)
|
||||
: this.card_uuid;
|
||||
const renderKind: CKind = isUpgradeCard ? CKind.Hero : this.cardData.kind;
|
||||
|
||||
// ---- 卡牌种类标识(近战 / 远程 / 辅助等) ----
|
||||
|
||||
@@ -246,9 +246,10 @@ 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;
|
||||
// 动态升级卡:显示对应英雄名 + "升级"后缀
|
||||
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) {
|
||||
const targetHero = HeroInfo[this.cardData.target_hero_uuid];
|
||||
// 动态升级卡:显示对应英雄名 + "升级"后缀(注意:升级卡绑定的是 target_hero_eid 而非 uuid,
|
||||
// 此轻量列表场景通常不展示升级卡;为保持渲染健壮性,回退到本卡 uuid 查 HeroInfo)
|
||||
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_eid) {
|
||||
const targetHero = HeroInfo[this.card_uuid];
|
||||
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
|
||||
} else {
|
||||
const specialCard = this.card_type === CardType.SpecialUpgrade
|
||||
|
||||
@@ -540,8 +540,8 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 使用特殊卡事件回调:
|
||||
* - SpecialUpgrade:按卡牌携带的 target_hero_uuid 精确升级场上对应英雄。
|
||||
* target_hero_uuid 缺失时回退为随机升级一个可升级的英雄。
|
||||
* - SpecialUpgrade:按卡牌携带的 target_hero_eid 精确升级场上对应英雄实体。
|
||||
* target_hero_eid 缺失或实体不存在时升级失败。
|
||||
* - SpecialRefresh:按英雄类型重新抽取英雄卡。
|
||||
*/
|
||||
private onUseSpecialCard(event: string, args: any) {
|
||||
@@ -553,8 +553,8 @@ export class MissionCardComp extends CCComp {
|
||||
if (type === CardType.SpecialUpgrade) {
|
||||
const template = SpecialUpgradeCardList[uuid];
|
||||
if (!template) return;
|
||||
const targetHeroUuid = Number(payload?.target_hero_uuid ?? 0);
|
||||
success = this.tryUpgradeHeroByUuid(targetHeroUuid);
|
||||
const targetHeroEid = Number(payload?.target_hero_eid ?? 0);
|
||||
success = this.tryUpgradeHeroByEid(targetHeroEid);
|
||||
if (!success) {
|
||||
oops.gui.toast(`场上没有可升级的英雄`);
|
||||
}
|
||||
@@ -735,7 +735,7 @@ export class MissionCardComp extends CCComp {
|
||||
*
|
||||
* 抽卡规则:
|
||||
* 1. **必出规则**:当场上有可升级英雄时,每次刷新必出 1 张升级卡,并放在返回数组首位(对应卡池最左侧槽位)。
|
||||
* 多次刷新时,按场上英雄 target_hero_uuid 升序循环切换升级卡(upgradeRotationIndex)。
|
||||
* 多次刷新时,按场上英雄 target_hero_eid 升序循环切换升级卡(upgradeRotationIndex)。
|
||||
* 2. 其余 2 张从混合池(剩余升级卡 + 基础英雄卡)按权重抽取。
|
||||
*
|
||||
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,全部出升级卡(若全部已满级则降级回混合池)。
|
||||
@@ -772,15 +772,15 @@ export class MissionCardComp extends CCComp {
|
||||
this.upgradeRotationIndex = (this.upgradeRotationIndex + 1) % upgradeCards.length;
|
||||
const mandatory = upgradeCards[idx];
|
||||
result.push(mandatory);
|
||||
usedUpgradeTargets.add(mandatory.target_hero_uuid ?? 0);
|
||||
usedUpgradeTargets.add(mandatory.target_hero_eid ?? 0);
|
||||
}
|
||||
|
||||
// 剩余张数从混合池抽取,排除已选升级卡 target_hero_uuid
|
||||
// 剩余张数从混合池抽取,排除已选升级卡 target_hero_eid
|
||||
const remainingCount = 3 - result.length;
|
||||
if (remainingCount > 0) {
|
||||
const remainingPool = mixedPool.filter(c => {
|
||||
if (c.type === CardType.SpecialUpgrade && c.target_hero_uuid) {
|
||||
return !usedUpgradeTargets.has(c.target_hero_uuid);
|
||||
if (c.type === CardType.SpecialUpgrade && c.target_hero_eid) {
|
||||
return !usedUpgradeTargets.has(c.target_hero_eid);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -798,7 +798,7 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
/**
|
||||
* 从混合池中按权重抽取 n 张卡。
|
||||
* 升级卡之间强制 unique(同一个 target_hero_uuid 只能出现一次)。
|
||||
* 升级卡之间强制 unique(同一个 target_hero_eid 只能出现一次)。
|
||||
*/
|
||||
private pickMixedCards(pool: CardConfig[], count: number, upgradeCards: CardConfig[]): CardConfig[] {
|
||||
if (pool.length === 0 || count <= 0) return [];
|
||||
@@ -807,8 +807,8 @@ export class MissionCardComp extends CCComp {
|
||||
|
||||
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);
|
||||
if (c.type === CardType.SpecialUpgrade && c.target_hero_eid) {
|
||||
return !usedUpgradeTargets.has(c.target_hero_eid);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -816,8 +816,8 @@ export class MissionCardComp extends CCComp {
|
||||
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);
|
||||
if (pick.type === CardType.SpecialUpgrade && pick.target_hero_eid) {
|
||||
usedUpgradeTargets.add(pick.target_hero_eid);
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
@@ -836,9 +836,9 @@ export class MissionCardComp extends CCComp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描场上存活英雄,为每个 UUID 至多生成一张升级卡。
|
||||
* 扫描场上存活英雄,为每个英雄实体生成一张升级卡(按 eid 绑定)。
|
||||
* - 已达 HERO_MAX_LV 的英雄不出卡。
|
||||
* - 同 UUID 多个英雄只生成一张(取等级最低的那个作为升级目标)。
|
||||
* - 同 eid 只生成一张(eid 本就唯一,不会重复)。
|
||||
* - cost = 模板 cost + (当前等级 - 1) * BASE_COST,等级越高升级越贵。
|
||||
*/
|
||||
private buildHeroUpgradeCards(): CardConfig[] {
|
||||
@@ -848,31 +848,22 @@ export class MissionCardComp extends CCComp {
|
||||
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; // 已达上限不再出升级卡
|
||||
for (const actor of actors) {
|
||||
const eid = actor.eid;
|
||||
const lv = actor.model.lv;
|
||||
if (lv >= FightSet.HERO_MAX_LV) continue; // 已达上限不再出升级卡
|
||||
const dynamicCost = template.cost + (lv - 1) * FightSet.BASE_COST;
|
||||
result.push({
|
||||
...template,
|
||||
cost: dynamicCost,
|
||||
weight: template.weight,
|
||||
target_hero_uuid: uuid,
|
||||
target_hero_eid: eid,
|
||||
hero_lv: lv + 1,
|
||||
});
|
||||
});
|
||||
// 按 target_hero_uuid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询)
|
||||
result.sort((a, b) => (a.target_hero_uuid ?? 0) - (b.target_hero_uuid ?? 0));
|
||||
}
|
||||
// 按 target_hero_eid 升序排序,保证返回顺序稳定(便于 buildDrawCards 按顺序轮询)
|
||||
result.sort((a, b) => (a.target_hero_eid ?? 0) - (b.target_hero_eid ?? 0));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1077,39 +1068,34 @@ export class MissionCardComp extends CCComp {
|
||||
return count;
|
||||
}
|
||||
|
||||
private queryAliveHeroActors(): Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> {
|
||||
const actors: Array<{ model: HeroAttrsComp, view: HeroViewComp | null }> = [];
|
||||
private queryAliveHeroActors(): Array<{ eid: number, model: HeroAttrsComp, view: HeroViewComp | null }> {
|
||||
const actors: Array<{ eid: number, 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 });
|
||||
actors.push({ eid: entity.eid, model, view });
|
||||
});
|
||||
return actors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按英雄 UUID 精确升级一个存活英雄。
|
||||
* 若同 UUID 多个英雄,取等级最低且未达上限的;若都达上限则失败。
|
||||
* 按 eid 精确升级场上对应英雄实体。
|
||||
*
|
||||
* @param heroUuid 要升级的英雄 UUID
|
||||
* @param heroEid 要升级的英雄实体 eid
|
||||
* @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;
|
||||
// 取等级最低的(先升级低等级英雄)
|
||||
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");
|
||||
private tryUpgradeHeroByEid(heroEid: number): boolean {
|
||||
if (!heroEid) return false;
|
||||
const actor = this.queryAliveHeroActors().find(item => item.eid === heroEid);
|
||||
if (!actor) return false;
|
||||
if (actor.model.lv >= FightSet.HERO_MAX_LV) return false;
|
||||
const nextLv = Math.min(FightSet.HERO_MAX_LV, actor.model.lv + 1);
|
||||
this.applyHeroLevel(actor.model, nextLv);
|
||||
if (actor.view) {
|
||||
actor.view.playBuff("buff_lvup");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user