refactor: 重构英雄养成与卡牌系统,移除旧合成机制

1.  移除三合一英雄合成与链式合成逻辑,统一使用升级卡进行英雄等级提升
2.  重构卡池系统:移除卡池等级机制,所有英雄卡牌统一为LV1
3.  重构升级逻辑:改为按UUID精确升级场上英雄,动态生成对应升级卡
4.  更新配置常量:拆分并重构成长倍率、英雄上限等战斗配置
5.  简化抽卡逻辑:不再按卡池等级分发卡牌,改为动态混合基础卡与升级卡
6.  清理废弃代码:移除卡池升级相关的UI、逻辑与配置
This commit is contained in:
pan
2026-07-20 17:26:50 +08:00
parent f0952ef82c
commit 15ab2f7b0f
10 changed files with 729 additions and 1059 deletions

View File

@@ -47,28 +47,32 @@ export enum CardSkillType {
HeroCall = 6, // 场上己方英雄召唤上场时触发
}
/** 卡池等级定义 */
export enum CardLV {
LV1 = 1,
LV2 = 2,
LV3 = 3,
LV4 = 4,
LV5 = 5,
}
/**
* 卡牌技能触发类型
* - 命名对齐英雄侧 SkillTriggerType便于跨模块认知统一
* - 枚举值从 1 开始,避免 0 的 falsy 坑if (trigger_type) 判断出错)
*/
export enum CardTriggerType {
Instant = 1, // 即时触发:使用后立即生效一次
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
Instant = 1, // 即时触发:使用后立即生效一次
Interval = 2, // 定时循环:战斗中按 t_inv 间隔重复触发
Field = 3, // 驻场光环:被动生效(仅显式分类,仍由 field 字段驱动)
FightStart = 4, // 战斗开始时触发
FightEnd = 5, // 战斗结束时触发(每波结束)
HeroDead = 6, // 场上己方英雄死亡时触发
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
FightEnd = 5, // 战斗结束时触发(每波结束)
HeroDead = 6, // 场上己方英雄死亡时触发
HeroCall = 7, // 英雄上场时触发(主角召唤 + 技能召唤 + 复活)
}
/**
* 卡池等级占位枚举(已废弃分层语义)。
* 新机制下所有英雄卡均属 LV1不再使用卡池升级。
* 保留枚举仅为兼容历史代码引用。
*/
export enum CardLV {
LV1 = 1,
LV2 = 2,
LV3 = 3,
LV4 = 4,
LV5 = 5,
}
/** 通用卡牌配置 */
@@ -104,7 +108,16 @@ export interface CardConfig {
* 注意:与 t_times 语义不同——t_times 控制每波内 Interval 的次数
*/
trigger_limit?: number;
/**
* 动态升级卡专属:目标英雄 UUID。
* 仅当 type === CardType.SpecialUpgrade 且该卡由 buildDrawCards 动态生成时使用。
* 使用该卡时精确升级场上对应 UUID 的英雄。
*/
target_hero_uuid?: number;
}
/** 升级卡折扣表(已废弃,保留以兼容历史引用) */
export const CardsUpSet: Record<number, number> = {
1: 50,
2: 100,
@@ -113,70 +126,43 @@ export const CardsUpSet: Record<number, number> = {
5: 250,
}
/** 卡池升级每波减免金额 */
/** 卡池升级每波减免金额(已废弃,保留以兼容历史引用) */
export const CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE = 10
/** 卡池默认初始等级 */
/** 卡池默认初始等级(已废弃,所有卡牌统一 LV1 */
export const CARD_POOL_INIT_LEVEL = CardLV.LV1
/** 卡池等级上限(统一由 FightSet.MAX_CARD_POOL_LEVEL 设定,保持单一数据源 */
export const CARD_POOL_MAX_LEVEL = FightSet.MAX_CARD_POOL_LEVEL as unknown as CardLV
/** 英雄最高等级限制 */
/** 卡池等级上限(已废弃,所有卡牌统一 LV1 */
export const CARD_POOL_MAX_LEVEL = CardLV.LV1
/** 英雄最高等级限制(已废弃,统一由 FightSet.HERO_MAX_LV 控制) */
export const CARD_HERO_MAX_LEVEL = 1
/** 基础卡池(英雄、技能、功能) */
/** 基础卡池(英雄、技能、功能) */
export const CardPoolList: CardConfig[] = [];
// 动态生成英雄卡池
// 动态生成英雄卡池:所有英雄统一生成 lv1 卡牌
HeroList.forEach(uuid => {
const hero = HeroInfo[uuid];
if (!hero) return;
const basePoolLv = hero.pool_lv || 1;
const baseHeroLv = hero.lv || 1;
const baseCost = FightSet.BASE_COST;
const baseWeight = 25;
// 生成从 basePoolLv 到 CARD_POOL_MAX_LEVEL 的卡牌
for (let pLv = basePoolLv; pLv <= CARD_POOL_MAX_LEVEL; pLv++) {
const offset = pLv - basePoolLv;
const targetHeroLv = baseHeroLv + offset;
// 【修改开始】永远只刷 lv1 等级的英雄卡牌,不再出现某英雄的 lv2 等级卡牌
// 设置为 true 则开启该限制。保留原有代码逻辑以便后续有变直接引用。
const ONLY_SPAWN_LV1_HERO = true;
if (ONLY_SPAWN_LV1_HERO && targetHeroLv > 1) {
break;
}
// 【修改结束】
// 英雄的最高等级 是MERGE_MAX-1
if (targetHeroLv > FightSet.MERGE_MAX - 1) {
break;
}
// cost = baseCost * 3^(lv-1): Lv1=5, Lv2=15, Lv3=45
let cost = baseCost;
if (targetHeroLv > 1) {
cost = baseCost * Math.pow(FightSet.MERGE_NEED, targetHeroLv - 1);
}
CardPoolList.push({
uuid: hero.uuid,
type: CardType.Hero,
cost: cost,
weight: baseWeight,
pool_lv: pLv as CardLV,
kind: CKind.Hero,
hero_lv: targetHeroLv,
base_pool_lv: basePoolLv
});
}
CardPoolList.push({
uuid: hero.uuid,
type: CardType.Hero,
cost: baseCost,
weight: baseWeight,
pool_lv: CardLV.LV1,
kind: CKind.Hero,
hero_lv: 1,
base_pool_lv: 1,
});
});
// 添加非英雄卡牌 (技能、功能卡)
// 体系wave 由 SKILL_CARD_WAVES 统一配置每档强度递增Field 靠 field uuid 区分数值Interval 靠 overrides 覆写)
// wave→pool_lv 映射由 SKILL_CARD_WAVES 索引+1 自动生成wave 1→lv1, wave 5→lv2, wave 8→lv3
const waveToPoolLv: Record<number, number> = {};
SKILL_CARD_WAVES.forEach((w, i) => { waveToPoolLv[w] = i + 1; });
// wave→card_lv 映射由 SKILL_CARD_WAVES 索引+1 自动生成wave 1→lv1, wave 5→lv2, wave 8→lv3
const waveToCardLv: Record<number, number> = {};
SKILL_CARD_WAVES.forEach((w, i) => { waveToCardLv[w] = i + 1; });
const SkillCardData: any[] = [
// ==================== wave 1 档(基础强度) ====================
@@ -214,7 +200,7 @@ const SkillCardData: any[] = [
{ uuid: 8265, skill: 6205, wave: SKILL_CARD_WAVES[1], name: "风墙+", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8266, skill: 6206, wave: SKILL_CARD_WAVES[1], name: "陨石术+", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_inv: 5, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 150 } },
{ uuid: 8760, skill: 0, wave: SKILL_CARD_WAVES[1], name: "金币收益+", info: "每回合金币收益+2", is_inst: false, keep_waves: -1, field: [7210], trigger_type: CardTriggerType.Field },
{ uuid: 8761, skill: 0, wave: SKILL_CARD_WAVES[1], name: "购买优惠+", info: "购买卡牌费用-2金币", is_inst: false, keep_waves: -1, field: [7212], trigger_type: CardTriggerType.Field },
{ uuid: 8762, skill: 0, wave: SKILL_CARD_WAVES[1], name: "刷新优惠", info: "刷新卡牌费用-1金币", is_inst: false, keep_waves: -1, field: [7213], trigger_type: CardTriggerType.Field },
@@ -239,7 +225,7 @@ const SkillCardData: any[] = [
{ uuid: 8365, skill: 6205, wave: SKILL_CARD_WAVES[2], name: "风墙++", info: "召唤风墙困住敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8366, skill: 6206, wave: SKILL_CARD_WAVES[2], name: "陨石术++", info: "召唤陨石范围攻击敌人,有概率击晕", is_inst: false, t_inv: 4, keep_waves: -1, trigger_type: CardTriggerType.Interval, overrides: { ap: 250 } },
{ uuid: 8810, skill: 0, wave: SKILL_CARD_WAVES[2], name: "金币收益++", info: "每回合金币收益+3", is_inst: false, keep_waves: -1, field: [7410], trigger_type: CardTriggerType.Field },
{ uuid: 8811, skill: 0, wave: SKILL_CARD_WAVES[2], name: "召唤强化++", info: "召唤触发技能次数+1", is_inst: false, keep_waves: -1, field: [7014], trigger_type: CardTriggerType.Field },
@@ -252,27 +238,28 @@ const SkillCardData: any[] = [
];
SkillCardData.forEach(data => {
const cardLv = waveToCardLv[data.wave] ?? 1;
CardPoolList.push({
uuid: data.uuid,
skill: data.skill || undefined,
type: CardType.Skill,
cost: 0,
weight: 10,
pool_lv: waveToPoolLv[data.wave] as CardLV,
pool_lv: CardLV.LV1,
wave: data.wave,
kind: CKind.Skill,
card_lv: waveToPoolLv[data.wave], // wave 1→1, 5→2, 8→3
card_lv: cardLv,
name: data.name,
info: data.info,
icon: data.icon, // 【新增】透传自定义图标ID优先级最高
icon: data.icon,
is_inst: data.is_inst,
t_times: data.t_times || (data.is_inst ? 1 : 999),
t_inv: data.t_inv || 0,
keep_waves: data.keep_waves,
field: data.field,
overrides: data.overrides, // 【修复】原遗漏
trigger_type: data.trigger_type, // 【新增】显式触发类型
trigger_limit: data.trigger_limit, // 【新增】事件型触发次数上限
overrides: data.overrides,
trigger_type: data.trigger_type,
trigger_limit: data.trigger_limit,
});
});
@@ -283,11 +270,13 @@ export enum SpecialRefreshHeroType {
Ranged = 2,
}
/** 升级功能卡完整配置 */
/** 升级功能卡完整配置(作为动态升级卡的基础模板) */
export interface SpecialUpgradeCardConfig extends CardConfig {
name: string
info: string
/** 模板字段currentLv=0 表示动态卡(实际值由 target_hero_uuid 对应的英雄决定) */
currentLv: number
/** 模板字段targetLv=0 表示"+1 级"(实际目标等级 = 当前等级 + 1 */
targetLv: number
}
@@ -299,18 +288,16 @@ export interface SpecialRefreshCardConfig extends CardConfig {
refreshHeroType: SpecialRefreshHeroType
}
/** 功能卡定义表 */
/**
* 升级卡模板。
* 仅有 7001 一条记录作为动态升级卡的基础配置cost/weight/name/info
* 实际使用时由 MissionCardComp.buildDrawCards 根据场上英雄动态生成 CardConfig
* 通过 target_hero_uuid 字段绑定具体英雄。
*/
export const SpecialUpgradeCardList: Record<number, SpecialUpgradeCardConfig> = {
7001: {
uuid: 7001, type: CardType.SpecialUpgrade, cost: 10, weight: 16, pool_lv: CardLV.LV1, kind: CKind.Card, name: t("scard_name_7001"), info: t("scard_info_7001"),
currentLv: 1, targetLv: 2,
},
7002: {
uuid: 7002, type: CardType.SpecialUpgrade, cost: 28, weight: 14, pool_lv: CardLV.LV2, kind: CKind.Card, name: t("scard_name_7002"), info: t("scard_info_7002"),
currentLv: 2, targetLv: 3,
uuid: 7001, type: CardType.SpecialUpgrade, cost: 10, weight: 18, pool_lv: CardLV.LV1, kind: CKind.Card, name: t("scard_name_7001"), info: t("scard_info_7001"),
currentLv: 0, targetLv: 0,
},
}
@@ -324,19 +311,15 @@ export const SpecialRefreshCardList: Record<number, SpecialRefreshCardConfig> =
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Ranged,
},
7103: {
uuid: 7103, type: CardType.SpecialRefresh, cost: 4, weight: 12, pool_lv: CardLV.LV2, kind: CKind.Card, name: t("scard_name_7103"), info: t("scard_info_7103"),
refreshLv: 3, refreshHeroType: SpecialRefreshHeroType.Any,
uuid: 7103, type: CardType.SpecialRefresh, cost: 4, weight: 12, pool_lv: CardLV.LV1, kind: CKind.Card, name: t("scard_name_7103"), info: t("scard_info_7103"),
refreshLv: 0, refreshHeroType: SpecialRefreshHeroType.Any,
},
}
/** 规范等级到合法区间 [LV1, LV6] */
/** 规范等级到合法区间(保留以兼容历史调用,新机制下统一返回 LV1 */
const clampCardLv = (lv: number): CardLV => {
const value = Math.floor(lv)
if (value < CARD_POOL_INIT_LEVEL) return CARD_POOL_INIT_LEVEL
if (value > CARD_POOL_MAX_LEVEL) return CARD_POOL_MAX_LEVEL
return value as CardLV
return CardLV.LV1;
}
/** 单次按权重抽取一张卡 */
@@ -368,13 +351,13 @@ const pickCards = (cards: CardConfig[], count: number, unique: boolean = false):
return selected
}
/** 获取指定等级可出现的基础卡池 */
/**
* 获取基础卡池(已废弃 lv 参数,新机制下返回完整卡池)。
* @param lv 历史遗留参数,不再生效
* @param onlyCurrentLv 历史遗留参数,不再生效
*/
export const getCardPoolByLv = (lv: number, onlyCurrentLv: boolean = false): CardConfig[] => {
const cardLv = clampCardLv(lv)
if (onlyCurrentLv) {
return CardPoolList.filter(card => card.pool_lv === cardLv)
}
return CardPoolList.filter(card => card.pool_lv <= cardLv)
return CardPoolList;
}
const normalizeTypeFilter = (type: CardType | CardType[]): Set<CardType> => {
@@ -382,7 +365,12 @@ const normalizeTypeFilter = (type: CardType | CardType[]): Set<CardType> => {
return new Set<CardType>(list)
}
/** 常规发牌:前 3 英雄 + 后 1 其他;支持按类型和等级模式过滤 */
/**
* 常规发牌:前 3 英雄 + 后 1 其他;支持按类型过滤。
* @param lv 历史遗留参数,不再生效
* @param type 限定卡牌类型
* @param onlyCurrentLv 历史遗留参数,不再生效
*/
export const getCardsByLv = (
lv: number,
type?: CardType | CardType[],
@@ -401,6 +389,9 @@ export const getCardsByLv = (
return [...heroes, ...others]
}
/**
* 通用按规则抽卡(已废弃 lv/targetPoolLv/onlyCurrentLv 参数,仅为兼容保留)。
*/
export const drawCardsByRule = (
lv: number,
options: {
@@ -415,30 +406,11 @@ export const drawCardsByRule = (
} = {}
): CardConfig[] => {
const count = Math.max(0, Math.floor(options.count ?? 4))
const onlyCurrentLv = options.onlyCurrentLv ?? false
let pool = getCardPoolByLv(lv, onlyCurrentLv)
let pool = getCardPoolByLv(lv, options.onlyCurrentLv ?? false)
if (options.type !== undefined) {
const typeSet = normalizeTypeFilter(options.type)
pool = pool.filter(card => typeSet.has(card.type))
}
if (options.targetPoolLv !== undefined) {
// 如果指定了目标卡池等级,则强制从所有配置中筛选该等级的卡牌,无视当前的卡池等级限制
pool = CardPoolList.filter(card => card.pool_lv === options.targetPoolLv)
if (options.type !== undefined) {
const typeSet = normalizeTypeFilter(options.type)
pool = pool.filter(card => typeSet.has(card.type))
}
// 如果强制筛选后池子为空(比如开启了 ONLY_SPAWN_LV1_HERO 导致没有高等级英雄卡),
// 且需要抽取英雄,则兜底降级回 pool_lv 为 1 的卡池,保证系统不会卡死
if (pool.length === 0) {
pool = CardPoolList.filter(card => card.pool_lv === 1);
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
@@ -454,7 +426,6 @@ export const drawCardsByRule = (
if (options.wave !== undefined) {
pool = pool.filter(card => {
if (card.type === CardType.Skill) {
// 只有 wave 值严格等于当前 wave 的技能卡才会留在池中
return card.wave === options.wave;
}
return true;

View File

@@ -27,9 +27,11 @@ export enum FightSet {
CRIT_DAMAGE = 50,//暴击伤害
MORE_RC = 10,//更多次数 广告获取的次数
HEARTPOS = -320,//基地位置
HERO_MAX_NUM = 6,//英雄最大数量
MERGE_MAX = 2, //英雄最大等级
MERGE_NEED = 3, //英雄升级需要的英雄数
HERO_MAX_NUM = 3,//英雄最大数量
/** 英雄可升级到的最高等级(通过升级卡提升) */
HERO_MAX_LV = 6,
/** 英雄属性随等级成长的倍率ap/hp = base * MULTIPLIER^(lv-1) */
HERO_LV_MULTIPLIER = 3,
// BACK_RANG=30,//后退范围
BACK_RANG = 30,//后退范围
FiIGHT_TIME = 30,//战斗时间
@@ -45,20 +47,8 @@ export enum FightSet {
REFRESH_COST = 2,
BASE_COST = 5,
INIT_COIN = 7, // 初始金币数
// 刷新成本
/** 卡池等级上限(对应 CardLV 最大值) */
MAX_CARD_POOL_LEVEL = 5,
}
/**
* 卡池升级波次配置(单一数据源)。
* 索引 i 对应目标等级 = i + 2
* - 第 1 个波次 → 升至 LV2
* - 第 2 个波次 → 升至 LV3
* - 依此类推,上限为 FightSet.MAX_CARD_POOL_LEVEL
*/
export const CARD_POOL_UPGRADE_WAVES: number[] = [4, 7, 10, 13];
/**
* 技能卡牌出现的波次配置(单一数据源)。
* 数组索引 i 对应卡牌档位 card_lv = i + 1

View File

@@ -3,37 +3,37 @@ import { BoxSet, FacSet } from "./GameSet"
import { SkillOverrides, TGroup } from "./SkillSet"
export enum HType {
Melee = 0,
Mid = 1,
Long = 2,
Melee = 0,
Mid = 1,
Long = 2,
}
export const HTypeName ={
0:"近战",
1:"中程",
2:"远程",
export const HTypeName = {
0: "近战",
1: "中程",
2: "远程",
}
export enum MonType {
Melee = 0,
Heavy = 1,
Long = 2,
Support = 3,
Summoner = 5,
Assassin = 6,
MeleeBoss = 8,
LongBoss = 9,
Melee = 0,
Heavy = 1,
Long = 2,
Support = 3,
Summoner = 5,
Assassin = 6,
MeleeBoss = 8,
LongBoss = 9,
}
export const MonTypeName: Record<number, string> = {
[MonType.Melee]: "近战",
[MonType.Heavy]: "重型",
[MonType.Long]: "远程",
[MonType.Support]: "辅助",
[MonType.Summoner]: "召唤师",
[MonType.Assassin]: "刺客",
[MonType.Melee]: "近战",
[MonType.Heavy]: "重型",
[MonType.Long]: "远程",
[MonType.Support]: "辅助",
[MonType.Summoner]: "召唤师",
[MonType.Assassin]: "刺客",
[MonType.MeleeBoss]: "近战Boss",
[MonType.LongBoss]: "远程Boss",
[MonType.LongBoss]: "远程Boss",
}
/**
@@ -43,38 +43,38 @@ export const MonTypeName: Record<number, string> = {
*/
export enum AtkSpeedLv {
VeryFast1 = 1, VeryFast2 = 2, VeryFast3 = 3,
Fast1 = 4, Fast2 = 5, Fast3 = 6,
Normal1 = 7, Normal2 = 8, Normal3 = 9,
Mid1 = 10, Mid2 = 11, Mid3 = 12,
Slow1 = 13, Slow2 = 14, Slow3 = 15,
VerySlow1 = 16,VerySlow2 = 17,VerySlow3 = 18,
Fast1 = 4, Fast2 = 5, Fast3 = 6,
Normal1 = 7, Normal2 = 8, Normal3 = 9,
Mid1 = 10, Mid2 = 11, Mid3 = 12,
Slow1 = 13, Slow2 = 14, Slow3 = 15,
VerySlow1 = 16, VerySlow2 = 17, VerySlow3 = 18,
}
export const AtkSpeedSet = {
[AtkSpeedLv.VeryFast1]: { name: "极速++", cd: 0.15 },
[AtkSpeedLv.VeryFast2]: { name: "极速+", cd: 0.20 },
[AtkSpeedLv.VeryFast1]: { name: "极速++", cd: 0.15 },
[AtkSpeedLv.VeryFast2]: { name: "极速+", cd: 0.20 },
[AtkSpeedLv.VeryFast3]: { name: "极速", cd: 0.30 },
[AtkSpeedLv.Fast1]: { name: "快速++", cd: 0.40 },
[AtkSpeedLv.Fast2]: { name: "快速+", cd: 0.50 },
[AtkSpeedLv.Fast3]: { name: "快速", cd: 0.70 },
[AtkSpeedLv.Normal1]: { name: "中速++", cd: 0.80 },
[AtkSpeedLv.Normal2]: { name: "中速+", cd: 0.90 },
[AtkSpeedLv.Normal3]: { name: "中速", cd: 1.00 },
[AtkSpeedLv.Mid1]: { name: "一般+", cd: 1.10 },
[AtkSpeedLv.Mid2]: { name: "一般", cd: 1.20 },
[AtkSpeedLv.Mid3]: { name: "一般-", cd: 1.30 },
[AtkSpeedLv.Slow1]: { name: "慢", cd: 1.50 },
[AtkSpeedLv.Slow2]: { name: "慢+", cd: 1.60 },
[AtkSpeedLv.Slow3]: { name: "慢++", cd: 1.80 },
[AtkSpeedLv.VerySlow1]: { name: "很慢", cd: 2.30 },
[AtkSpeedLv.VerySlow2]: { name: "很慢+", cd: 2.50 },
[AtkSpeedLv.Fast1]: { name: "快速++", cd: 0.40 },
[AtkSpeedLv.Fast2]: { name: "快速+", cd: 0.50 },
[AtkSpeedLv.Fast3]: { name: "快速", cd: 0.70 },
[AtkSpeedLv.Normal1]: { name: "中速++", cd: 0.80 },
[AtkSpeedLv.Normal2]: { name: "中速+", cd: 0.90 },
[AtkSpeedLv.Normal3]: { name: "中速", cd: 1.00 },
[AtkSpeedLv.Mid1]: { name: "一般+", cd: 1.10 },
[AtkSpeedLv.Mid2]: { name: "一般", cd: 1.20 },
[AtkSpeedLv.Mid3]: { name: "一般-", cd: 1.30 },
[AtkSpeedLv.Slow1]: { name: "慢", cd: 1.50 },
[AtkSpeedLv.Slow2]: { name: "慢+", cd: 1.60 },
[AtkSpeedLv.Slow3]: { name: "慢++", cd: 1.80 },
[AtkSpeedLv.VerySlow1]: { name: "很慢", cd: 2.30 },
[AtkSpeedLv.VerySlow2]: { name: "很慢+", cd: 2.50 },
[AtkSpeedLv.VerySlow3]: { name: "很慢++", cd: 2.80 },
};
export const HeroPos={
0:{pos:v3(-320,BoxSet.GAME_LINE,0)},
1:{pos:v3(0,BoxSet.GAME_LINE,0)},
2:{pos:v3(0,BoxSet.GAME_LINE,0)},
export const HeroPos = {
0: { pos: v3(-320, BoxSet.GAME_LINE, 0) },
1: { pos: v3(0, BoxSet.GAME_LINE, 0) },
2: { pos: v3(0, BoxSet.GAME_LINE, 0) },
}
export const FormationPointX = {
@@ -97,12 +97,12 @@ export const resolveFormationTargetX = (fac: FacSet, type: HType): number => {
export enum MonStart {
SLINE_1=140, //上线y
SLINE_2=100, //下线y
SLINE_3=180, //下线y
SLINE_4=60, //y起始点
START_X=320, //x起始点
START_I=90, //x轴间隔
SLINE_1 = 140, //上线y
SLINE_2 = 100, //下线y
SLINE_3 = 180, //下线y
SLINE_4 = 60, //y起始点
START_X = 320, //x起始点
START_I = 90, //x轴间隔
}
@@ -147,41 +147,41 @@ export const SkillTriggerDesc = {
* 英雄/怪物基础信息接口
*/
export interface heroInfo {
uuid: number; // 唯一标识英雄5000段,怪物5200段
name: string; // 显示名称
icon?: string; // 图标名称(对应美术资源名)
path: string; // 资源路径(对应美术资源名)
fac: FacSet; // 阵营FacSet.HERO 或 FacSet.MON
kind?: number; // 未使用
lv: number; // 英雄等级
pool_lv?: number; // 卡片等级
type: HType; // 攻击定位(近战/中程/远程)
monType?: MonType; // 怪物专属类型
hp: number; // 生命值上限
ap: number; // 攻击力
[SkillTriggerType.Call]?:{s_uuid:number, t_num:number, overrides?: SkillOverrides}[]; // 召唤后触发的技能配置
[SkillTriggerType.Dead]?:{s_uuid:number, t_num:number, overrides?: SkillOverrides}[]; // 死亡后触发的技能配置
[SkillTriggerType.FStart]?:{s_uuid:number, t_num:number, overrides?: SkillOverrides}[]; // 战斗开始时释放的技能配置
[SkillTriggerType.FEnd]?:{s_uuid:number, t_num:number, overrides?: SkillOverrides}[]; // 战斗结束时释放的技能配置
[SkillTriggerType.Field]?:number[]; // 驻场技能uuid列表英雄在场时对全局生效
[SkillTriggerType.Atking]?:{s_uuid:number, t_num:number, overrides?: SkillOverrides}[]; // 普通攻击后触发的技能配置s_uuid: 技能id, t_num: 触发所需的普攻次数
[SkillTriggerType.Atked]?:{s_uuid:number, t_num:number, overrides?: SkillOverrides}[]; // 受击后触发的技能配置s_uuid: 技能id, t_num: 触发所需的受击次数
[SkillTriggerType.Revive]?:{s_uuid:number,r_num:number,upr:number}; // 复活技能配置s_uuid: 技能id, r_num: 触发所需的复活次数, upr 等级对复活次数的影响
dis?: number; // 攻击距离(像素)
speed?: number; // 移动速度(像素/秒)
skills: Record<number, HSkillInfo> ; // 携带技能ID列表
evolve?: Record<number, HeroEvolve>; // 等级进化配置key=等级(2,3,...)
info: string; // 描述文案
uuid: number; // 唯一标识英雄5000段,怪物5200段
name: string; // 显示名称
icon?: string; // 图标名称(对应美术资源名)
path: string; // 资源路径(对应美术资源名)
fac: FacSet; // 阵营FacSet.HERO 或 FacSet.MON
kind?: number; // 未使用
lv: number; // 英雄等级
pool_lv?: number; // 卡片等级
type: HType; // 攻击定位(近战/中程/远程)
monType?: MonType; // 怪物专属类型
hp: number; // 生命值上限
ap: number; // 攻击力
[SkillTriggerType.Call]?: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[]; // 召唤后触发的技能配置
[SkillTriggerType.Dead]?: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[]; // 死亡后触发的技能配置
[SkillTriggerType.FStart]?: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[]; // 战斗开始时释放的技能配置
[SkillTriggerType.FEnd]?: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[]; // 战斗结束时释放的技能配置
[SkillTriggerType.Field]?: number[]; // 驻场技能uuid列表英雄在场时对全局生效
[SkillTriggerType.Atking]?: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[]; // 普通攻击后触发的技能配置s_uuid: 技能id, t_num: 触发所需的普攻次数
[SkillTriggerType.Atked]?: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[]; // 受击后触发的技能配置s_uuid: 技能id, t_num: 触发所需的受击次数
[SkillTriggerType.Revive]?: { s_uuid: number, r_num: number, upr: number }; // 复活技能配置s_uuid: 技能id, r_num: 触发所需的复活次数, upr 等级对复活次数的影响
dis?: number; // 攻击距离(像素)
speed?: number; // 移动速度(像素/秒)
skills: Record<number, HSkillInfo>; // 携带技能ID列表
evolve?: Record<number, HeroEvolve>; // 等级进化配置key=等级(2,3,...)
info: string; // 描述文案
}
/**
* 技能基础信息接口
*/
export interface HSkillInfo {
uuid: number; // 唯一标识技能6000段等
lv:number; // 技能等级
cd:number; // 技能cd
ccd:number; // 占位当前cd用于cd计时
overrides?: SkillOverrides; // 角色专属参数覆盖
uuid: number; // 唯一标识技能6000段等
lv: number; // 技能等级
cd: number; // 技能cd
ccd: number; // 占位当前cd用于cd计时
overrides?: SkillOverrides; // 角色专属参数覆盖
}
/**
* 英雄等级进化配置
@@ -192,11 +192,11 @@ export interface HeroEvolve {
skill?: { s_uuid: number; cd?: number; overrides?: SkillOverrides };
/** 覆盖触发技能(完全替换该类型的触发配置) */
atking?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
atked?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
dead?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
fstart?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
fend?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
revive?: { s_uuid: number; r_num: number; upr: number };
atked?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
dead?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
fstart?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
fend?: { s_uuid: number; t_num: number; overrides?: SkillOverrides }[];
revive?: { s_uuid: number; r_num: number; upr: number };
/** 额外属性加成(在等级倍率基础上叠加) */
ap_bonus?: number;
hp_bonus?: number;
@@ -221,242 +221,324 @@ export interface HeroEvolve {
export const HeroInfo: Record<number, heroInfo> = {
// ========== atked 类(战士 · 自身强化) ==========
5011:{uuid:5011,name:"小铁卫",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:4}}],
info:"每受击3次为自身添加4层护盾"},
5012:{uuid:5012,name:"不死小强",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6302,t_num:3,overrides:{TGroup:TGroup.Self,ap:250}}],
info:"每受击3次为自身回复攻击力250%的生命值"},
5013:{uuid:5013,name:"铁骨头",path:"hk3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[{s_uuid:6402,t_num:5,overrides:{TGroup:TGroup.Self,ap:100}}],
info:"每受击5次永久提升自身最大生命值100点"},
5014:{uuid:5014,name:"怒火武者",path:"hk4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
atked:[{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:12}}],
info:"每受击3次永久提升自身攻击力12点"},
5015:{uuid:5015,name:"血刃武者",path:"hk5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[
{s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:3}},
{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
],
info:"每受击3次加3层护盾每受击5次永久+15攻击力"},
5016:{uuid:5016,name:"狂血战士",path:"hc1", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
atked:[
{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:10}},
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
],
info:"每受击3次永久+10攻击力每受击5次永久+15%暴击率"},
5011: {
uuid: 5011, name: "小铁卫", path: "hk1", fac: FacSet.HERO, pool_lv: 1, lv: 1, type: HType.Melee, hp: 300, ap: 28,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
atked: [{ s_uuid: 6301, t_num: 3, overrides: { TGroup: TGroup.Self, ap: 4 } }],
info: "每受击3次为自身添加4层护盾"
},
// 以下英雄暂时注释,保留待后续扩展
// 5012:{uuid:5012,name:"不死小强",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
// skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
// atked:[{s_uuid:6302,t_num:3,overrides:{TGroup:TGroup.Self,ap:250}}],
// info:"每受击3次为自身回复攻击力250%的生命值"},
// 5013:{uuid:5013,name:"铁骨头",path:"hk3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
// skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
// atked:[{s_uuid:6402,t_num:5,overrides:{TGroup:TGroup.Self,ap:100}}],
// info:"每受击5次永久提升自身最大生命值100点"},
// 5014:{uuid:5014,name:"怒火武者",path:"hk4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
// skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
// atked:[{s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:12}}],
// info:"每受击3次永久提升自身攻击力12点"},
// 5015:{uuid:5015,name:"血刃武者",path:"hk5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
// skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
// atked:[
// {s_uuid:6301,t_num:3,overrides:{TGroup:TGroup.Self,ap:3}},
// {s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
// ],
// info:"每受击3次加3层护盾每受击5次永久+15攻击力"},
// 5016:{uuid:5016,name:"狂血战士",path:"hc1", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
// skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
// atked:[
// {s_uuid:6401,t_num:3,overrides:{TGroup:TGroup.Self,ap:10}},
// {s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:15}}
// ],
// info:"每受击3次永久+10攻击力每受击5次永久+15%暴击率"},
// ========== atking 类 — 刺客(自身强化) ==========
5021:{uuid:5021,name:"小刺客",path:"hc1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast2].cd,ccd:0}},
atking:[{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Self,ap:8}}],
info:"每攻击5次永久提升自身攻击力8点"},
5022:{uuid:5022,name:"嗜血剑客",path:"hc2", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast2].cd,ccd:0}},
atking:[
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:10}},
{s_uuid:6401,t_num:7,overrides:{TGroup:TGroup.Self,ap:12}}
],
info:"每攻击5次永久+10%暴击率每攻击7次永久+12攻击力"},
5023:{uuid:5023,name:"暗影杀手",path:"hc3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast1].cd,ccd:0}},
atking:[
{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Self,ap:10}},
{s_uuid:6404,t_num:7,overrides:{TGroup:TGroup.Self,ap:15}}
],
info:"每攻击5次永久+10%暴击率每攻击7次永久+15%暴伤"},
5021: {
uuid: 5021, name: "小刺客", path: "hc1", fac: FacSet.HERO, pool_lv: 1, lv: 1, type: HType.Melee, hp: 300, ap: 28,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
atking: [{ s_uuid: 6401, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 8 } }],
info: "每攻击5次永久提升自身攻击力8点"
},
// 5022: {
// uuid: 5022, name: "嗜血剑客", path: "hc2", fac: FacSet.HERO, pool_lv: 3, lv: 1, type: HType.Melee, hp: 900, ap: 85,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast2].cd, ccd: 0 } },
// atking: [
// { s_uuid: 6403, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 10 } },
// { s_uuid: 6401, t_num: 7, overrides: { TGroup: TGroup.Self, ap: 12 } }
// ],
// info: "每攻击5次永久+10%暴击率每攻击7次永久+12攻击力"
// },
// 5023: {
// uuid: 5023, name: "暗影杀手", path: "hc3", fac: FacSet.HERO, pool_lv: 5, lv: 1, type: HType.Melee, hp: 1500, ap: 142,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast1].cd, ccd: 0 } },
// atking: [
// { s_uuid: 6403, t_num: 5, overrides: { TGroup: TGroup.Self, ap: 10 } },
// { s_uuid: 6404, t_num: 7, overrides: { TGroup: TGroup.Self, ap: 15 } }
// ],
// info: "每攻击5次永久+10%暴击率每攻击7次永久+15%暴伤"
// },
// ========== atking 类 — 射手队友强化hit_count 控制目标数) ==========
5031:{uuid:5031,name:"援护弓手",path:"ha1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
atking:[{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:1,ap:8}}],
info:"每攻击5次为随机1名队友永久提升攻击力8点"},
5032:{uuid:5032,name:"战术弓手",path:"ha2", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal1].cd,ccd:0}},
atking:[{s_uuid:6403,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:3,ap:10}}],
info:"每攻击5次为随机3名队友永久提升暴击率10%"},
5033:{uuid:5033,name:"鹰眼弓将",path:"ha3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
atking:[
{s_uuid:6401,t_num:5,overrides:{TGroup:TGroup.Team,hit_count:6,ap:8}},
{s_uuid:6404,t_num:7,overrides:{TGroup:TGroup.Team,hit_count:6,ap:12}}
],
info:"每攻击5次为随机6名队友永久+8攻击力每攻击7次永久+12%暴伤"},
5031: {
uuid: 5031, name: "援护弓手", path: "ha1", fac: FacSet.HERO, pool_lv: 1, lv: 1, type: HType.Long, hp: 143, ap: 40,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
atking: [{ s_uuid: 6401, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 1, ap: 8 } }],
info: "每攻击5次为随机1名队友永久提升攻击力8点"
},
// 5032: {
// uuid: 5032, name: "战术弓手", path: "ha2", fac: FacSet.HERO, pool_lv: 3, lv: 1, type: HType.Long, hp: 430, ap: 120,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal1].cd, ccd: 0 } },
// atking: [{ s_uuid: 6403, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 3, ap: 10 } }],
// info: "每攻击5次为随机3名队友永久提升暴击率10%"
// },
// 5033: {
// uuid: 5033, name: "鹰眼弓将", path: "ha3", fac: FacSet.HERO, pool_lv: 5, lv: 1, type: HType.Long, hp: 717, ap: 200,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
// atking: [
// { s_uuid: 6401, t_num: 5, overrides: { TGroup: TGroup.Team, hit_count: 6, ap: 8 } },
// { s_uuid: 6404, t_num: 7, overrides: { TGroup: TGroup.Team, hit_count: 6, ap: 12 } }
// ],
// info: "每攻击5次为随机6名队友永久+8攻击力每攻击7次永久+12%暴伤"
// },
// ========== dead 类(战士+刺客 · 死亡遗产) ==========
5041:{uuid:5041,name:"殉道卫士",path:"hk1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Melee,hp:300,ap:28,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
dead:[{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:3}}],
info:"死亡时为全队添加3层护盾"},
5042:{uuid:5042,name:"遗志将军",path:"hk2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Melee,hp:600,ap:57,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
dead:[
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}},
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:80}}
],
revive:{s_uuid:6501,r_num:1,upr:0.3},
info:"死亡时全队永久+20攻击力、+80最大生命值死后复活一次"},
5043:{uuid:5043,name:"亡魂刺客",path:"hc1", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Melee,hp:900,ap:85,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal1].cd,ccd:0}},
dead:[{s_uuid:6405,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}}],
info:"死亡时全队永久提升击晕概率15%"},
5044:{uuid:5044,name:"血誓剑客",path:"hc2", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Melee,hp:1200,ap:113,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Fast3].cd,ccd:0}},
dead:[
{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}},
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
],
info:"死亡时全队永久+15%暴击率、+20%暴伤"},
5045:{uuid:5045,name:"不灭战魂",path:"hk3", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Melee,hp:1500,ap:142,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow3].cd,ccd:0}},
dead:[
{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:5}},
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:30}},
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:120}}
],
revive:{s_uuid:6501,r_num:1,upr:0.5},
info:"死亡时全队获得5层护盾、永久+30攻击力、永久+120最大生命值死后复活一次"},
// 5041: {
// uuid: 5041, name: "殉道卫士", path: "hk1", fac: FacSet.HERO, pool_lv: 1, lv: 1, type: HType.Melee, hp: 300, ap: 28,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
// dead: [{ s_uuid: 6301, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 3 } }],
// info: "死亡时为全队添加3层护盾"
// },
// 5042: {
// uuid: 5042, name: "遗志将军", path: "hk2", fac: FacSet.HERO, pool_lv: 2, lv: 1, type: HType.Melee, hp: 600, ap: 57,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
// dead: [
// { s_uuid: 6401, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 20 } },
// { s_uuid: 6402, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 80 } }
// ],
// revive: { s_uuid: 6501, r_num: 1, upr: 0.3 },
// info: "死亡时全队永久+20攻击力、+80最大生命值死后复活一次"
// },
// 5043: {
// uuid: 5043, name: "亡魂刺客", path: "hc1", fac: FacSet.HERO, pool_lv: 3, lv: 1, type: HType.Melee, hp: 900, ap: 85,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal1].cd, ccd: 0 } },
// dead: [{ s_uuid: 6405, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 15 } }],
// info: "死亡时全队永久提升击晕概率15%"
// },
// 5044: {
// uuid: 5044, name: "血誓剑客", path: "hc2", fac: FacSet.HERO, pool_lv: 4, lv: 1, type: HType.Melee, hp: 1200, ap: 113,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast3].cd, ccd: 0 } },
// dead: [
// { s_uuid: 6403, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 15 } },
// { s_uuid: 6404, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 20 } }
// ],
// info: "死亡时全队永久+15%暴击率、+20%暴伤"
// },
// 5045: {
// uuid: 5045, name: "不灭战魂", path: "hk3", fac: FacSet.HERO, pool_lv: 5, lv: 1, type: HType.Melee, hp: 1500, ap: 142,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow3].cd, ccd: 0 } },
// dead: [
// { s_uuid: 6301, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 5 } },
// { s_uuid: 6401, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 30 } },
// { s_uuid: 6402, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 120 } }
// ],
// revive: { s_uuid: 6501, r_num: 1, upr: 0.5 },
// info: "死亡时全队获得5层护盾、永久+30攻击力、永久+120最大生命值死后复活一次"
// },
// ========== fstart 类(法师 · 战前增益) ==========
5051:{uuid:5051,name:"占卜师",path:"hm1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid3].cd,ccd:0}},
fstart:[{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}}],
info:"战斗开始时为全队永久提升攻击力15点"},
5052:{uuid:5052,name:"护盾牧师",path:"hm2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Slow1].cd,ccd:0}},
fstart:[{s_uuid:6301,t_num:1,overrides:{TGroup:TGroup.Team,ap:2}}],
info:"战斗开始时为全队添加2层护盾"},
5053:{uuid:5053,name:"血盟法师",path:"hm3", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid2].cd,ccd:0}},
fstart:[{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:100}}],
info:"战斗开始时为全队永久提升最大生命值100点"},
5054:{uuid:5054,name:"暴击法师",path:"hm4", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid3].cd,ccd:0}},
fstart:[{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}],
info:"战斗开始时为全队永久提升暴击率20%"},
5055:{uuid:5055,name:"毁灭法师",path:"hm5", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Long,hp:573,ap:160,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid1].cd,ccd:0}},
fstart:[
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:25}},
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
],
info:"战斗开始时为全队永久+25%暴伤、+20攻击力"},
5056:{uuid:5056,name:"预言法师",path:"hm6", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
fstart:[
{s_uuid:6405,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}},
{s_uuid:6403,t_num:1,overrides:{TGroup:TGroup.Team,ap:15}},
{s_uuid:6404,t_num:1,overrides:{TGroup:TGroup.Team,ap:20}}
],
info:"战斗开始时为全队永久+20%击晕概率、+15%暴击率、+20%暴伤"},
5051: {
uuid: 5051, name: "占卜师", path: "hm1", fac: FacSet.HERO, pool_lv: 1, lv: 1, type: HType.Long, hp: 143, ap: 40,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
fstart: [{ s_uuid: 6401, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 15 } }],
info: "战斗开始时为全队永久提升攻击力15点"
},
// 5052: {
// uuid: 5052, name: "护盾牧师", path: "hm2", fac: FacSet.HERO, pool_lv: 2, lv: 1, type: HType.Long, hp: 287, ap: 80,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow1].cd, ccd: 0 } },
// fstart: [{ s_uuid: 6301, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 2 } }],
// info: "战斗开始时为全队添加2层护盾"
// },
// 5053: {
// uuid: 5053, name: "血盟法师", path: "hm3", fac: FacSet.HERO, pool_lv: 2, lv: 1, type: HType.Long, hp: 287, ap: 80,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
// fstart: [{ s_uuid: 6402, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 100 } }],
// info: "战斗开始时为全队永久提升最大生命值100点"
// },
// 5054: {
// uuid: 5054, name: "暴击法师", path: "hm4", fac: FacSet.HERO, pool_lv: 3, lv: 1, type: HType.Long, hp: 430, ap: 120,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid3].cd, ccd: 0 } },
// fstart: [{ s_uuid: 6403, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 20 } }],
// info: "战斗开始时为全队永久提升暴击率20%"
// },
// 5055: {
// uuid: 5055, name: "毁灭法师", path: "hm5", fac: FacSet.HERO, pool_lv: 4, lv: 1, type: HType.Long, hp: 573, ap: 160,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
// fstart: [
// { s_uuid: 6404, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 25 } },
// { s_uuid: 6401, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 20 } }
// ],
// info: "战斗开始时为全队永久+25%暴伤、+20攻击力"
// },
// 5056: {
// uuid: 5056, name: "预言法师", path: "hm6", fac: FacSet.HERO, pool_lv: 5, lv: 1, type: HType.Long, hp: 717, ap: 200,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
// fstart: [
// { s_uuid: 6405, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 20 } },
// { s_uuid: 6403, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 15 } },
// { s_uuid: 6404, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 20 } }
// ],
// info: "战斗开始时为全队永久+20%击晕概率、+15%暴击率、+20%暴伤"
// },
// ========== field 类(法师 · 驻场光环) ==========
5061:{uuid:5061,name:"亡语法师",path:"hm1", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid2].cd,ccd:0}},
field:[7015],
info:"驻场期间全队死亡触发技能次数+1死亡后光环消失"},
// 5061: {
// uuid: 5061, name: "亡语法师", path: "hm1", fac: FacSet.HERO, pool_lv: 3, lv: 1, type: HType.Long, hp: 430, ap: 120,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid2].cd, ccd: 0 } },
// field: [7015],
// info: "驻场期间全队死亡触发技能次数+1死亡后光环消失"
// },
// ========== fend + atking 类(辅助 · 治疗续航 + 波次增益) ==========
5071:{uuid:5071,name:"治愈牧师",path:"hh1", fac:FacSet.HERO,pool_lv:1,lv:1,type:HType.Long,hp:143,ap:40,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:200}}],
info:"每攻击5次治疗全队200%AP"},
5072:{uuid:5072,name:"小金库",path:"hh2", fac:FacSet.HERO,pool_lv:2,lv:1,type:HType.Long,hp:287,ap:80,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Mid1].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:200}}],
fend:[{s_uuid:6303,t_num:1,overrides:{gold:1}}],
info:"每攻击5次治疗全队每波结束获得1金币"},
5073:{uuid:5073,name:"强化牧师",path:"hh3", fac:FacSet.HERO,pool_lv:3,lv:1,type:HType.Long,hp:430,ap:120,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:250}}],
fend:[{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:10}}],
info:"每攻击5次治疗全队每波结束全队永久+10攻击力"},
5074:{uuid:5074,name:"生命牧师",path:"hh4", fac:FacSet.HERO,pool_lv:4,lv:1,type:HType.Long,hp:573,ap:160,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal3].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:250}}],
fend:[{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:80}}],
info:"每攻击5次治疗全队每波结束全队永久+80最大生命值"},
5075:{uuid:5075,name:"全能牧师",path:"hh5", fac:FacSet.HERO,pool_lv:5,lv:1,type:HType.Long,hp:717,ap:200,
skills:{6001:{uuid:6001,lv:1,cd:AtkSpeedSet[AtkSpeedLv.Normal2].cd,ccd:0}},
atking:[{s_uuid:6302,t_num:5,overrides:{TGroup:TGroup.Team,ap:300}}],
fend:[
{s_uuid:6401,t_num:1,overrides:{TGroup:TGroup.Team,ap:12}},
{s_uuid:6402,t_num:1,overrides:{TGroup:TGroup.Team,ap:60}}
],
info:"每攻击5次治疗全队300%AP每波结束全队永久+12攻击力、+60最大生命值"},
5071: {
uuid: 5071, name: "治愈牧师", path: "hh1", fac: FacSet.HERO, pool_lv: 1, lv: 1, type: HType.Long, hp: 143, ap: 40,
skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
atking: [{ s_uuid: 6302, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 200 } }],
info: "每攻击5次治疗全队200%AP"
},
// 5072: {
// uuid: 5072, name: "小金库", path: "hh2", fac: FacSet.HERO, pool_lv: 2, lv: 1, type: HType.Long, hp: 287, ap: 80,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Mid1].cd, ccd: 0 } },
// atking: [{ s_uuid: 6302, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 200 } }],
// fend: [{ s_uuid: 6303, t_num: 1, overrides: { gold: 1 } }],
// info: "每攻击5次治疗全队每波结束获得1金币"
// },
// 5073: {
// uuid: 5073, name: "强化牧师", path: "hh3", fac: FacSet.HERO, pool_lv: 3, lv: 1, type: HType.Long, hp: 430, ap: 120,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
// atking: [{ s_uuid: 6302, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 250 } }],
// fend: [{ s_uuid: 6401, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 10 } }],
// info: "每攻击5次治疗全队每波结束全队永久+10攻击力"
// },
// 5074: {
// uuid: 5074, name: "生命牧师", path: "hh4", fac: FacSet.HERO, pool_lv: 4, lv: 1, type: HType.Long, hp: 573, ap: 160,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal3].cd, ccd: 0 } },
// atking: [{ s_uuid: 6302, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 250 } }],
// fend: [{ s_uuid: 6402, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 80 } }],
// info: "每攻击5次治疗全队每波结束全队永久+80最大生命值"
// },
// 5075: {
// uuid: 5075, name: "全能牧师", path: "hh5", fac: FacSet.HERO, pool_lv: 5, lv: 1, type: HType.Long, hp: 717, ap: 200,
// skills: { 6001: { uuid: 6001, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal2].cd, ccd: 0 } },
// atking: [{ s_uuid: 6302, t_num: 5, overrides: { TGroup: TGroup.Team, ap: 300 } }],
// fend: [
// { s_uuid: 6401, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 12 } },
// { s_uuid: 6402, t_num: 1, overrides: { TGroup: TGroup.Team, ap: 60 } }
// ],
// info: "每攻击5次治疗全队300%AP每波结束全队永久+12攻击力、+60最大生命值"
// },
/*
*=============怪物配置列表================
* 基础近战型(lv:1) SPEED:800 |AP:12 | HP:360 | skills[0].cd=0.65
* 重型坦克型(lv:1) SPEED:800 |AP:30 | HP:1050 | skills[0].cd=2
* 远程dps(lv:1) SPEED:800 |AP:45 | HP:240 | skills[0].cd=1.5
* 远程辅助(lv:1) SPEED:800 |AP:20 | HP:240 | skills[0].cd=1
* 精英 (lv:1) SPEED:800 |AP:20 | HP:4500 | skills[0].cd=1
*/
/*
*=============怪物配置列表================
* 基础近战型(lv:1) SPEED:800 |AP:12 | HP:360 | skills[0].cd=0.65
* 重型坦克型(lv:1) SPEED:800 |AP:30 | HP:1050 | skills[0].cd=2
* 远程dps(lv:1) SPEED:800 |AP:45 | HP:240 | skills[0].cd=1.5
* 远程辅助(lv:1) SPEED:800 |AP:20 | HP:240 | skills[0].cd=1
* 精英 (lv:1) SPEED:800 |AP:20 | HP:4500 | skills[0].cd=1
*/
// 基础怪物 (全部固定点位站桩攻击HType仅决定是前排还是后排)
// 前排怪物 (站在前排,承受更多伤害) — v5: TD节奏CD多而弱爽感设计
6001:{uuid:6001,name:"兽人战士",path:"m1", fac:FacSet.MON,lv:1,type:HType.Melee,monType:MonType.Melee,hp:220,ap:10,speed:70,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"基础前排怪"},
6002:{uuid:6002,name:"兽人精锐战士",path:"m2", fac:FacSet.MON,lv:1,type:HType.Melee,monType:MonType.Melee,hp:300,ap:14,speed:110,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"进阶前排怪,更快更痛"},
6003:{uuid:6003,name:"兽人重装兵",path:"m3", fac:FacSet.MON,lv:1,type:HType.Melee,monType:MonType.Heavy,hp:850,ap:20,speed:50,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"重型坦克怪高HP慢攻"},
6001: {
uuid: 6001, name: "兽人战士", path: "m1", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 220, ap: 10, speed: 70,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "基础前排怪"
},
6002: {
uuid: 6002, name: "兽人精锐战士", path: "m2", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Melee, hp: 300, ap: 14, speed: 110,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "进阶前排怪,更快更痛"
},
6003: {
uuid: 6003, name: "兽人重装兵", path: "m3", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.Heavy, hp: 850, ap: 20, speed: 50,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "重型坦克怪高HP慢攻"
},
// 后排怪物 (站在后排,输出更高)
6004:{uuid:6004,name:"兽人射手",path:"m4", fac:FacSet.MON,lv:1,type:HType.Long,monType:MonType.Long,hp:190,ap:35,speed:70,
skills:{6008:{uuid:6008,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排高DPS怪"},
6005:{uuid:6005,name:"兽人刺客",path:"m5", fac:FacSet.MON,lv:1,type:HType.Long,monType:MonType.Assassin,hp:210,ap:38,speed:130,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"高AP快速攻击刺客"},
6004: {
uuid: 6004, name: "兽人射手", path: "m4", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Long, hp: 190, ap: 35, speed: 70,
skills: { 6008: { uuid: 6008, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排高DPS怪"
},
6005: {
uuid: 6005, name: "兽人刺客", path: "m5", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Assassin, hp: 210, ap: 38, speed: 130,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "高AP快速攻击刺客"
},
// 特殊位怪物
6006:{uuid:6006,name:"骷髅领主",path:"m6", fac:FacSet.MON,lv:1,type:HType.Melee,monType:MonType.MeleeBoss,hp:5000,ap:20,speed:60,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排MiniBoss级坦克"},
6007:{uuid:6007,name:"兽人术士",path:"m7", fac:FacSet.MON,lv:1,type:HType.Long,monType:MonType.Support,hp:300,ap:24,speed:70,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排法师怪,魔法攻击"},
6008:{uuid:6008,name:"兽人火法",path:"m8", fac:FacSet.MON,lv:1,type:HType.Long,monType:MonType.Summoner,hp:270,ap:32,speed:70,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"后排高输出法师怪"},
6006: {
uuid: 6006, name: "骷髅领主", path: "m6", fac: FacSet.MON, lv: 1, type: HType.Melee, monType: MonType.MeleeBoss, hp: 5000, ap: 20, speed: 60,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排MiniBoss级坦克"
},
6007: {
uuid: 6007, name: "兽人术士", path: "m7", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Support, hp: 300, ap: 24, speed: 70,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排法师怪,魔法攻击"
},
6008: {
uuid: 6008, name: "兽人火法", path: "m8", fac: FacSet.MON, lv: 1, type: HType.Long, monType: MonType.Summoner, hp: 270, ap: 32, speed: 70,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "后排高输出法师怪"
},
// BOSS怪物 — Boss节奏1.2-1.5s删除不存在的6206技能
6101:{uuid:6101,name:"兽人首领-双刀战士",path:"mb1", fac:FacSet.MON,lv:6,type:HType.Melee,monType:MonType.MeleeBoss,hp:1900,ap:30,speed:120,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排Boss高攻速"},
6102:{uuid:6102,name:"兽人首领-斧头战士",path:"mb2", fac:FacSet.MON,lv:6,type:HType.Melee,monType:MonType.MeleeBoss,hp:7500,ap:26,speed:60,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"前排Boss超高HP"},
6103:{uuid:6103,name:"兽人首领-魔法师",path:"mb3", fac:FacSet.MON,lv:6,type:HType.Long,monType:MonType.LongBoss,hp:2250,ap:38,speed:110,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow2].cd,ccd:0}},info:"后排法系Boss高AP"},
6104:{uuid:6104,name:"兽人首领-射手",path:"mb4", fac:FacSet.MON,lv:6,type:HType.Long,monType:MonType.LongBoss,hp:6800,ap:30,speed:70,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"后排位Boss均衡型"},
6105:{uuid:6105,name:"亡灵首领-法师",path:"mb5", fac:FacSet.MON,lv:6,type:HType.Long,monType:MonType.LongBoss,hp:2600,ap:42,speed:110,
skills:{6103:{uuid:6103,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow1].cd,ccd:0}},info:"后排高AP Boss"},
6106:{uuid:6106,name:"亡灵首领-骑马战士",path:"mb6", fac:FacSet.MON,lv:6,type:HType.Melee,monType:MonType.MeleeBoss,hp:9000,ap:26,speed:130,
skills:{6005:{uuid:6005,lv:1,cd:AtkSpeedSet[AtkSpeedLv.VerySlow3].cd,ccd:0}},info:"前排终极Boss最高HP+高速"},
// BOSS怪物 — Boss节奏1.2-1.5s删除不存在的6206技能
6101: {
uuid: 6101, name: "兽人首领-双刀战士", path: "mb1", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 1900, ap: 30, speed: 120,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排Boss高攻速"
},
6102: {
uuid: 6102, name: "兽人首领-斧头战士", path: "mb2", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 7500, ap: 26, speed: 60,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "前排Boss超高HP"
},
6103: {
uuid: 6103, name: "兽人首领-魔法师", path: "mb3", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 2250, ap: 38, speed: 110,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow2].cd, ccd: 0 } }, info: "后排法系Boss高AP"
},
6104: {
uuid: 6104, name: "兽人首领-射手", path: "mb4", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 6800, ap: 30, speed: 70,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "后排位Boss均衡型"
},
6105: {
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, type: HType.Long, monType: MonType.LongBoss, hp: 2600, ap: 42, speed: 110,
skills: { 6103: { uuid: 6103, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow1].cd, ccd: 0 } }, info: "后排高AP Boss"
},
6106: {
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, type: HType.Melee, monType: MonType.MeleeBoss, hp: 9000, ap: 26, speed: 130,
skills: { 6005: { uuid: 6005, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.VerySlow3].cd, ccd: 0 } }, info: "前排终极Boss最高HP+高速"
},
};
};
export const HeroList: number[] = [
// atked 类(战士 · 自身强化)
5011, 5012, 5013, 5014, 5015, 5016,
5011,
// 5012, 5013, 5014, 5015, 5016,
// atking 刺客(自身强化)
5021, 5022, 5023,
5021,
// 5022, 5023,
// atking 射手(队友强化)
5031, 5032, 5033,
5031,
// 5032, 5033,
// dead 类(死亡遗产)
5041, 5042, 5043, 5044, 5045,
// 5041, 5042, 5043, 5044, 5045,
// fstart 类(法师 · 战前增益)
5051, 5052, 5053, 5054, 5055, 5056,
5051,
// 5052, 5053, 5054, 5055, 5056,
// field 类(法师 · 驻场光环)
5061,
// 5061,
// fend + atking 类(辅助 · 治疗 + 波次增益)
5071, 5072, 5073, 5074, 5075,
5071,
// 5072, 5073, 5074, 5075,
];

View File

@@ -136,9 +136,9 @@ export class Hero extends ecs.Entity {
model.revive = hero.revive;
// 基础属性按等级倍率初始化
// 使用指数增长公式等级2时为原来的3倍等级3时为原来的9倍 (若需线性增长可改为 hero.ap * (1 + (model.lv - 1) * (FightSet.H_HERO_POW - 1)))
let base_ap = hero.ap * Math.pow(FightSet.MERGE_NEED, model.lv - 1);
let base_hp = hero.hp * Math.pow(FightSet.MERGE_NEED, model.lv - 1);
// 使用指数增长公式等级 2 时为原来的 HERO_LV_MULTIPLIER 倍,等级 3 时为 HERO_LV_MULTIPLIER^2 倍
let base_ap = hero.ap * Math.pow(FightSet.HERO_LV_MULTIPLIER, model.lv - 1);
let base_hp = hero.hp * Math.pow(FightSet.HERO_LV_MULTIPLIER, model.lv - 1);
model.base_ap = base_ap;
model.base_hp = base_hp;

View File

@@ -98,7 +98,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
// 动态计算卡牌的虚拟攻击力:
// 1. 根据卡牌等级给予基础成长(同英雄升级公式,基准设为 100
let baseAp = 100 * Math.pow(FightSet.MERGE_NEED, cardLv - 1);
let baseAp = 100 * Math.pow(FightSet.HERO_LV_MULTIPLIER, cardLv - 1);
let highestAp = baseAp;
// 2. 获取场上最高攻击力的英雄,保证后期奶量/增益绝对够用

View File

@@ -764,14 +764,28 @@ export class CardComp extends CCComp {
if (this.info_node) this.info_node.active = false;
} else {
if (this.lvl_node) this.lvl_node.node.active = false;
// 特殊卡(升级 / 刷新):显示卡名 + 品质后缀 + 描述
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]
: SpecialRefreshCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
// 动态升级卡:显示对应英雄名 + "升级"后缀 + 目标等级
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) {
const targetHero = HeroInfo[this.cardData.target_hero_uuid];
const targetLv = Math.max(1, Math.floor(this.cardData.hero_lv ?? 1));
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
if (this.info_node) {
this.info_node.active = true;
this.setLabel(this.info_node, `升至 Lv.${targetLv}`);
}
} else {
// 特殊卡(升级 / 刷新):显示卡名 + 品质后缀 + 描述
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]
: SpecialRefreshCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
if (this.info_node) {
this.info_node.active = true;
this.setLabel(this.info_node, specialCard?.info || "");
}
}
this.ap_node.active = false;
this.hp_node.active = false;
}

View File

@@ -246,12 +246,18 @@ 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;
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]
: SpecialRefreshCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
// 动态升级卡:显示对应英雄名 + "升级"后缀
if (this.card_type === CardType.SpecialUpgrade && this.cardData.target_hero_uuid) {
const targetHero = HeroInfo[this.cardData.target_hero_uuid];
this.setLabel(this.name_node, `${targetHero?.name || ""} 升级`);
} else {
const specialCard = this.card_type === CardType.SpecialUpgrade
? SpecialUpgradeCardList[this.card_uuid]
: SpecialRefreshCardList[this.card_uuid];
const card_lv = Math.max(1, Math.floor(this.cardData.card_lv ?? 1));
const spSuffix = card_lv >= 2 ? "★".repeat(card_lv - 1) : "";
this.setLabel(this.name_node, `${spSuffix}${specialCard?.name || ""}${spSuffix}`);
}
}
if (this.cost_node) {

View File

@@ -3,41 +3,43 @@
* @description 卡牌系统核心控制器(战斗 UI 层 + 业务逻辑层)
*
* 职责:
* 1. **卡牌分发管理** —— 从卡池抽取 4 张卡,分发到 4 个 CardComp 槽位。
* 2. **卡池升级** —— 消耗金币提升卡池等级poolLv解锁更高稀有度的卡牌。
* 3. **金币费用管理** —— 抽卡费用refreshCost、升级费用CardsUpSet
* 波次折扣CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE的计算与扣除。
* 4. **英雄数量上限校验** —— 在 UseHeroCard 事件的 guard 阶段判断是否允许
* 再召唤英雄(含合成后腾位的特殊判断 canUseHeroCardByMerge
* 5. **场上英雄信息面板HInfoComp 列表)同步** ——
* 1. **卡牌分发管理** —— 从卡池抽取 3 张卡,分发到 3 个 CardComp 槽位。
* 抽卡规则:场上已有英雄时按权重混合"对应英雄的升级卡" + "其他英雄卡" + "刷新卡"
* 场上无英雄时仅抽取英雄卡和刷新卡。
* 2. **金币费用管理** —— 抽卡费用refreshCost扣除。
* 3. **英雄数量上限校验** —— 在 UseHeroCard 事件的 guard 阶段判断是否允许再召唤英雄。
* 4. **场上英雄信息面板HInfoComp 列表)同步** ——
* 英雄上场时实例化面板,死亡时移除,定时刷新属性显示。
* 6. **特殊卡执行** —— 处理英雄升级卡SpecialUpgrade和英雄刷新卡SpecialRefresh
* 7. **准备/战斗阶段切换** —— 控制卡牌面板的 展开/收起 动画
* 5. **特殊卡执行** —— 处理英雄升级卡SpecialUpgrade,按 UUID 精确升级)和
* 英雄刷新卡SpecialRefresh
* 6. **准备/战斗阶段切换** —— 控制卡牌面板的 展开/收起 动画。
*
* 关键设计:
* - 4 个 CardComp 通过 cacheCardComps() 映射为有序数组 cardComps[]
* - 3 个 CardComp 通过 cacheCardComps() 映射为有序数组 cardComps[]
* 之后所有分发、清空操作均通过此数组进行。
* - buildDrawCards() 保证每次抽取 4 张,不足时循环补齐。
* - buildDrawCards() 动态合并升级卡池和基础卡池后抽取 3 张,不足时循环补齐。
* - 英雄上限校验onUseHeroCard采用 guard/cancel 模式:
* CardComp 发出 UseHeroCard 事件并传入 guard 对象,
* 本组件可通过 guard.cancel=true 阻止使用。
* - ensureHeroInfoPanel() 建立 EID → HInfoComp 的 Map 映射,
* 支持英雄合成升级后面板热更新。
*
* 历史:
* 旧版本曾包含"卡池等级poolLv"机制和"三合一合成腾位"判断,已全部移除:
* - 卡牌不再分级,所有英雄卡统一 lv1。
* - 英雄升级仅通过升级卡SpecialUpgrade触发按 UUID 精确升级场上对应英雄。
*
* 依赖:
* - CardComp —— 单卡槽位
* - HInfoComp —— 英雄信息面板
* - CardSet 模块 —— 卡池配置、抽卡规则、特殊卡数据
* - HeroAttrsComp —— 英雄属性(合成校验 / 升级)
* - MissionHeroComp —— 获取合成规则needCount / maxLv
* - HeroAttrsComp —— 英雄属性(升级)
* - smc.vmdata.mission_data —— 局内数据coin / hero_num / hero_max_num
*/
import { mLogger } from "../common/Logger";
import { _decorator, instantiate, Label, Node, NodeEventType, Prefab, SpriteAtlas, Tween, tween, Vec3, Widget } from "cc";
import { _decorator, Label, Node, NodeEventType, Tween, tween, Vec3 } 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 { GameEvent } from "../common/config/GameEvent";
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 { CardConfig, CardPoolList, CardType, drawCardsByRule, SpecialRefreshCardList, SpecialRefreshHeroType, SpecialUpgradeCardList } from "../common/config/CardSet";
import { CardComp } from "./CardComp";
import { SCardComp } from "./SCardComp";
import { oops } from "db://oops-framework/core/Oops";
@@ -45,11 +47,8 @@ import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp";
import { HeroInfo, HType } from "../common/config/heroSet";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES, SKILL_CARD_WAVES } from "../common/config/GameSet";
import { MoveComp } from "../hero/MoveComp";
import { MissionHeroComp } from "./MissionHeroComp";
import { FacSet, FightSet, SKILL_CARD_WAVES } from "../common/config/GameSet";
import { MissionEconomy } from "./MissionEconomy";
import { MissionComp } from "./MissionComp";
import { UIID } from "../common/config/GameUIConfig";
const { ccclass, property } = _decorator;
@@ -58,7 +57,7 @@ const { ccclass, property } = _decorator;
/**
* MissionCardComp —— 卡牌系统核心控制器
*
* 管理 4 个卡牌槽位的抽卡分发、卡池升级、金币费用、
* 管理 3 个卡牌槽位的抽卡分发、金币费用、
* 英雄上限校验、场上英雄信息面板同步以及特殊卡执行。
*/
@ccclass('MissionCardComp')
@@ -99,13 +98,13 @@ export class MissionCardComp extends CCComp {
/** 抽卡(刷新)按钮节点 */
@property(Node)
cards_chou: Node = null!
/** 卡池升级按钮节点 */
/** 卡池升级按钮节点(已废弃,保留节点引用防止 editor 报错) */
@property(Node)
cards_up: Node = null!
/** 金币显示节点(含 icon + num 子节点) */
@property(Node)
coins_node: Node = null!
/** 卡池等级显示节点 */
/** 卡池等级显示节点(已废弃,保留节点引用) */
@property(Node)
pool_lv_node: Node = null!
/** 英雄数量显示节点(含 icon + num 子节点) */
@@ -135,12 +134,10 @@ export class MissionCardComp extends CCComp {
// ======================== 运行时状态 ========================
/** 个槽位对应的 CardComp 控制器缓存(有序数组) */
/** 个槽位对应的 CardComp 控制器缓存(有序数组) */
private cardComps: CardComp[] = [];
/** 技能卡槽控制器缓存 */
private skillCardComps: SCardComp[] = [];
/** 当前卡池等级(仅影响抽卡来源,不直接改卡槽现有内容) */
private poolLv: number = CARD_POOL_INIT_LEVEL;
/** 是否已缓存卡牌面板基准缩放 */
private hasCachedCardsBaseScale: boolean = false;
/** 卡牌面板基准缩放(从场景读取) */
@@ -157,20 +154,17 @@ export class MissionCardComp extends CCComp {
/**
* 组件加载:
* 1. 绑定生命周期事件和按钮交互事件。
* 2. 缓存 4 个 CardComp 子控制器引用。
* 2. 缓存 3 个 CardComp 子控制器引用。
* 3. 计算并设置槽位水平布局。
* 4. 初始化卡牌面板缩放参数。
* 5. 触发首次任务开始流程。
*/
onLoad() {
this.bindEvents();
this.cacheCardComps();
this.layoutCardSlots();
this.initCardsPanelPos();
// this.onMissionStart(); // 移除 onLoad 自动触发,改为事件驱动
mLogger.log(this.debugMode, "MissionCardComp", "onLoad init", {
slots: this.cardComps.length,
poolLv: this.poolLv
});
}
@@ -193,15 +187,13 @@ export class MissionCardComp extends CCComp {
/**
* 任务开始:
* 1. 进入准备阶段(展开卡牌面板)。
* 2. 重置卡池等级为初始值
* 3. 初始化局内数据(金币、英雄数量上限)
* 4. 清空旧英雄信息面板和卡牌槽位
* 5. 重置按钮状态和 UI 显示
* 6. 执行首次抽卡并分发到 4 个槽位。
* 2. 初始化局内数据(金币、英雄数量上限)
* 3. 清空旧英雄信息面板和卡牌槽位
* 4. 重置按钮状态和 UI 显示
* 5. 执行首次抽卡并分发到 3 个槽位
*/
onMissionStart() {
this.enterPreparePhase();
this.poolLv = CARD_POOL_INIT_LEVEL;
const missionData = this.getMissionData();
if (missionData) {
missionData.coin = Math.max(0, Math.floor(missionData.coin ?? 0));
@@ -217,11 +209,7 @@ export class MissionCardComp extends CCComp {
this.layoutCardSlots();
this.clearAllCards();
// if (this.cards_up) {
// this.cards_up.active = true;
// }
this.resetButtonScale(this.cards_chou);
// this.resetButtonScale(this.cards_up);
this.updateCoinAndCostUI();
this.updateHeroNumUI(false, false);
if (this.node && this.node.isValid) {
@@ -235,12 +223,10 @@ export class MissionCardComp extends CCComp {
this.showSkillCardPopup();
}
mLogger.log(this.debugMode, "MissionCardComp", "mission start", {
poolLv: this.poolLv
});
mLogger.log(this.debugMode, "MissionCardComp", "mission start");
}
/** 任务结束:清空 4 槽 + 英雄面板并隐藏整个节点 */
/** 任务结束:清空 3 槽 + 英雄面板并隐藏整个节点 */
onMissionEnd() {
this.clearAllCards();
if (this.node && this.node.isValid) {
@@ -271,7 +257,7 @@ export class MissionCardComp extends CCComp {
* 绑定所有事件监听:
* - 节点级事件MissionStart / MissionEnd / NewWave / FightStart
* - 全局消息CoinAdd / MasterCalled / HeroDead / UseHeroCard / UseSpecialCard
* - 按钮触控抽卡cards_chou、升级cards_up
* - 按钮触控抽卡cards_chou
*/
private bindEvents() {
/** 生命周期事件(节点级) */
@@ -288,10 +274,9 @@ export class MissionCardComp extends CCComp {
oops.message.on(GameEvent.UseHeroCard, this.onUseHeroCard, this);
oops.message.on(GameEvent.UseSkillCard, this.onUseSkillCard, this);
oops.message.on(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
oops.message.on(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
oops.message.on(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
/** 按钮触控事件:抽卡与卡池升级 */
/** 按钮触控事件:抽卡 */
this.cards_chou?.on(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
this.cards_chou?.on(NodeEventType.TOUCH_END, this.onDrawTouchEnd, this);
this.cards_chou?.on(NodeEventType.TOUCH_CANCEL, this.onDrawTouchCancel, this);
@@ -303,9 +288,6 @@ export class MissionCardComp extends CCComp {
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.cards_up?.on(NodeEventType.TOUCH_START, this.onUpgradeTouchStart, this);
// this.cards_up?.on(NodeEventType.TOUCH_END, this.onUpgradeTouchEnd, this);
// this.cards_up?.on(NodeEventType.TOUCH_CANCEL, this.onUpgradeTouchCancel, this);
}
// ======================== 事件回调 ========================
@@ -333,47 +315,17 @@ export class MissionCardComp extends CCComp {
}
}
/**
* 接收卡池升级事件:
* - 更新卡池等级
* - 更新UI显示
*/
private onCardPoolUpgrade(event: string, args: any) {
const targetLv = args?.targetLv;
if (!targetLv) return;
if (targetLv > CARD_POOL_MAX_LEVEL) {
this.poolLv = CARD_POOL_MAX_LEVEL;
} else {
this.poolLv = targetLv;
}
mLogger.log(this.debugMode, "MissionCardComp", "onCardPoolUpgrade", {
targetLv,
poolLv: this.poolLv
});
// 提示卡池升级
this.showSmallTip("pool_upgrade");
// 更新UI
this.updatePoolLvUI();
}
private onShowSmallTip(event: string, args: any) {
const type = args as string;
this.showSmallTip(type as any);
}
public showSmallTip(type: "refresh_coin" | "pool_upgrade" | "buy_coin" | "hero_full") {
public showSmallTip(type: "refresh_coin" | "buy_coin" | "hero_full") {
let targetNode: Node | null = null;
switch (type) {
case "refresh_coin":
targetNode = this.cards_chou;
break;
case "pool_upgrade":
targetNode = this.pool_lv_node;
break;
case "buy_coin":
targetNode = this.coins_node;
break;
@@ -481,14 +433,6 @@ export class MissionCardComp extends CCComp {
this.skill_card_node.active = false;
}
// 首次完成技能选取后 关闭guide2打开guide3
// 之前这里有个逻辑漏洞:玩家如果在弹出 guide2 之前就已经因为手快点掉了 guide2
// smc.finish_guides 里就会有 2。
// 但其实这里的本意是:只要发生了选取技能,并且 guide3 还没弹过,就弹 guide3。
// 如果我们把它包在 `if (!smc.finish_guides.includes(2))` 里,
// 当玩家点击 guide2 把它关掉时finish_guides 存入了 2
// 再点技能卡触发这个方法,外层 if 就会进不去guide3 就永远弹不出来了!
// 修复:独立判断 guide2 的关闭 和 guide3 的开启
if (!smc.finish_guides.includes(2)) {
smc.finish_guides.push(2);
@@ -500,7 +444,6 @@ export class MissionCardComp extends CCComp {
}
// 驻场技能可能影响刷新费用(如"刷新优惠"),延迟到下一帧刷新费用 UI
// 确保 MissSkillsComp 已创建 SkillBoxComp 并注册驻场效果
this.scheduleOnce(() => this.updateCoinAndCostUI(), 0);
}
@@ -513,7 +456,6 @@ export class MissionCardComp extends CCComp {
oops.message.off(GameEvent.UseHeroCard, this.onUseHeroCard, this);
oops.message.off(GameEvent.UseSkillCard, this.onUseSkillCard, this);
oops.message.off(GameEvent.UseSpecialCard, this.onUseSpecialCard, this);
oops.message.off(GameEvent.CardPoolUpgrade, this.onCardPoolUpgrade, this);
oops.message.off(GameEvent.ShowSmallTip, this.onShowSmallTip, this);
if (this.cards_chou && this.cards_chou.isValid) {
this.cards_chou.off(NodeEventType.TOUCH_START, this.onDrawTouchStart, this);
@@ -530,9 +472,6 @@ export class MissionCardComp extends CCComp {
this.skill_ad_refresh.off(NodeEventType.TOUCH_END, this.onSkillAdDrawTouchEnd, this);
this.skill_ad_refresh.off(NodeEventType.TOUCH_CANCEL, this.onSkillAdDrawTouchCancel, this);
}
// this.cards_up?.off(NodeEventType.TOUCH_START, this.onUpgradeTouchStart, this);
// this.cards_up?.off(NodeEventType.TOUCH_END, this.onUpgradeTouchEnd, this);
// this.cards_up?.off(NodeEventType.TOUCH_CANCEL, this.onUpgradeTouchCancel, this);
}
/**
@@ -575,28 +514,18 @@ export class MissionCardComp extends CCComp {
/**
* 使用英雄卡的 guard 校验(由 CardComp 通过 UseHeroCard 事件调用):
* - 当前英雄数 < 上限 → 允许使用。
* - 已满但新卡可触发合成(腾位) → 允许使用
* - 已满且不可合成 → 阻止使用cancel=true弹 toast。
* - 已满 → 阻止使用cancel=true弹 toast
*
* 注意:英雄不再支持合成腾位,满员时一律阻止。
*/
private onUseHeroCard(event: string, args: any) {
const payload = args ?? event;
if (!payload) return;
// 战斗阶段也允许召唤英雄(无需额外费用),仅校验英雄数量上限
const current = this.getAliveHeroCount();
this.syncMissionHeroData(current);
const heroMax = this.getMissionHeroMaxNum();
if (current >= heroMax) {
const heroUuid = Number(payload?.uuid ?? 0);
const heroLv = Math.max(1, Math.floor(Number(payload?.hero_lv ?? 1)));
const cardLv = Math.max(1, Math.floor(Number(payload?.pool_lv ?? 1)));
// 检查是否可以通过合成腾出位置
if (this.canUseHeroCardByMerge(heroUuid, heroLv)) {
payload.cancel = false;
payload.reason = "";
return;
}
payload.cancel = true;
payload.reason = "hero_limit";
this.showSmallTip("hero_full");
@@ -604,53 +533,11 @@ export class MissionCardComp extends CCComp {
}
}
/**
* 判断新召唤的英雄是否能通过合成腾位:
* 场上同 UUID 同等级数量 + 1新卡自身>= 合成所需数量 → 可以合成。
*/
private canUseHeroCardByMerge(heroUuid: number, heroLv: number): boolean {
if (!heroUuid) return false;
const mergeRule = this.getMergeRule();
if (heroLv >= mergeRule.maxLv) return false;
const sameCount = this.countAliveHeroesByUuidAndLv(heroUuid, heroLv);
return sameCount + 1 >= mergeRule.needCount;
}
/** 统计场上同 UUID 同等级的存活英雄数量 */
private countAliveHeroesByUuidAndLv(heroUuid: number, heroLv: number): number {
let count = 0;
const actors = this.queryAliveHeroActors();
for (let i = 0; i < actors.length; i++) {
const model = actors[i].model;
if (!model) continue;
if (model.hero_uuid !== heroUuid) continue;
if (model.lv !== heroLv) continue;
count += 1;
}
return count;
}
/**
* 从 MissionHeroComp 实时读取合成规则。
* 通过 ECS 查询获取,避免硬编码与 MissionHeroComp 不一致。
* @returns { needCount: 合成所需数量, maxLv: 最大合成等级 }
*/
private getMergeRule(): { needCount: number, maxLv: number } {
let needCount = FightSet.MERGE_NEED ? FightSet.MERGE_NEED : 2
let maxLv = Math.max(1, Math.floor(FightSet.MERGE_MAX ?? 3));
ecs.query(ecs.allOf(MissionHeroComp)).forEach((entity: ecs.Entity) => {
const comp = entity.get(MissionHeroComp);
if (!comp) return;
needCount = comp.merge_need_count === 2 ? 2 : 3;
maxLv = Math.max(1, Math.floor(comp.merge_max_lv ?? 3));
});
return { needCount, maxLv };
}
/**
* 使用特殊卡事件回调:
* - SpecialUpgrade随机选一个指定等级的英雄升级到目标等级
* - SpecialRefresh按英雄类型 / 指定等级重新抽取英雄
* - SpecialUpgrade按卡牌携带的 target_hero_uuid 精确升级场上对应英雄
* target_hero_uuid 缺失时回退为随机升级一个可升级的英雄。
* - SpecialRefresh按英雄类型重新抽取英雄卡。
*/
private onUseSpecialCard(event: string, args: any) {
const payload = args ?? event;
@@ -659,14 +546,17 @@ export class MissionCardComp extends CCComp {
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}级的英雄`);
const template = SpecialUpgradeCardList[uuid];
if (!template) return;
const targetHeroUuid = Number(payload?.target_hero_uuid ?? 0);
success = this.tryUpgradeHeroByUuid(targetHeroUuid);
if (!success) {
oops.gui.toast(`场上没有可升级的英雄`);
}
} else if (type === CardType.SpecialRefresh) {
const card = SpecialRefreshCardList[uuid];
if (!card) return;
success = this.tryRefreshHeroCardsByEffect(card.refreshHeroType, card.refreshLv);
success = this.tryRefreshHeroCardsByEffect(card.refreshHeroType);
if (!success) oops.gui.toast("当前卡池无符合条件的英雄卡");
}
mLogger.log(this.debugMode, "MissionCardComp", "use special card", {
@@ -731,20 +621,8 @@ export class MissionCardComp extends CCComp {
const cards = this.buildSkillDrawCards();
this.dispatchCardsToSkillSlots(cards);
}
// /** 升级按钮按下反馈 */
// private onUpgradeTouchStart() {
// this.playButtonPressAnim(this.cards_up);
// }
// /** 升级按钮释放 → 执行升级逻辑 */
// private onUpgradeTouchEnd() {
// this.playButtonClickAnim(this.cards_up, () => this.onClickUpgrade());
// }
// /** 升级按钮取消 → 恢复缩放 */
// private onUpgradeTouchCancel() {
// this.playButtonResetAnim(this.cards_up);
// }
/** 将个卡槽节点映射为 CardComp形成固定顺序控制数组 */
/** 将个卡槽节点映射为 CardComp形成固定顺序控制数组 */
private cacheCardComps() {
if (this.card4) {
this.card4.active = false;
@@ -760,16 +638,15 @@ export class MissionCardComp extends CCComp {
.filter((comp): comp is SCardComp => !!comp);
}
// ======================== 核心业务:抽卡 & 升级 ========================
// ======================== 核心业务:抽卡 ========================
/**
* 抽卡按钮核心逻辑:
* 1. 检查金币是否足够 → 不够则 toast 提示。
* 2. 扣除费用、播放金币动画。
* 3. 重新布局槽位 → 从卡池构建 4 张卡 → 分发到槽位。
* 3. 重新布局槽位 → 从卡池构建 3 张卡 → 分发到槽位。
*/
private onClickDraw() {
// 战斗阶段和倒计时阶段均允许刷新抽卡
const cost = MissionEconomy.getRefreshCost(this.refreshCost);
const success = MissionEconomy.executeRefresh(this.refreshCost);
if (!success) {
@@ -783,7 +660,6 @@ export class MissionCardComp extends CCComp {
}
mLogger.log(this.debugMode, "MissionCardComp", "click draw", {
poolLv: this.poolLv,
cost,
leftCoin: MissionEconomy.getCoin()
});
@@ -792,35 +668,6 @@ export class MissionCardComp extends CCComp {
this.dispatchCardsToSlots(cards);
}
// /** 升级按钮:仅提升卡池等级,卡槽是否更新由下一次抽卡触发 */
// private onClickUpgrade() {
// if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
// mLogger.log(this.debugMode, "MissionCardComp", "pool already max", this.poolLv);
// return;
// }
// const cost = this.getUpgradeCost(this.poolLv);
// const currentCoin = this.getMissionCoin();
// if (currentCoin < cost) {
// oops.gui.toast(`金币不足,升级需要${cost}`);
// this.updateCoinAndCostUI();
// mLogger.log(this.debugMode, "MissionCardComp", "pool upgrade coin not enough", {
// poolLv: this.poolLv,
// currentCoin,
// cost
// });
// return;
// }
// this.setMissionCoin(currentCoin - cost);
// this.poolLv += 1;
// this.playCoinChangeAnim(false);
// this.updateCoinAndCostUI();
// mLogger.log(this.debugMode, "MissionCardComp", "pool level up", {
// poolLv: this.poolLv,
// cost,
// leftCoin: this.getMissionCoin()
// });
// }
// ======================== 阶段切换 ========================
/** 缓存卡牌面板的基准缩放值(从场景初始状态读取,仅缓存一次) */
@@ -853,7 +700,7 @@ export class MissionCardComp extends CCComp {
private enterBattlePhase() {
if (!this.cards_node || !this.cards_node.isValid) return;
this.initCardsPanelPos();
// 战斗阶段允许抽卡nobg 按"金币是否足够"判断,而非强制置灰
// 战斗阶段允许抽卡nobg 按"金币是否足够"判断
if (this.cards_chou && this.cards_chou.isValid) {
const nobg = this.cards_chou.getChildByName("nobg");
if (nobg) {
@@ -862,30 +709,140 @@ export class MissionCardComp extends CCComp {
}
}
/** 构建本次抽卡结果保证最终可分发3条数据 */
/**
* 构建本次抽卡结果,保证最终可分发 3 条数据。
*
* 抽卡池构成:
* - 动态升级卡:扫描场上存活英雄,每个 UUID 至多生成一张升级卡
* (已达到 HERO_MAX_LV 的英雄不出卡)。同 UUID 只出一张,避免重复。
* - 基础英雄卡:所有英雄的 lv1 卡。
* - 刷新功能卡SpecialRefresh 卡。
*
* 三类卡按 weight 权重混合抽 3 张,且升级卡之间 unique 去重。
*
* 特殊规则:当场存活英雄数已达 HERO_MAX_NUM 时,
* 不再抽取基础英雄卡和刷新卡,只抽取场上已有英雄的升级卡
* (若全部已满级则降级回混合池,避免卡池为空)。
*/
private buildDrawCards(): CardConfig[] {
const targetType = [CardType.Hero, CardType.SpecialRefresh];
const cards = getCardsByLv(this.poolLv, targetType);
const upgradeCards = this.buildHeroUpgradeCards();
const aliveHeroCount = this.getAliveHeroCount();
const heroMax = this.getMissionHeroMaxNum();
/** 正常情况下直接取前3 */
if (cards.length >= 3) return cards.slice(0, 3);
/** 兜底当返回不足3张时循环补齐保证分发不缺位 */
const filled = [...cards];
// 英雄已满员且有可升级的英雄 → 只出升级卡
if (aliveHeroCount >= heroMax && upgradeCards.length > 0) {
const picked = this.pickMixedCards(upgradeCards, 3, upgradeCards);
if (picked.length >= 3) return picked.slice(0, 3);
/** 兜底:不足 3 张时循环补齐 */
const filled = [...picked];
while (filled.length < 3) {
filled.push(upgradeCards[filled.length % upgradeCards.length]);
}
return filled;
}
const heroCards = CardPoolList.filter(c => c.type === CardType.Hero);
const refreshCards: CardConfig[] = Object.values(SpecialRefreshCardList);
const mixedPool: CardConfig[] = [...upgradeCards, ...heroCards, ...refreshCards];
if (mixedPool.length === 0) return [];
const picked = this.pickMixedCards(mixedPool, 3, upgradeCards);
if (picked.length >= 3) return picked.slice(0, 3);
/** 兜底:不足 3 张时循环补齐 */
const filled = [...picked];
while (filled.length < 3) {
const fallback = getCardsByLv(this.poolLv, targetType);
if (fallback.length === 0) break;
filled.push(fallback[filled.length % fallback.length]);
if (mixedPool.length === 0) break;
filled.push(mixedPool[filled.length % mixedPool.length]);
}
return filled;
}
/**
* 从混合池中按权重抽取 n 张卡。
* 升级卡之间强制 unique同一个 target_hero_uuid 只能出现一次)。
*/
private pickMixedCards(pool: CardConfig[], count: number, upgradeCards: CardConfig[]): CardConfig[] {
if (pool.length === 0 || count <= 0) return [];
const selected: CardConfig[] = [];
const usedUpgradeTargets = new Set<number>();
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);
}
return true;
});
if (available.length === 0) break;
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);
}
}
return selected;
}
/** 单次按权重抽取一张卡 */
private weightedPick(cards: CardConfig[]): CardConfig | null {
if (cards.length === 0) return null;
const totalWeight = cards.reduce((total, card) => total + (card.weight ?? 0), 0);
let random = Math.random() * totalWeight;
for (const card of cards) {
random -= (card.weight ?? 0);
if (random <= 0) return card;
}
return cards[cards.length - 1];
}
/**
* 扫描场上存活英雄,为每个 UUID 至多生成一张升级卡。
* - 已达 HERO_MAX_LV 的英雄不出卡。
* - 同 UUID 多个英雄只生成一张(取等级最低的那个作为升级目标)。
* - cost = 模板 cost + (当前等级 - 1) * BASE_COST等级越高升级越贵。
*/
private buildHeroUpgradeCards(): CardConfig[] {
const template = SpecialUpgradeCardList[7001];
if (!template) return [];
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; // 已达上限不再出升级卡
const dynamicCost = template.cost + (lv - 1) * FightSet.BASE_COST;
result.push({
...template,
cost: dynamicCost,
weight: template.weight,
target_hero_uuid: uuid,
hero_lv: lv + 1,
});
});
return result;
}
private buildSkillDrawCards(): CardConfig[] {
const targetType = CardType.Skill;
const currentWave = this.getCurrentWave();
// 使用明确规则的 drawCardsByRule指定只要 3 张技能卡,并且过滤对应 wave
const cards = drawCardsByRule(this.poolLv, {
const cards = drawCardsByRule(1, {
count: 3,
type: targetType,
type: CardType.Skill,
wave: currentWave,
unique: true // 保证技能牌不重复
});
@@ -893,9 +850,9 @@ export class MissionCardComp extends CCComp {
if (cards.length >= 3) return cards.slice(0, 3);
const filled = [...cards];
while (filled.length < 3) {
const fallback = drawCardsByRule(this.poolLv, {
const fallback = drawCardsByRule(1, {
count: 3,
type: targetType,
type: CardType.Skill,
wave: currentWave,
unique: true
});
@@ -912,12 +869,11 @@ export class MissionCardComp extends CCComp {
return filled;
}
private tryRefreshHeroCards(heroType?: HType, targetPoolLv?: number): boolean {
const cards = drawCardsByRule(this.poolLv, {
private tryRefreshHeroCards(heroType?: HType): boolean {
const cards = drawCardsByRule(1, {
count: 3,
type: CardType.Hero,
heroType,
targetPoolLv
});
if (cards.length <= 0) return false;
this.layoutCardSlots();
@@ -925,10 +881,9 @@ export class MissionCardComp extends CCComp {
return true;
}
private tryRefreshHeroCardsByEffect(refreshHeroType: SpecialRefreshHeroType, refreshLv: number): boolean {
private tryRefreshHeroCardsByEffect(refreshHeroType: SpecialRefreshHeroType): boolean {
const heroType = this.resolveRefreshHeroType(refreshHeroType);
const targetPoolLv = refreshLv > 0 ? refreshLv : undefined;
return this.tryRefreshHeroCards(heroType, targetPoolLv);
return this.tryRefreshHeroCards(heroType);
}
private resolveRefreshHeroType(refreshHeroType: SpecialRefreshHeroType): HType | undefined {
@@ -937,7 +892,7 @@ export class MissionCardComp extends CCComp {
return undefined;
}
/** 全量分发给4槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */
/** 全量分发给 3 槽;每个槽位是否接收由 CardComp 自己判断(锁定可跳过) */
private dispatchCardsToSlots(cards: CardConfig[]) {
if (!this.cardComps) return;
for (let i = 0; i < this.cardComps.length; i++) {
@@ -952,7 +907,7 @@ export class MissionCardComp extends CCComp {
}
}
/** 系统清空4槽(用于任务切换) */
/** 系统清空 3 槽(用于任务切换) */
private clearAllCards() {
if (!this.cardComps) return;
this.cardComps.forEach(comp => {
@@ -1001,77 +956,10 @@ export class MissionCardComp extends CCComp {
Tween.stopAllByTarget(node);
node.setScale(this.buttonNormalScale, this.buttonNormalScale, 1);
}
private canUpPool() {
if (this.poolLv >= CARD_POOL_MAX_LEVEL) return false;
const currentCoin = MissionEconomy.getCoin();
return currentCoin >= this.getUpgradeCost(this.poolLv);
}
private canDrawCards() {
return MissionEconomy.getCoin() >= MissionEconomy.getRefreshCost(this.refreshCost);
}
/** 更新升级按钮上的等级文案,反馈当前卡池层级 */
private updatePoolLvUI() {
if (this.pool_lv_node) {
this.pool_lv_node.active = true;
const lv = Math.max(CARD_POOL_INIT_LEVEL, Math.min(CARD_POOL_MAX_LEVEL, Math.floor(this.poolLv)));
const lvNode = this.pool_lv_node.getChildByName("lv");
if (lvNode) {
const label = lvNode.getComponent(Label);
if (label) {
label.string = `lv.${lv}`;
}
}
const nextNode = this.pool_lv_node.getChildByName("next");
if (nextNode) {
const nextLabel = nextNode.getComponent(Label);
if (nextLabel) {
if (this.poolLv >= CARD_POOL_MAX_LEVEL) {
nextLabel.string = `已满级`;
} else {
// 优先取 MissionComp 运行时配置,缺失时回退到全局常量
let upgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
ecs.query(ecs.allOf(MissionComp)).forEach((entity) => {
const mission = entity.get(MissionComp);
if (mission && mission.cardPoolUpgradeWaves && mission.cardPoolUpgradeWaves.length > 0) {
upgradeWaves = mission.cardPoolUpgradeWaves;
}
});
// 已完成的升级次数 = 当前等级 - 初始等级
// 例poolLv=2INIT=1→ 已升 1 次 → 下一升级对应 upgradeWaves[1]
const upgradedCount = Math.max(0, Math.floor(this.poolLv) - CARD_POOL_INIT_LEVEL);
const currentWave = this.getCurrentWave();
if (upgradedCount >= upgradeWaves.length) {
// 配置已耗尽但等级未到上限(配置缺陷)
nextLabel.string = `已满级`;
} else {
const nextWave = upgradeWaves[upgradedCount];
if (nextWave > currentWave) {
const remain = nextWave - currentWave;
nextLabel.string = `${remain} 回合后升级`;
} else if (nextWave === currentWave) {
// 当前波次正好是升级波次(事件可能即将触发或刚刚触发)
nextLabel.string = `本回合升级`;
} else {
// nextWave < currentWave异常状态升级事件未按时触发
nextLabel.string = `即将升级`;
}
}
}
}
}
const peak = 1.2
this.playHeroNumNodePop(this.pool_lv_node, peak);
}
mLogger.log(this.debugMode, "MissionCardComp", "pool lv ui update", {
poolLv: this.poolLv,
cost: this.getUpgradeCost(this.poolLv)
});
}
private updateDrawCostUI() {
// 战斗阶段也允许抽卡nobg 统一按"金币是否足够"判断
@@ -1101,7 +989,6 @@ export class MissionCardComp extends CCComp {
}
private updateCoinAndCostUI() {
this.updatePoolLvUI();
this.updateDrawCostUI();
}
@@ -1116,13 +1003,6 @@ export class MissionCardComp extends CCComp {
this.playHeroNumNodePop(num, peak);
}
private getUpgradeCost(lv: number): number {
const baseCost = Math.max(0, Math.floor(CardsUpSet[lv] ?? 0));
const completedWave = Math.max(0, this.getCurrentWave() - 1);
const discount = Math.max(0, Math.floor(CARD_POOL_UPGRADE_DISCOUNT_PER_WAVE)) * completedWave;
return Math.max(0, baseCost - discount);
}
public setHeroMaxCount(max: number) {
const missionData = this.getMissionData();
if (!missionData) return;
@@ -1171,28 +1051,42 @@ export class MissionCardComp extends CCComp {
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);
/**
* 按英雄 UUID 精确升级一个存活英雄。
* 若同 UUID 多个英雄,取等级最低且未达上限的;若都达上限则失败。
*
* @param heroUuid 要升级的英雄 UUID
* @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;
const target = candidates[Math.floor(Math.random() * candidates.length)];
this.applyHeroLevel(target.model, toLv);
// 取等级最低的(先升级低等级英雄)
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");
}
return true;
}
/**
* 应用英雄等级变化(属性按 HERO_LV_MULTIPLIER 重新计算)。
* 沿用旧版本 SkillLvUp 的技能等级映射规则。
*/
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 nextLv = Math.max(1, Math.min(FightSet.HERO_MAX_LV, 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.ap = hero.ap * Math.pow(FightSet.HERO_LV_MULTIPLIER, nextLv - 1);
model.hp_max = hero.hp * Math.pow(FightSet.HERO_LV_MULTIPLIER, nextLv - 1);
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) {
@@ -1266,17 +1160,12 @@ export class MissionCardComp extends CCComp {
return Math.max(0, Math.floor(missionData?.hero_num ?? 0));
}
private getCurrentWave(): number {
const missionData = this.getMissionData();
return Math.max(1, Math.floor(missionData?.level ?? 1));
}
private getMissionHeroMaxNum(): number {
return FightSet.HERO_MAX_NUM
}
@@ -1290,7 +1179,6 @@ export class MissionCardComp extends CCComp {
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
reset() {
this.resetButtonScale(this.cards_chou);
// this.resetButtonScale(this.cards_up);
// 关键:在 reset/销毁 时将 Map 置空,彻底切断引用
this.cardComps = [] as any;

View File

@@ -39,7 +39,7 @@ import { HeroViewComp } from "../hero/HeroViewComp";
import { SkillTriggerHelper } from "../hero/SkillTriggerHelper";
import { UIID } from "../common/config/GameUIConfig";
import { SkillView } from "../skill/SkillView";
import { FacSet, FightSet, CARD_POOL_UPGRADE_WAVES } from "../common/config/GameSet";
import { FacSet, FightSet } from "../common/config/GameSet";
import { HeroInfo } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { Monster } from "../hero/Mon";
@@ -86,8 +86,6 @@ export class MissionComp extends CCComp {
private maxMonsterCount: number = 80;
/** 怪物数量恢复阈值(降至此值以下恢复刷怪) */
private resumeMonsterCount: number = 45;
/** 卡池升级波次配置(默认值来自 GameSet.CARD_POOL_UPGRADE_WAVES保持全局统一 */
public cardPoolUpgradeWaves: number[] = CARD_POOL_UPGRADE_WAVES;
// ======================== 编辑器绑定节点 ========================
@@ -829,22 +827,6 @@ export class MissionComp extends CCComp {
this.lastTimeSecond = -1;
this.clearTime = 0;
this.update_time();
// 检查并推送卡池升级事件
this.checkCardPoolUpgrade(wave);
}
/** 检查是否达到卡池升级波次,并推送升级事件 */
private checkCardPoolUpgrade(wave: number) {
if (!this.cardPoolUpgradeWaves || this.cardPoolUpgradeWaves.length === 0) return;
const upgradeIndex = this.cardPoolUpgradeWaves.indexOf(wave);
if (upgradeIndex !== -1) {
// 根据配置的索引,计算目标等级(初始等级 + index + 1
// 例如 index=0对应等级为2index=1对应等级为3
const targetLv = upgradeIndex + 2;
oops.message.dispatchEvent(GameEvent.CardPoolUpgrade, { wave, targetLv });
mLogger.log(this.debugMode, 'MissionComp', "card pool upgrade event pushed", { wave, targetLv });
}
}
// ======================== 怪物数量管理 ========================

View File

@@ -1,29 +1,24 @@
/**
* @file MissionHeroComp.ts
* @description 英雄召唤与合成管理组件(逻辑层 + 视图层)
* @description 英雄召唤管理组件(逻辑层 + 视图层)
*
* 职责:
* 1. 处理 **英雄召唤**:接收 CallHero 事件 → 通过串行队列执行召唤。
* 2. 处理 **英雄合成**:检测同 UUID 同等级英雄是否达到合成条件 →
* 执行合成动画 → 销毁素材 → 生成高一级英雄。
* 3. 支持 **链式合成**:合成完成后自动检测更高等级是否也满足合成条件。
* 4. 管理英雄的出生点和掉落动画。
* 2. 管理英雄的出生点和掉落动画。
*
* 关键设计:
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**
* 避免同帧并发导致合成判断错误
* - handleSingleSummon() 在每次召唤后检测是否触发合成。
* - mergeGroupHeroes() 执行完整合成流程
* 聚合属性 → 向出生点汇聚动画 → 爆点特效 → 生成高级英雄
* - merge_need_count 控制合成所需数量2 合 1 或 3 合 1
* - merge_max_lv 控制合成上限等级。
* - summon_queue + processSummonQueue() 确保召唤请求 **串行处理**
* - handleSingleSummon() 仅负责生成英雄英雄升级由升级卡系统MissionCardComp独立处理
*
* 历史
* 旧版本曾包含"三合一合成 + 链式合成"机制,已移除
* 英雄等级提升现在仅通过升级卡SpecialUpgrade实现
*
* 依赖:
* - Herohero/Hero.ts—— 英雄 ECS 实体类
* - HeroAttrsComp —— 英雄属性组件
* - HeroInfo / HeroPos / HTypeheroSet—— 英雄静态配置
* - FightSet —— 战斗常量MERGE_NEED / MERGE_MAX
* - oneCom —— 一次性特效组件(控制爆点特效生命周期)
* - FightSet —— 战斗常量
*/
import { _decorator, instantiate, Prefab, v3, Vec3, BoxCollider2D } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
@@ -36,17 +31,16 @@ import { HeroInfo, HeroPos, HType } from "../common/config/heroSet";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { FacSet, FightSet, BoxSet } from "../common/config/GameSet";
import { oneCom } from "../skill/oncend";
import { HeroViewComp } from "../hero/HeroViewComp";
import { FieldSkillSet, FieldSkillType } from "../common/config/SkillSet";
import { MoveComp } from "../hero/MoveComp";
const { ccclass } = _decorator;
/**
* MissionHeroComp —— 英雄召唤与合成管理器
* MissionHeroComp —— 英雄召唤管理器
*
* 管理英雄的召唤请求队列出生动画和合成系统
* 合成支持 2 合 1 或 3 合 1且可链式合成至上限等级
* 管理英雄的召唤请求队列出生动画。
* 英雄升级由 MissionCardComp 的升级卡系统统一处理
*/
@ccclass('MissionHeroComp')
@ecs.register('MissionHeroComp', false)
@@ -76,15 +70,9 @@ export class MissionHeroComp extends CCComp {
current_hero_uuid:number=0
/** 当前英雄数量缓存 */
current_hero_num:number=-1
/** 合成规则需要几个同级英雄才能合成2 或 3 */
merge_need_count:number=FightSet.MERGE_NEED
/** 允许合成的最高等级(合成产物不超过此等级) */
merge_max_lv:number=FightSet.MERGE_MAX
/** 是否正在执行一次合成流程(防止并发) */
is_merging:boolean=false
/** 是否正在消费召唤队列(防止并发) */
is_processing_queue:boolean=false
/** 召唤请求队列:保证召唤与合成按顺序串行执行 */
/** 召唤请求队列:保证召唤按顺序串行执行 */
summon_queue:{ uuid: number; hero_lv: number; pool_lv: number }[]=[]
/** 预留英雄列表 */
heros:any=[]
@@ -134,8 +122,7 @@ export class MissionHeroComp extends CCComp {
view.alive();
const posIndex = this.pickPositionIndexForHero([hero.eid]);
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
// 不再直接设置位置,而是播放下落入场动画
// 计算出出生点(空中)
// 计算出生点(空中)
const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
view.node.setPosition(spawnPos);
model.posIndex = posIndex;
@@ -172,7 +159,7 @@ export class MissionHeroComp extends CCComp {
/**
* 动态分配英雄上场的位置
* @param excludeEids 排除计算的实体ID数组避免复活或合成时把自己算成占据的位置)
* @param excludeEids 排除计算的实体ID数组避免复活时把自己算成占据的位置
*/
private pickPositionIndexForHero(excludeEids: number[] = []): number {
const heroes = this.getAllHeroes().filter(h => {
@@ -205,11 +192,10 @@ export class MissionHeroComp extends CCComp {
*
* @param uuid 英雄 UUID
* @param hero_lv 英雄等级
* @param pool_lv 卡池等级
* @param pool_lv 卡池等级(历史遗留,新机制下不再使用)
* @returns 创建的 Hero 实体
*/
private addHero(uuid:number=1001,hero_lv:number=1, pool_lv:number=1) {
console.log("addHero uuid:",uuid)
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
const posIndex = this.pickPositionIndexForHero();
@@ -229,56 +215,6 @@ export class MissionHeroComp extends CCComp {
return hero;
}
/**
* 生成合成后的高级英雄,并覆盖为聚合后的属性。
*
* @param uuid 英雄 UUID
* @param hero_lv 合成后等级
* @param pool_lv 卡池等级
* @param ap 聚合后攻击力
* @param hp_max 聚合后最大生命值
* @param targetPos 指定生成位置
* @returns 实际生成的英雄等级
*/
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number, targetPosIndex?: number, targetPos?: Vec3): number {
console.log("addMergedHero uuid:",uuid)
let hero = ecs.getEntity<Hero>(Hero);
let scale = 1
let posIndex = targetPosIndex;
let landingPos = targetPos;
if (posIndex === undefined || posIndex < 0 || !landingPos) {
posIndex = this.pickPositionIndexForHero();
landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
}
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv,posIndex);
// 召唤完成后,派发事件以更新英雄面板
const model = hero.get(HeroAttrsComp);
if (model) {
model.ap = Math.max(0, ap);
model.hp_max = Math.max(1, hp_max);
model.hp = model.hp_max;
model.dirty_hp = true;
// 获取视图组件触发升级特效(包含描边更新)
const view = hero.get(HeroViewComp);
if (view && typeof view['lv_up'] === 'function') {
view['lv_up']();
}
oops.message.dispatchEvent(GameEvent.MasterCalled, {
eid: hero.eid,
model: model
});
return model.lv;
}
return hero_lv;
}
// ======================== 英雄查询 ========================
/** 获取当前全部友方英雄 ECS 实体列表(包括存活和墓地) */
@@ -293,62 +229,6 @@ export class MissionHeroComp extends CCComp {
return heroes;
}
/**
* 从存活英雄中挑选可参与本次合成的英雄组。
*
* @param aliveHeroes 存活英雄列表
* @param uuid 目标英雄 UUID
* @param hero_lv 目标等级
* @param needCount 合成需要数量
* @returns 匹配的英雄数组(长度 = needCount 或不足)
*/
private pickMergeHeroes(aliveHeroes: Hero[], uuid: number, hero_lv: number, needCount: number = 3): Hero[] {
const mergeHeroes: Hero[] = [];
for (let i = 0; i < aliveHeroes.length; i++) {
const model = aliveHeroes[i].get(HeroAttrsComp);
if (!model) continue;
if (model.hero_uuid !== uuid) continue;
if (model.lv !== hero_lv) continue;
mergeHeroes.push(aliveHeroes[i]);
if (mergeHeroes.length === needCount) break;
}
return mergeHeroes;
}
/** 统计满足同 UUID 同等级的可合成英雄数量 */
private countMergeHeroes(aliveHeroes: Hero[], uuid: number, hero_lv: number): number {
let count = 0;
for (let i = 0; i < aliveHeroes.length; i++) {
const model = aliveHeroes[i].get(HeroAttrsComp);
if (!model) continue;
if (model.hero_uuid !== uuid) continue;
if (model.lv !== hero_lv) continue;
count += 1;
}
return count;
}
// ======================== 合成规则 ========================
/**
* 读取合成所需数量(仅支持 2 或 3
* 由 FightSet.MERGE_NEED 配置。
*/
private getMergeNeedCount(): number {
return this.merge_need_count === 2 ? 2 : 3;
}
/**
* 判断该等级是否还能继续向上合成。
* @param hero_lv 当前等级
* @returns true = 可以合成(未达上限)
*/
private canMergeLevel(hero_lv: number): boolean {
return hero_lv < Math.max(1, this.merge_max_lv);
}
// ======================== 召唤队列 ========================
/**
@@ -371,159 +251,16 @@ export class MissionHeroComp extends CCComp {
}
/**
* 处理单次召唤:
* 1. 生成英雄。
* 2. 检测是否满足合成条件。
* 3. 满足则执行合成 + 链式合成。
* 处理单次召唤:仅生成英雄,不再触发合成。
*
* @param uuid 英雄 UUID
* @param hero_lv 英雄等级
* @param pool_lv 卡池等级
* @param pool_lv 卡池等级(历史遗留)
*/
private async handleSingleSummon(uuid: number, hero_lv: number, pool_lv: number = 1) {
this.addHero(uuid, hero_lv, pool_lv);
if (!this.canMergeLevel(hero_lv)) return;
const needCount = this.getMergeNeedCount();
const aliveHeroes = this.getAllHeroes();
const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, hero_lv, needCount);
if (mergeHeroes.length !== needCount) return;
this.is_merging = true;
try {
const mergedLv = await this.mergeGroupHeroes(mergeHeroes, uuid, hero_lv, pool_lv);
await this.tryChainMerge(uuid, mergedLv, pool_lv);
} finally {
this.is_merging = false;
}
}
// ======================== 合成动画 ========================
/**
* 将一组合成素材英雄向出生点汇聚并销毁。
* 所有素材动画完成后 Promise resolve。
*
* @param mergeHeroes 合成素材英雄数组
* @param spawnPos 汇聚目标位置
*/
private mergeDestroyAtBirth(mergeHeroes: Hero[], spawnPos: Vec3): Promise<void> {
return new Promise((resolve) => {
let doneCount = 0;
const total = mergeHeroes.length;
if (total <= 0) {
resolve();
return;
}
const onDone = () => {
doneCount += 1;
if (doneCount >= total) {
resolve();
}
};
for (let i = 0; i < mergeHeroes.length; i++) {
mergeHeroes[i].mergeToBirthAndDestroy(spawnPos, onDone);
}
});
}
/**
* 播放合成爆点特效(使用 oneCom 控制生命周期)。
* 延迟 0.4 秒后 resolve。
*
* @param worldPos 特效播放位置
*/
private playMergeBoomFx(worldPos: Vec3): Promise<void> {
return new Promise((resolve) => {
const scene = smc.map?.MapView?.scene;
const layer = scene?.entityLayer?.node;
if (!layer || !layer.isValid) {
resolve();
return;
}
const prefab: Prefab = oops.res.get("game/skill/end/dead", Prefab)!;
if (!prefab) {
resolve();
return;
}
const fx = instantiate(prefab);
if (!fx || !fx.isValid) {
resolve();
return;
}
fx.parent = layer;
fx.setPosition(worldPos);
fx.getComponent(oneCom) || fx.addComponent(oneCom);
this.scheduleOnce(() => resolve(), 0.4);
});
}
/**
* 执行一次完整合成流程:
* 1. 聚合素材的 AP 和 HP。
* 2. 将素材向出生点汇聚并销毁。
* 3. 播放爆点特效。
* 4. 生成高一级英雄(属性为聚合值)。
*
* @param mergeHeroes 合成素材
* @param uuid 英雄 UUID
* @param hero_lv 素材等级
* @param pool_lv 卡池等级
* @returns 合成产物的实际等级
*/
private async mergeGroupHeroes(mergeHeroes: Hero[], uuid: number, hero_lv: number, pool_lv: number): Promise<number> {
// 聚合属性
let sumAp = 0;
let sumHpMax = 0;
const mergeEids = [];
for (let i = 0; i < mergeHeroes.length; i++) {
const model = mergeHeroes[i].get(HeroAttrsComp);
mergeEids.push(mergeHeroes[i].eid);
if (!model) continue;
sumAp += model.ap;
sumHpMax += model.hp_max;
}
const posIndex = this.pickPositionIndexForHero(mergeEids);
const landingPos = MissionHeroComp.HERO_POSITIONS[posIndex];
const spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroComp.HERO_DROP_HEIGHT, 0);
// 汇聚 → 特效 → 生成
await this.mergeDestroyAtBirth(mergeHeroes, spawnPos);
await this.playMergeBoomFx(spawnPos);
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax, posIndex, landingPos);
}
/**
* 链式合成:合成完成后继续检测更高等级是否也满足条件。
* 最多循环 20 次作为安全上限。
*
* @param uuid 英雄 UUID
* @param startLv 起始检测等级
* @param pool_lv 卡池等级
*/
private async tryChainMerge(uuid: number, startLv: number, pool_lv: number) {
let checkLv = Math.max(1, startLv);
const needCount = this.getMergeNeedCount();
let guard = 0;
while (guard < 20) {
guard += 1;
if (!this.canMergeLevel(checkLv)) {
break;
}
const aliveHeroes = this.getAllHeroes();
const sameCount = this.countMergeHeroes(aliveHeroes, uuid, checkLv);
if (sameCount < needCount) {
break;
}
const mergeHeroes = this.pickMergeHeroes(aliveHeroes, uuid, checkLv, needCount);
if (mergeHeroes.length < needCount) {
break;
}
checkLv = await this.mergeGroupHeroes(mergeHeroes, uuid, checkLv, pool_lv);
}
}
/** ECS 组件移除时触发(当前不销毁节点,保留引用) */
reset() {