refactor(hero): 移除天赋系统和相关属性,简化英雄架构
- 删除 SCDSystem、HeroAttrSystem 等独立系统,将功能整合到现有组件 - 移除 TalComp 天赋组件及相关配置(TalSet、AttrSet、CardSet) - 清理 HeroAttrs 中未使用的属性枚举,保留核心战斗属性 - 简化 Hero 实体创建逻辑,不再为主角挂载天赋组件 - 移除 SingletonModuleComp 中与天赋、经验、收集相关的数据管理
This commit is contained in:
@@ -5,12 +5,7 @@ import { GameMap } from "../map/GameMap";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
|
||||
import { GameEvent } from "./config/GameEvent";
|
||||
import * as exp from "constants";
|
||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||
import { Attrs, GameScoreStats } from "./config/HeroAttrs";
|
||||
import { count, time } from "console";
|
||||
import { getLevelExp } from "../map/RogueConfig";
|
||||
import { FightSet } from "./config/GameSet";
|
||||
import { GameScoreStats } from "./config/HeroAttrs";
|
||||
import { mLogger } from "./Logger";
|
||||
/**
|
||||
* 用远程数据覆盖本地数据(统一方法)
|
||||
@@ -107,126 +102,13 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
elite_kill_count: 0, // 精英怪击杀数量
|
||||
boss_kill_count: 0, // Boss击杀数
|
||||
} as GameScoreStats,
|
||||
hero:{
|
||||
name:'',
|
||||
path:'',
|
||||
as:0,
|
||||
type:0,
|
||||
lv:1,
|
||||
exp:0,
|
||||
exp_max:100,
|
||||
exp_pre:0,
|
||||
hp:50,
|
||||
hp_max:100,
|
||||
mp:50,
|
||||
mp_max:100,
|
||||
def:0,
|
||||
ap:0,
|
||||
crt:0,
|
||||
dodge:0,
|
||||
skills:[],
|
||||
buff:[],
|
||||
tal:[],
|
||||
info:'',
|
||||
},
|
||||
// 收集记录
|
||||
collection: {
|
||||
talents: {} as Record<number, number>,
|
||||
skill: {uuid:0,count:0},
|
||||
friend:{uuid:0,count:0},
|
||||
},
|
||||
|
||||
gold: 0, // 金币数据(MVVM绑定字段)
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 全局属性加成 {attrIndex: [value, count]}
|
||||
global_attrs: Record<number, [number, number]> = {
|
||||
[Attrs.AP]: [1, 0], // 攻击力
|
||||
[Attrs.HP_MAX]: [10, 10], // 生命上限
|
||||
[Attrs.DEF]: [1, 0], // 防御
|
||||
[Attrs.DODGE]: [1, 0], // 闪避率
|
||||
[Attrs.CRITICAL]: [1, 0], // 暴击率
|
||||
[Attrs.STUN_CHANCE]: [1, 0], // 眩晕率
|
||||
[Attrs.WFUNY]: [1, 0], // 风怒率
|
||||
};
|
||||
|
||||
/** 主角实体引用 */
|
||||
// role: ecs.Entity | null = null;
|
||||
|
||||
/**
|
||||
* 记录天赋获取
|
||||
* @param id 天赋ID
|
||||
*/
|
||||
addTalentRecord(id: number) {
|
||||
if (!this.vmdata.collection.talents[id]) {
|
||||
this.vmdata.collection.talents[id] = 0;
|
||||
}
|
||||
this.vmdata.collection.talents[id]++;
|
||||
mLogger.log(this.debugMode, 'SMC', `[SMC] 记录天赋获取: ID=${id}, 次数=${this.vmdata.collection.talents[id]}`);
|
||||
oops.message.dispatchEvent(GameEvent.UpdateCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录技能获取
|
||||
* @param id 技能ID
|
||||
*/
|
||||
addSkillRecord(id: number) {
|
||||
if (!this.vmdata.collection.skill.uuid) {
|
||||
this.vmdata.collection.skill.uuid = id;
|
||||
}
|
||||
this.vmdata.collection.skill.count++;
|
||||
mLogger.log(this.debugMode, 'SMC', `[SMC] 记录技能获取: ID=${id}, 次数=${this.vmdata.collection.skill.count}`);
|
||||
oops.message.dispatchEvent(GameEvent.UpdateCollection);
|
||||
}
|
||||
/**
|
||||
* 记录好友获取
|
||||
* @param id 好友ID
|
||||
*/
|
||||
addFriendHero(id: number) {
|
||||
if (!this.vmdata.collection.friend.uuid) {
|
||||
this.vmdata.collection.friend.uuid = id;
|
||||
}
|
||||
this.vmdata.collection.friend.count++;
|
||||
mLogger.log(this.debugMode, 'SMC', `[SMC] 记录好友获取: ID=${id}, 次数=${this.vmdata.collection.friend.count}`);
|
||||
oops.message.dispatchEvent(GameEvent.UpdateCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加经验并处理升级逻辑
|
||||
* @param exp 获得的经验值
|
||||
*/
|
||||
addExp(exp: number) {
|
||||
if (exp <= 0) return;
|
||||
|
||||
const h = this.vmdata.hero;
|
||||
// 确保等级至少为1
|
||||
if (h.lv < 1) h.lv = 1;
|
||||
// 确保经验上限正确
|
||||
if (h.exp_max <= 0) h.exp_max = getLevelExp(h.lv);
|
||||
|
||||
h.exp += exp;
|
||||
|
||||
// 检查升级
|
||||
let isLevelUp = false;
|
||||
while (h.exp >= h.exp_max) {
|
||||
h.exp -= h.exp_max;
|
||||
h.lv++;
|
||||
isLevelUp = true;
|
||||
|
||||
// 更新下一级所需经验
|
||||
h.exp_max = getLevelExp(h.lv);
|
||||
|
||||
mLogger.log(this.debugMode, 'SMC', `[SMC] 升级! Lv.${h.lv - 1} -> Lv.${h.lv}, 下级所需: ${h.exp_max}`);
|
||||
}
|
||||
h.exp_pre=Math.round(h.exp/h.exp_max*100)
|
||||
if (isLevelUp) {
|
||||
mLogger.log(this.debugMode, 'SMC', `[SMC] 触发升级事件: Lv.${h.lv}`);
|
||||
// 发送升级事件
|
||||
oops.message.dispatchEvent(GameEvent.CanUpdateLv, { lv: h.lv });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vmAdd() {
|
||||
VM.add(this.vmdata, "data");
|
||||
// mLogger.log(this.debugMode, 'SMC', "[MissionComp]局内数据初始化",smc.vmdata.mission_data)
|
||||
@@ -332,19 +214,7 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
|
||||
return true
|
||||
}
|
||||
// 设置单个出战英雄
|
||||
updateFihgtHero(heroId: number) {
|
||||
this.fight_hero = heroId;
|
||||
if(this.isWxClient()){
|
||||
let res = this.updateCloudData()
|
||||
if (res){
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
updateGold(gold:number, is_sync: boolean = true){
|
||||
this.vmdata.gold += gold;
|
||||
if(this.isWxClient() && is_sync){
|
||||
@@ -361,56 +231,6 @@ export class SingletonModuleComp extends ecs.Comp {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新主角英雄数据到 VM
|
||||
* @param heroAttrs 英雄属性组件
|
||||
*/
|
||||
updateHeroInfo(heroAttrs: HeroAttrsComp) {
|
||||
if (!heroAttrs || !heroAttrs.is_master) return;
|
||||
|
||||
const h = this.vmdata.hero;
|
||||
|
||||
// 基础信息
|
||||
h.name = heroAttrs.hero_name;
|
||||
h.type = heroAttrs.type;
|
||||
|
||||
// 防止 ECS 旧数据覆盖 VM 新数据 (如果 VM 里的等级更高,说明刚升级还没同步到 ECS)
|
||||
if (heroAttrs.lv > h.lv) {
|
||||
h.lv = heroAttrs.lv;
|
||||
} else if (h.lv > heroAttrs.lv) {
|
||||
// 此时应该反向同步?或者等待 CanUpdateLv 事件处理
|
||||
// 这里暂时保持 VM 的高等级,不被 ECS 覆盖
|
||||
} else {
|
||||
h.lv = heroAttrs.lv;
|
||||
}
|
||||
|
||||
// 动态属性
|
||||
h.hp = Math.floor(heroAttrs.hp);
|
||||
h.mp = Math.floor(heroAttrs.mp);
|
||||
|
||||
// 计算属性
|
||||
h.hp_max = Math.floor(heroAttrs.Attrs[Attrs.HP_MAX] || 0);
|
||||
h.mp_max = Math.floor(heroAttrs.Attrs[Attrs.MP_MAX] || 0);
|
||||
h.def = Math.floor(heroAttrs.Attrs[Attrs.DEF] || 0);
|
||||
h.ap = Math.floor(heroAttrs.Attrs[Attrs.AP] || 0);
|
||||
h.dodge = Math.floor(heroAttrs.Attrs[Attrs.DODGE] || 0);
|
||||
h.crt = Math.floor(heroAttrs.Attrs[Attrs.CRITICAL] || 0);
|
||||
h.as = Math.floor(heroAttrs.Attrs[Attrs.AS] || 0);
|
||||
|
||||
// 强制触发 VM 更新
|
||||
// 如果 VM 监听的是 smc.vmdata.hero 的属性变化,上面的赋值应该有效。
|
||||
// 但如果 UI 绑定的是 hero 整体对象,或者因为深层监听问题,可能需要手动通知。
|
||||
// 为了保险,我们可以重新赋值一次(如果是对象引用可能不会触发),或者使用 VM 提供的 set 方法
|
||||
// 这里尝试直接重新赋值整个对象属性来触发更新,或者假设 VM 已经处理好了深层监听。
|
||||
// 如果 UI 没变,可能是 VM 没有检测到深层属性变化。
|
||||
|
||||
// 尝试手动通知或重新赋值关键路径
|
||||
// 注意:Oops Framework 的 VM 通常支持对象属性修改的监听,前提是初始化时已经建立了监听。
|
||||
// 这里我们尝试显式调用 VM.modify 来通知更新(如果有这个 API),或者重新赋值给 vmdata
|
||||
|
||||
// 方案:重新设置 vmdata.hero 来触发根节点的更新通知
|
||||
this.vmdata.hero = h;
|
||||
}
|
||||
error(){
|
||||
oops.gui.toast("数据处理异常,请重试或重新登录")
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Attrs } from "./HeroAttrs";
|
||||
|
||||
export interface AttrInfo {
|
||||
uuid: number;
|
||||
icon:string; // 属性图标
|
||||
attr: Attrs; // 属性类型
|
||||
value: number;// 属性值
|
||||
desc: string;// 属性描述
|
||||
isSpecial: boolean;// 是否为特殊属性
|
||||
name?: string;// 属性备注
|
||||
}
|
||||
export const AttrCards: Record<number, AttrInfo> = {
|
||||
//*一阶 */
|
||||
2001:{uuid:2001, icon:"1020", attr: Attrs.AP, value: 20, desc: "攻击力 +20%", isSpecial: false,name: "攻击" },
|
||||
2002:{uuid:2002, icon:"1020", attr: Attrs.HP_MAX, value: 20, desc: "生命上限 +20%", isSpecial: false,name: "生命" },
|
||||
2003:{uuid:2003, icon:"1020", attr: Attrs.DEF, value: 20, desc: "防御力 +20%", isSpecial: false,name: "防御" },
|
||||
2004:{uuid:2004, icon:"1020", attr: Attrs.AS, value: 5, desc: "攻击速度 +5%", isSpecial: false,name: "攻速" },
|
||||
2005:{uuid:2007, icon:"1020", attr: Attrs.CRITICAL_DMG, value: 10, desc: "暴击伤害 +10%", isSpecial: true,name: "爆伤" },
|
||||
2006:{uuid:2013, icon:"1020", attr: Attrs.DODGE, value: 5, desc: "闪避率 +5%", isSpecial: true,name: "闪避" },
|
||||
|
||||
}
|
||||
|
||||
export interface PotionInfo extends AttrInfo {
|
||||
duration: number; // 持续时间
|
||||
}
|
||||
|
||||
export const PotionCards: Record<number, PotionInfo> = {
|
||||
// 持续时间20秒的强力药水
|
||||
3001: { uuid: 3001, icon: "1020", attr: Attrs.AP, value: 100, desc: "15秒内攻击力 +100%", isSpecial: false,name: "狂暴药水", duration: 15 },
|
||||
3002: { uuid: 3002, icon: "1020", attr: Attrs.AS, value: 100, desc: "15秒内攻速 +100%", isSpecial: false,name: "急速药水", duration: 15 },
|
||||
3003: { uuid: 3003, icon: "1020", attr: Attrs.DEF, value: 100, desc: "15秒内防御 +100%", isSpecial: false,name: "防御药水", duration: 15 },
|
||||
3004: { uuid: 3004, icon: "1020", attr: Attrs.SPEED, value: 100, desc: "15秒内移速 +100%", isSpecial: false,name: "神行药水", duration: 15 },
|
||||
|
||||
// 持续时间60秒的普通药水
|
||||
3005: { uuid: 3005, icon: "1020", attr: Attrs.AP, value: 25, desc: "60秒内攻击力 +25%", isSpecial: false,name: "力量药剂", duration: 60 },
|
||||
3006: { uuid: 3006, icon: "1020", attr: Attrs.AS, value: 25, desc: "60秒内攻速 +25%", isSpecial: false,name: "敏捷药剂", duration: 60 },
|
||||
3007: { uuid: 3007, icon: "1020", attr: Attrs.DEF, value: 25, desc: "60秒内防御 +25%", isSpecial: false,name: "护甲药剂", duration: 60 },
|
||||
3008: { uuid: 3008, icon: "1020", attr: Attrs.SPEED, value: 25, desc: "60秒内移速 +25%", isSpecial: false,name: "轻灵药剂", duration: 60 },
|
||||
|
||||
// 闪避药水
|
||||
3009: { uuid: 3009, icon: "1020", attr: Attrs.DODGE, value: 100, desc: "20秒内闪避率 +100%", isSpecial: false,name: "残影药水", duration: 15 },
|
||||
3010: { uuid: 3010, icon: "1020", attr: Attrs.DODGE, value: 25, desc: "60秒内闪避率 +25%", isSpecial: false,name: "闪避药剂", duration: 60 },
|
||||
// 回血药水
|
||||
3011: { uuid: 3011, icon: "1020", attr: Attrs.HP_REGEN, value: 30, desc: "每5秒回血生命最大值的30%", isSpecial: false,name: "生命药水", duration: 15 },
|
||||
3012: { uuid: 3012, icon: "1020", attr: Attrs.HP_REGEN, value: 10, desc: "每5秒回血生命最大值的10%", isSpecial: false,name: "回春药剂", duration: 60 },
|
||||
};
|
||||
|
||||
export const CanSelectAttrs: Record<number, number[]> = {
|
||||
// 1阶属性
|
||||
1: [2001, 2002, 2003, 2004, 2005, 2006],
|
||||
};
|
||||
|
||||
export const CanSelectPotions: Record<number, number[]> = {
|
||||
// 全药水
|
||||
1: [3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011, 3012]
|
||||
};
|
||||
@@ -1,372 +0,0 @@
|
||||
import { AttrCards, AttrInfo, CanSelectAttrs, PotionCards, CanSelectPotions } from "./AttrSet";
|
||||
import { talConf, ItalConf, CanSelectTalents } from "./TalSet";
|
||||
import { SkillSet, SkillConfig, CanSelectSkills } from "./SkillSet";
|
||||
import { HeroInfo, heroInfo, CanSelectHeros } from "./heroSet";
|
||||
import { CardType, CardKind } from "./GameSet";
|
||||
|
||||
/**
|
||||
* 获取等级对应的奖励类型
|
||||
* @param level 当前等级
|
||||
* @returns 奖励类型 CardType
|
||||
*/
|
||||
export function getLevelRewardType(level: number): CardType {
|
||||
switch (level) {
|
||||
case 2: return CardType.Talent;
|
||||
case 3: return CardType.Attr;
|
||||
case 4: return CardType.Attr;
|
||||
case 5: return CardType.Talent;
|
||||
case 6: return CardType.Attr;
|
||||
case 7: return CardType.Attr;
|
||||
case 8: return CardType.Attr;
|
||||
case 9: return CardType.Attr;
|
||||
case 10: return CardType.Talent;
|
||||
case 11: return CardType.Attr;
|
||||
case 12: return CardType.Attr;
|
||||
case 13: return CardType.Attr;
|
||||
case 14: return CardType.Attr;
|
||||
case 15: return CardType.Talent;
|
||||
case 16: return CardType.Attr;
|
||||
case 17: return CardType.Attr;
|
||||
case 18: return CardType.Attr;
|
||||
case 19: return CardType.Attr;
|
||||
case 20: return CardType.Attr;
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一卡牌信息接口 (用于UI显示和逻辑处理)
|
||||
*/
|
||||
export interface ICardInfo {
|
||||
uuid: number;
|
||||
type: CardType;
|
||||
kind: CardKind;
|
||||
name: string;
|
||||
desc: string;
|
||||
icon: string;
|
||||
weight: number; // 抽取权重
|
||||
tag?: string; // 标签 (如 "special" 表示特殊属性)
|
||||
payload: any; // 原始配置数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体卡牌配置项 (内部使用)
|
||||
*/
|
||||
export interface IPoolItem {
|
||||
id: number; // 卡牌UUID
|
||||
weight: number; // 该卡牌在池中的权重
|
||||
}
|
||||
|
||||
/**
|
||||
* 等级池配置项
|
||||
* 仅定义类型和权重,具体卡牌内容由各模块的 CanSelectXXX 配置决定
|
||||
*/
|
||||
export interface IPoolConfig {
|
||||
type: CardType; // 卡牌类型
|
||||
poolWeight: number; // 该类型池被选中的概率权重
|
||||
tag?: string; // 辅助筛选(从全池中筛选带tag的,如 "special")
|
||||
}
|
||||
|
||||
// 默认单卡权重
|
||||
const DEFAULT_CARD_WEIGHT = 100;
|
||||
|
||||
/**
|
||||
* 1-20 级卡牌池配置表
|
||||
* 定义每个等级可能出现的卡牌类型及其权重
|
||||
*/
|
||||
export const LevelPoolConfigs: Record<number, IPoolConfig[]> = {
|
||||
1: [{ type: CardType.Skill, poolWeight: 100 }],
|
||||
2: [{ type: CardType.Attr, poolWeight: 100 }], // 常规属性
|
||||
3: [{ type: CardType.Talent, poolWeight: 50 }, { type: CardType.Attr, poolWeight: 50, tag: "special" }], // 天赋或特殊属性
|
||||
4: [{ type: CardType.Attr, poolWeight: 100 }],
|
||||
5: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||
6: [{ type: CardType.Partner, poolWeight: 100 }], // 伙伴节点
|
||||
7: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }],
|
||||
8: [{ type: CardType.Attr, poolWeight: 80 }, { type: CardType.Skill, poolWeight: 20 }],
|
||||
9: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
|
||||
10: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||
11: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }],
|
||||
12: [{ type: CardType.Attr, poolWeight: 70 }, { type: CardType.Skill, poolWeight: 30 }],
|
||||
13: [{ type: CardType.Attr, poolWeight: 100 }],
|
||||
14: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
|
||||
15: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||
16: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }],
|
||||
17: [{ type: CardType.Attr, poolWeight: 60 }, { type: CardType.Skill, poolWeight: 40 }],
|
||||
18: [{ type: CardType.Attr, poolWeight: 50, tag: "special" }, { type: CardType.Talent, poolWeight: 50 }],
|
||||
19: [{ type: CardType.Attr, poolWeight: 100 }],
|
||||
20: [{ type: CardType.Talent, poolWeight: 100 }],
|
||||
};
|
||||
|
||||
// ========== 卡牌池构建逻辑 ==========
|
||||
|
||||
/**
|
||||
* 获取指定类型的卡牌信息(不含权重,仅基础信息)
|
||||
*/
|
||||
function getCardBaseInfo(type: CardType, uuid: number): ICardInfo | null {
|
||||
let baseInfo: any = null;
|
||||
let name = "";
|
||||
let desc = "";
|
||||
let icon = "";
|
||||
let kind = CardKind.Attr;
|
||||
let tag = undefined;
|
||||
|
||||
switch (type) {
|
||||
case CardType.Attr:
|
||||
baseInfo = AttrCards[uuid];
|
||||
if (!baseInfo) return null;
|
||||
name = baseInfo.name || "属性";
|
||||
desc = baseInfo.desc;
|
||||
icon = baseInfo.icon;
|
||||
kind = CardKind.Attr;
|
||||
tag = baseInfo.isSpecial ? "special" : undefined;
|
||||
break;
|
||||
case CardType.Talent:
|
||||
baseInfo = talConf[uuid];
|
||||
if (!baseInfo) return null;
|
||||
name = baseInfo.name;
|
||||
desc = baseInfo.desc;
|
||||
icon = baseInfo.icon;
|
||||
kind = baseInfo.kind;
|
||||
break;
|
||||
case CardType.Skill:
|
||||
baseInfo = SkillSet[uuid];
|
||||
if (!baseInfo) return null;
|
||||
name = baseInfo.name;
|
||||
desc = baseInfo.info;
|
||||
icon = baseInfo.icon;
|
||||
kind = CardKind.Skill;
|
||||
break;
|
||||
case CardType.Partner:
|
||||
baseInfo = HeroInfo[uuid];
|
||||
if (!baseInfo) return null;
|
||||
name = baseInfo.name;
|
||||
desc = baseInfo.info;
|
||||
icon = baseInfo.icon;
|
||||
kind = CardKind.Partner;
|
||||
break;
|
||||
case CardType.Potion:
|
||||
baseInfo = PotionCards[uuid];
|
||||
if (!baseInfo) return null;
|
||||
name = baseInfo.name || "药水";
|
||||
desc = baseInfo.desc;
|
||||
icon = baseInfo.icon;
|
||||
kind = CardKind.Buff; // 药水归类为 Buff 类型的卡片显示
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
uuid,
|
||||
type,
|
||||
kind,
|
||||
name,
|
||||
desc,
|
||||
icon,
|
||||
weight: 0, // 基础信息不包含权重,权重由配置决定
|
||||
tag,
|
||||
payload: baseInfo
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的全量池
|
||||
* 自动累加所有 <= level 的配置项
|
||||
* @param type 卡牌类型
|
||||
* @param level 当前等级
|
||||
*/
|
||||
function getDefaultPool(type: CardType, level: number = 1): IPoolItem[] {
|
||||
const items: IPoolItem[] = [];
|
||||
let configMap: Record<number, number[]> | null = null;
|
||||
let defaultWeight = 100;
|
||||
|
||||
switch (type) {
|
||||
case CardType.Attr:
|
||||
configMap = CanSelectAttrs;
|
||||
defaultWeight = 100;
|
||||
break;
|
||||
case CardType.Talent:
|
||||
configMap = CanSelectTalents;
|
||||
defaultWeight = 50;
|
||||
break;
|
||||
case CardType.Skill:
|
||||
configMap = CanSelectSkills;
|
||||
defaultWeight = 80;
|
||||
break;
|
||||
case CardType.Partner:
|
||||
configMap = CanSelectHeros;
|
||||
defaultWeight = 100;
|
||||
break;
|
||||
case CardType.Potion:
|
||||
configMap = CanSelectPotions;
|
||||
defaultWeight = 100;
|
||||
break;
|
||||
}
|
||||
|
||||
if (configMap) {
|
||||
// 1. 找到符合条件的最大等级 Key (<= level 且 != 99)
|
||||
let targetKey = -1;
|
||||
let maxLv = -1;
|
||||
|
||||
Object.keys(configMap).forEach(lvlStr => {
|
||||
const lv = parseInt(lvlStr);
|
||||
if (lv !== 99 && lv <= level) {
|
||||
if (lv > maxLv) {
|
||||
maxLv = lv;
|
||||
targetKey = lv;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 如果找到了目标等级配置,则使用该配置
|
||||
if (targetKey !== -1 && configMap[targetKey]) {
|
||||
const ids = configMap[targetKey];
|
||||
ids.forEach(id => {
|
||||
items.push({ id, weight: defaultWeight });
|
||||
});
|
||||
}
|
||||
// 3. 如果没找到(等级过低),且存在 99 号默认配置,则使用 99 号配置
|
||||
else if (configMap[99]) {
|
||||
configMap[99].forEach(id => {
|
||||
items.push({ id, weight: defaultWeight });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 兜底逻辑:如果该类型没有配置表 (理论上不应发生,除非新增类型未配置)
|
||||
// 这里保留原有的全量兜底逻辑作为最后的防线
|
||||
switch (type) {
|
||||
case CardType.Attr:
|
||||
Object.keys(AttrCards).forEach(key => items.push({ id: Number(key), weight: 100 }));
|
||||
break;
|
||||
case CardType.Talent:
|
||||
Object.keys(talConf).forEach(key => items.push({ id: Number(key), weight: 50 }));
|
||||
break;
|
||||
case CardType.Skill:
|
||||
Object.keys(SkillSet).forEach(key => items.push({ id: Number(key), weight: 80 }));
|
||||
break;
|
||||
case CardType.Partner:
|
||||
Object.keys(HeroInfo).forEach(key => {
|
||||
const id = Number(key);
|
||||
if (id < 5200) items.push({ id, weight: 100 });
|
||||
});
|
||||
break;
|
||||
case CardType.Potion:
|
||||
Object.keys(PotionCards).forEach(key => items.push({ id: Number(key), weight: 100 }));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据等级获取随机卡牌选项
|
||||
* @param level 当前等级
|
||||
* @param count 选项数量 (默认3个)
|
||||
* @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有)
|
||||
* @param forcedType 强制指定卡牌类型 (用于特殊获取,如商店、技能书等)
|
||||
* @param preferredAttrs 偏好的属性类型列表 (用于增加权重)
|
||||
*/
|
||||
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = [], forcedType?: CardType, preferredAttrs: number[] = []): ICardInfo[] {
|
||||
// 1. 确定类型:强制指定 > 默认为属性
|
||||
const type = forcedType !== undefined ? forcedType : CardType.Attr;
|
||||
|
||||
const result: ICardInfo[] = [];
|
||||
const excludeSet = new Set(excludeUuids);
|
||||
|
||||
// 2. 获取该类型的候选池
|
||||
const rawItems = getDefaultPool(type, level);
|
||||
|
||||
// 3. 构建候选列表(包含完整信息)
|
||||
const candidates: ICardInfo[] = [];
|
||||
for (const item of rawItems) {
|
||||
if (excludeSet.has(item.id)) continue;
|
||||
|
||||
const info = getCardBaseInfo(type, item.id);
|
||||
if (info) {
|
||||
info.weight = item.weight;
|
||||
|
||||
// 如果是属性卡,且该属性在偏好列表中,增加权重
|
||||
if (type === CardType.Attr && preferredAttrs.length > 0) {
|
||||
// AttrCards 的 payload 就是 AttrInfo,包含 attr 字段
|
||||
if (info.payload && preferredAttrs.includes(info.payload.attr)) {
|
||||
// 增加权重,这里设置为 2 倍
|
||||
info.weight *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
candidates.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 抽取
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (candidates.length === 0) break;
|
||||
|
||||
const card = weightedRandomCard(candidates);
|
||||
if (card) {
|
||||
result.push(card);
|
||||
// 移除已选,防止重复
|
||||
const idx = candidates.indexOf(card);
|
||||
if (idx > -1) candidates.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 兜底逻辑:如果数量不足,且当前类型不是属性,则用属性卡补齐
|
||||
if (result.length < count && type !== CardType.Attr) {
|
||||
const attrItems = getDefaultPool(CardType.Attr); // 兜底可以用全量属性池
|
||||
for (const item of attrItems) {
|
||||
if (result.length >= count) break;
|
||||
|
||||
// 检查是否已存在(ID去重)
|
||||
if (excludeSet.has(item.id) || result.some(r => r.uuid === item.id)) continue;
|
||||
|
||||
const info = getCardBaseInfo(CardType.Attr, item.id);
|
||||
if (info) {
|
||||
info.weight = 100;
|
||||
result.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shuffleArray(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ========== 工具函数 ==========
|
||||
|
||||
function weightedRandomPool(configs: IPoolConfig[]): IPoolConfig | null {
|
||||
if (!configs || configs.length === 0) return null;
|
||||
|
||||
const totalWeight = configs.reduce((sum, item) => sum + item.poolWeight, 0);
|
||||
let randomVal = Math.random() * totalWeight;
|
||||
|
||||
for (const config of configs) {
|
||||
randomVal -= config.poolWeight;
|
||||
if (randomVal <= 0) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
return configs[configs.length - 1];
|
||||
}
|
||||
|
||||
function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
|
||||
if (!cards || cards.length === 0) return null;
|
||||
|
||||
const totalWeight = cards.reduce((sum, item) => sum + item.weight, 0);
|
||||
let randomVal = Math.random() * totalWeight;
|
||||
|
||||
for (const card of cards) {
|
||||
randomVal -= card.weight;
|
||||
if (randomVal <= 0) {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
return cards[cards.length - 1];
|
||||
}
|
||||
|
||||
function shuffleArray(array: any[]) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,6 @@ export enum BType {
|
||||
export enum NeAttrs {
|
||||
IN_FROST = 0, // 冰冻状态
|
||||
IN_STUN = 1, // 眩晕状态
|
||||
IN_BURN = 2, // 灼烧状态
|
||||
IN_POISON = 3, // 中毒状态
|
||||
}
|
||||
|
||||
// ========== 属性枚举 ==========
|
||||
@@ -26,53 +24,27 @@ export enum NeAttrs {
|
||||
export enum Attrs {
|
||||
// ========== 基础生存属性 (0-9) ==========
|
||||
HP_MAX = 0, // 最大生命值
|
||||
MP_MAX = 1, // 最大魔法值
|
||||
SHIELD_MAX = 2, // 最大护盾值
|
||||
HP_REGEN = 3, // 生命回复
|
||||
MP_REGEN = 4, // 魔法回复
|
||||
LUCK = 5, // 幸运值
|
||||
SPEED = 3, // 移动速度
|
||||
|
||||
// ========== 攻击属性 (10-19) ==========
|
||||
AP = 10, // 攻击力
|
||||
DIS = 12, // 攻击距离
|
||||
AS = 13, // 攻击速度(减少技能skills[0]CD)
|
||||
SS = 14, // 技能速度 (减少skills[0] 以外的cd)
|
||||
SKILL_DURATION = 15, // 技能持续时间
|
||||
AREA_OF_EFFECT = 16, // 作用范围
|
||||
|
||||
// ========== 防御属性 (20-29) ==========
|
||||
DEF = 20, // 防御 伤害减免
|
||||
DODGE = 22, // 闪避率
|
||||
THORNS = 25, // 反伤
|
||||
CRITICAL_RES = 26, // 暴击抗性
|
||||
CON_RES = 27, // 控制抗性
|
||||
|
||||
// ========== 暴击与命中属性 (30-39) ==========
|
||||
CRITICAL = 30, // 暴击率
|
||||
CRITICAL_DMG = 31, // 暴击伤害
|
||||
HIT = 32, // 命中率
|
||||
|
||||
|
||||
// ========== 特殊效果属性 (50-59) ==========
|
||||
LIFESTEAL = 50, // 吸血比率
|
||||
MANASTEAL = 51, // 吸蓝比率
|
||||
FREEZE_CHANCE = 52, // 冰冻概率
|
||||
BURN_CHANCE = 53, // 燃烧概率
|
||||
STUN_CHANCE = 54, // 眩晕概率
|
||||
BACK_CHANCE = 55, // 击退概率
|
||||
SLOW_CHANCE = 56, // 减速概率
|
||||
POISON_CHANCE = 57, // 中毒概率
|
||||
SILENCE_CHANCE = 58, // 沉默概率
|
||||
EXPLOSION_CHANCE = 59, // 爆炸概率
|
||||
|
||||
// ========== 增益效果属性 (60-69) ==========
|
||||
BUFF_UP = 60, // Buff效果提升
|
||||
DBUFF_UP = 61, // Debuff效果提升
|
||||
SHIELD_UP = 62, // 护盾效果提升
|
||||
SPEED = 63, // 移动速度加成
|
||||
EXP_GAIN = 64, // 经验获取
|
||||
GOLD_GAIN = 65, // 金币获取
|
||||
DROP_CHANCE = 66, // 掉落率
|
||||
REVIVE_COUNT = 67, // 复活次数
|
||||
REVIVE_TIME = 68, // 复活时间
|
||||
INVINCIBLE_TIME = 69, // 无敌时间
|
||||
@@ -80,23 +52,14 @@ export enum Attrs {
|
||||
// ========== 武器进化相关 (70-79) ==========
|
||||
PUNCTURE = 70, // 穿刺次数
|
||||
PUNCTURE_DMG = 71, // 穿刺伤害
|
||||
MOVE_SPEED = 74, // 移动速度
|
||||
BURN = 75, // 易伤效果
|
||||
WFUNY = 77, // 风怒
|
||||
|
||||
// ========== 负面状态相关 (80-89) ==========
|
||||
DMG_INVUL = 80, //易伤
|
||||
// ========== 怪物特殊属性 (90-99)==========
|
||||
ATK_TRI_RESET = 90, // 英雄攻击触发计数重置
|
||||
DMG_TRI_RESET = 91, // 英雄伤害触发计数重置
|
||||
BOOM = 92, // 自爆怪
|
||||
|
||||
}
|
||||
|
||||
export const defaultAttrs = {
|
||||
[Attrs.BACK_CHANCE]:0,
|
||||
[Attrs.DODGE]:0,
|
||||
[Attrs.CON_RES]:0,
|
||||
}
|
||||
/**
|
||||
* 初始化英雄属性对象
|
||||
@@ -136,55 +99,27 @@ export const getNeAttrs = () => {
|
||||
export const AttrsType: Record<Attrs, BType> = {
|
||||
// ========== 基础生存属性(数值型) ==========
|
||||
[Attrs.HP_MAX]: BType.VALUE, // 最大生命值 - 数值型
|
||||
[Attrs.MP_MAX]: BType.VALUE, // 最大魔法值 - 数值型
|
||||
[Attrs.SHIELD_MAX]: BType.VALUE, // 最大护盾值 - 数值型
|
||||
[Attrs.HP_REGEN]: BType.RATIO, // 生命回复 - 百分比型
|
||||
[Attrs.MP_REGEN]: BType.VALUE, // 魔法回复 - 数值型
|
||||
[Attrs.LUCK]: BType.VALUE, // 幸运 - 数值型
|
||||
|
||||
// ========== 攻击属性(数值型) ==========
|
||||
[Attrs.AP]: BType.VALUE, // 攻击力 - 数值型
|
||||
[Attrs.DIS]: BType.VALUE, // 攻击距离 - 数值型
|
||||
[Attrs.AS]: BType.RATIO, // 攻击速度 - 百分比型
|
||||
[Attrs.SS]: BType.RATIO, // 技能速度 - 百分比型
|
||||
[Attrs.SKILL_DURATION]: BType.RATIO, // 技能持续时间 - 百分比型
|
||||
[Attrs.AREA_OF_EFFECT]: BType.VALUE, // 作用范围 - 数值型
|
||||
|
||||
// ========== 防御属性(混合类型) ==========
|
||||
[Attrs.DEF]: BType.VALUE, // 物理防御 - 数值型
|
||||
[Attrs.DODGE]: BType.RATIO, // 闪避率 - 百分比型
|
||||
[Attrs.THORNS]: BType.RATIO, // 反伤 - 百分比型
|
||||
|
||||
// ========== 暴击与命中属性(百分比型) ==========
|
||||
[Attrs.CRITICAL]: BType.RATIO, // 暴击率 - 百分比型
|
||||
[Attrs.CRITICAL_DMG]: BType.RATIO, // 暴击伤害 - 百分比型
|
||||
[Attrs.HIT]: BType.RATIO, // 命中率 - 百分比型
|
||||
[Attrs.CRITICAL_RES]: BType.RATIO, // 暴击抗性 - 百分比型
|
||||
[Attrs.CON_RES]: BType.RATIO, // 控制抗性 - 百分比型
|
||||
|
||||
|
||||
|
||||
|
||||
// ========== 特殊效果属性(百分比型) ==========
|
||||
[Attrs.LIFESTEAL]: BType.RATIO, // 吸血比率 - 百分比型
|
||||
[Attrs.MANASTEAL]: BType.RATIO, // 吸蓝 - 百分比型
|
||||
[Attrs.FREEZE_CHANCE]: BType.RATIO, // 冰冻概率 - 百分比型
|
||||
[Attrs.BURN_CHANCE]: BType.RATIO, // 燃烧概率 - 百分比型
|
||||
[Attrs.STUN_CHANCE]: BType.RATIO, // 眩晕概率 - 百分比型
|
||||
[Attrs.BACK_CHANCE]: BType.RATIO, // 击退概率 - 百分比型
|
||||
[Attrs.SLOW_CHANCE]: BType.RATIO, // 减速概率 - 百分比型
|
||||
[Attrs.POISON_CHANCE]: BType.RATIO, // 中毒概率 - 百分比型
|
||||
[Attrs.SILENCE_CHANCE]: BType.RATIO, // 沉默概率 - 百分比型
|
||||
[Attrs.EXPLOSION_CHANCE]: BType.RATIO, // 爆炸概率 - 百分比型
|
||||
|
||||
// ========== 增益效果属性(百分比型) ==========
|
||||
[Attrs.BUFF_UP]: BType.RATIO, // Buff效果提升 - 百分比型
|
||||
[Attrs.DBUFF_UP]: BType.RATIO, // Debuff效果提升 - 百分比型
|
||||
[Attrs.SHIELD_UP]: BType.RATIO, // 护盾效果提升 - 百分比型
|
||||
[Attrs.SPEED]: BType.RATIO, // 移动速度加成 - 百分比型
|
||||
[Attrs.EXP_GAIN]: BType.RATIO, // 经验获取 - 百分比型
|
||||
[Attrs.GOLD_GAIN]: BType.RATIO, // 金币获取 - 百分比型
|
||||
[Attrs.DROP_CHANCE]: BType.RATIO, // 掉落率 - 百分比型
|
||||
[Attrs.REVIVE_COUNT]: BType.VALUE, // 复活次数 - 数值型
|
||||
[Attrs.REVIVE_TIME]: BType.RATIO, // 复活时间 - 百分比型
|
||||
[Attrs.INVINCIBLE_TIME]: BType.RATIO, // 无敌时间 - 百分比型
|
||||
@@ -192,17 +127,7 @@ export const AttrsType: Record<Attrs, BType> = {
|
||||
// ========== 武器进化相关(混合类型) ==========
|
||||
[Attrs.PUNCTURE]: BType.VALUE, // 穿刺次数 - 数值型
|
||||
[Attrs.PUNCTURE_DMG]: BType.RATIO, // 穿刺伤害 - 百分比型
|
||||
[Attrs.MOVE_SPEED]: BType.VALUE, // 移动速度 - 数值型
|
||||
[Attrs.BURN]: BType.RATIO, // 易伤效果 - 百分比型
|
||||
[Attrs.WFUNY]: BType.RATIO, // 未知特殊属性 - 百分比型
|
||||
|
||||
|
||||
// ========== 负面状态相关(混合类型) ==========
|
||||
[Attrs.DMG_INVUL]: BType.RATIO, //易伤
|
||||
|
||||
// ========== 怪物独有特殊属性 (90-99)==========
|
||||
[Attrs.ATK_TRI_RESET]: BType.BOOLEAN, // 英雄攻击触发计数重置
|
||||
[Attrs.DMG_TRI_RESET]: BType.BOOLEAN, // 英雄伤害触发计数重置
|
||||
[Attrs.BOOM]: BType.BOOLEAN, // 自爆怪
|
||||
|
||||
};
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
import { count } from "console";
|
||||
import { Attrs, BType } from "./HeroAttrs";
|
||||
import { CardKind } from "./GameSet";
|
||||
|
||||
/**
|
||||
* 天赋类型枚举,也是触发条件
|
||||
*/
|
||||
export enum TriType {
|
||||
ATK = 1, // 等级升触发
|
||||
DMG = 2, //普通攻击触发
|
||||
ATKED=3,
|
||||
SKILL = 4, // 技能触发
|
||||
HPL = 6, // 失去生命值触发
|
||||
HPA = 7, // 获得生命值触发
|
||||
INIT = 8, // 初始触发,如:多1个技能
|
||||
DEAD = 9, // 基于死亡触发
|
||||
}
|
||||
|
||||
|
||||
|
||||
export enum TalEffet {
|
||||
ATK_DMG=1, // 伤害 次数+伤害加成,如额外5次 伤害+20%
|
||||
SKILL_DMG=2, // 技能伤害 次数+伤害加成,如额外5次 伤害+20%
|
||||
DEF=10, // 减伤 次数+减伤加成,如额外5次 伤害-20%
|
||||
THORNS=14, //反伤 百分比 次数+反伤加成,如额外5次 反伤-20%
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
HP=3, // 回血 百分比 直接触发,回血20%
|
||||
SHIELD=9, // 护盾 直接触发,获得20%的生命值护盾
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
BUFF = 5, // 数值叠加 触发后清零: 暴击率,闪避率等,触发后效果取消
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
ATTR=6, // 属性 永久添加
|
||||
|
||||
WFUNY=7, // 风怒 次数 叠加 ,如额外5次 风怒
|
||||
D_SKILL=8, //两次技能 次数 叠加,如额外5次 两次技能
|
||||
C_MSKILL=11, // 必杀技能必暴 次数 叠加 ,如额外5次 必杀技能必暴
|
||||
C_ATK=12, // 普工必爆 次数 叠加 ,如额外5次 普工必爆
|
||||
C_SKILL=13, // 一般技能必暴 次数 叠加 ,如额外5次 一般技能必暴
|
||||
}
|
||||
|
||||
|
||||
export enum TalTarget {
|
||||
SELF = 1, // 自己触发
|
||||
ENEMY = 2, // 敌人触发
|
||||
}
|
||||
|
||||
export enum TalAttrs {
|
||||
NON=0,
|
||||
FREEZE_CHANCE=Attrs.FREEZE_CHANCE, // 冰冻概率
|
||||
STUN_CHANCE=Attrs.STUN_CHANCE, // 冰冻概率
|
||||
BACK_CHANCE=Attrs.BACK_CHANCE, // 击退概率
|
||||
SILENCE_CHANCE=Attrs.SILENCE_CHANCE, // 沉默概率
|
||||
CRITICAL=Attrs.CRITICAL, // 暴击率
|
||||
AP=Attrs.AP, // 攻击力
|
||||
HP_MAX=Attrs.HP_MAX, // 最大生命值
|
||||
THORNS=Attrs.THORNS, //反伤 百分比
|
||||
REVIVE_COUNT=Attrs.REVIVE_COUNT, // 复活次数
|
||||
WFUNY=Attrs.WFUNY, // 风怒
|
||||
}
|
||||
|
||||
/**
|
||||
* 天赋配置接口
|
||||
* 定义一个完整的天赋效果
|
||||
*/
|
||||
|
||||
export interface ItalConf {
|
||||
uuid: number; // 天赋ID
|
||||
name: string; // 天赋名称
|
||||
icon:string; // 天赋图标
|
||||
kind:CardKind; // 天赋类型
|
||||
triType: TriType; // 天赋触发类型
|
||||
target: TalTarget; // 天赋触发目标
|
||||
effet: TalEffet; // 天赋触发效果
|
||||
vType:BType; //数值型还是百分比型
|
||||
value: number; // 触发的效果值(如增加10%攻击力, 触发的技能uuid,增加1个技能uuid)
|
||||
attrs?:TalAttrs //触发的attrs效果的对应attrs
|
||||
Trigger:number //触发值
|
||||
Pts:number //添加的天赋点数
|
||||
CPts:number //消耗的天赋点数
|
||||
desc: string; // 天赋描述(说明触发条件和效果)
|
||||
count:number //执行次数,及可以触发的次数
|
||||
}
|
||||
|
||||
// ========== 天赋配置表 ==========
|
||||
|
||||
/**
|
||||
* 天赋配置表 - 2行紧凑格式
|
||||
* 每个天赋配置压缩为2行:注释行 + 配置对象行
|
||||
*
|
||||
* 格式说明:
|
||||
* 第1行:// 天赋名称 - 英雄专属/通用 | 触发条件 | 效果描述
|
||||
* 第2行:{uuid, name, desc, type, triggerType, chance, t_value, e_value, e_name, e_type, e_scaling, e_count, stackable, maxStack}
|
||||
*
|
||||
* 使用说明:
|
||||
* 1. 等级类天赋:当英雄升级到指定等级时,每次都会触发效果
|
||||
* 2. 行为计数类:当特定行为累计达到阈值时触发,支持是否重置计数
|
||||
* 3. 受伤计数类:当受伤累计达到阈值时触发,支持是否重置计数
|
||||
* 4. 技能触发类:当特定条件满足时自动触发指定技能
|
||||
*/
|
||||
export const talConf: Record<number, ItalConf> = {
|
||||
/*** 普通攻击触发 ***/
|
||||
7001:{uuid:7001,name:"风怒",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.WFUNY,vType:BType.RATIO, value:50,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 立即给与目标150%伤害的额外打击"},
|
||||
7003:{uuid:7003,name:"回血",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.SELF,effet:TalEffet.HP,vType:BType.RATIO, value:1,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 会回复1%的生命值"},
|
||||
7004:{uuid:7004,name:"回血(大)",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.SELF,effet:TalEffet.HP,vType:BType.RATIO, value:2,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"普通攻击5次后, 会回复2%的生命值"},
|
||||
7005:{uuid:7005,name:"冰冻",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.FREEZE_CHANCE,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定冻结目标"},
|
||||
7006:{uuid:7006,name:"沉默",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.SILENCE_CHANCE,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定沉默目标"},
|
||||
7007:{uuid:7007,name:"击退",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.BACK_CHANCE,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定击退目标"},
|
||||
7008:{uuid:7008,name:"会心",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.SELF,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.CRITICAL,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定获得100%的暴击率"},
|
||||
7009:{uuid:7009,name:"眩晕",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.BUFF,vType:BType.RATIO, value:100,attrs:TalAttrs.STUN_CHANCE,
|
||||
Pts:2,CPts:0,desc:"普通攻击3次后, 下一次攻击必定获得100%的眩晕率"},
|
||||
7010:{uuid:7010,name:"熟练",icon:"7001",kind:CardKind.Atk,triType:TriType.ATK,Trigger:10,count:1,target:TalTarget.SELF,effet:TalEffet.D_SKILL,vType:BType.RATIO, value:0,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"普通攻击10次后, 下次一般技能额外释放1次,伤害100%"},
|
||||
|
||||
/*** 受伤触发 ***/
|
||||
7101:{uuid:7101,name:"反击",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.ATK_DMG,vType:BType.RATIO, value:50,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"被攻击3次后, 给于目标50%的伤害"},
|
||||
7102:{uuid:7102,name:"护盾",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:30,count:1,target:TalTarget.SELF,effet:TalEffet.SHIELD,vType:BType.RATIO, value:20,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"被攻击30次后, 获得20%的生命值护盾"},
|
||||
7103:{uuid:7103,name:"减伤",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:3,count:1,target:TalTarget.ENEMY,effet:TalEffet.DEF,vType:BType.RATIO, value:50,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"被攻击3次后, 下1次伤害减50%"},
|
||||
7104:{uuid:7104,name:"复苏",icon:"7001",kind:CardKind.Atted,triType:TriType.ATKED,Trigger:100,count:1,target:TalTarget.SELF,effet:TalEffet.BUFF,vType:BType.VALUE, value:1,attrs:TalAttrs.REVIVE_COUNT,
|
||||
Pts:2,CPts:0,desc:"被攻击100次后, 获得1次复活"},
|
||||
|
||||
/*** 失去血量触发 ***/
|
||||
7201:{uuid:7201,name:"背水",icon:"7001",kind:CardKind.Hp,triType:TriType.HPL,Trigger:50,count:10,target:TalTarget.SELF,effet:TalEffet.C_ATK,vType:BType.VALUE, value:0,attrs:TalAttrs.NON,
|
||||
Pts:2,CPts:0,desc:"每失去50%生命值,获得下10次普通攻击暴击"},
|
||||
|
||||
|
||||
|
||||
/*** 7400 回血量触发 ***/
|
||||
/** 7500 一击必杀触发 */
|
||||
};
|
||||
export const CanSelectTalents: Record<number, number[]> = {
|
||||
// 3级开放攻击类天赋
|
||||
3: [7001, 7003, 7005, 7008],
|
||||
// 5级必出防御类
|
||||
5: [7101, 7102, 7103],
|
||||
// 9级混合
|
||||
9: [7001, 7003, 7005, 7008, 7101, 7102, 7103],
|
||||
// 20级终极天赋
|
||||
20: [7301, 7302],
|
||||
// 默认全开
|
||||
99: [7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7101, 7102, 7103, 7104, 7201, 7301, 7302]
|
||||
};
|
||||
|
||||
// ========== 工具函数 ==========
|
||||
@@ -3,7 +3,6 @@ import { BoxSet, FacSet } from "./GameSet"
|
||||
import { smc } from "../SingletonModuleComp"
|
||||
import { BuffConf, SkillRange } from "./SkillSet"
|
||||
import { Interface } from "readline"
|
||||
import { TriType } from "./TalSet"
|
||||
|
||||
export enum AttrSet {
|
||||
ATTR_MAX = 85,
|
||||
@@ -94,11 +93,6 @@ export enum HRegen {
|
||||
HP=0.5
|
||||
}
|
||||
|
||||
export interface ITalPts {
|
||||
TriType: TriType; //触发添加天赋点数类型
|
||||
TriVal: number; //触发添加天赋点需求数值
|
||||
TalPts: number; //添加天赋点数值
|
||||
}
|
||||
/**
|
||||
* 不同职业升级属性加成配置
|
||||
* 战士:高血量成长,低攻击成长
|
||||
@@ -137,8 +131,6 @@ export interface heroInfo {
|
||||
speed: number; // 移动速度(像素/秒)
|
||||
skills: number[]; // 携带技能ID列表
|
||||
buff: BuffConf[]; // 自带buff配置(通常为空,由技能动态添加)
|
||||
tal: number[]; // 天赋ID列表
|
||||
talPts?: ITalPts[]; // 天赋触发配置
|
||||
info: string; // 描述文案
|
||||
}
|
||||
|
||||
@@ -159,37 +151,37 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
5001:{uuid:5001,name:"盾战士",icon:"1001",path:"hk1", fac:FacSet.HERO, kind:1,as:1,
|
||||
type:HType.warrior,lv:1,hp:300,mp:200,def:5,ap:25,speed:120,skills:[6001,6004],
|
||||
rangeType: SkillRange.Melee,
|
||||
buff:[],tal:[],talPts:[{TriType:TriType.ATK,TriVal:1,TalPts:1}],info:"盾战士"},
|
||||
buff:[],info:"盾战士"},
|
||||
|
||||
5002:{uuid:5002,name:"奥术法师",icon:"1001",path:"hm2", fac:FacSet.HERO, kind:2,as:1,
|
||||
type:HType.mage,lv:1,hp:150,mp:135,def:0,ap:40,speed:95,skills:[6003,6101],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"奥术法师"},
|
||||
buff:[],info:"奥术法师"},
|
||||
|
||||
5003:{uuid:5003,name:"射手",icon:"1001",path:"ha1", fac:FacSet.HERO, kind:2,as:1,
|
||||
type:HType.remote,lv:1,hp:180,mp:80,def:0,ap:30,speed:140,skills:[6002,6100],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"射手"},
|
||||
buff:[],info:"射手"},
|
||||
|
||||
5005:{uuid:5005,name:"牧师",icon:"1001",path:"hh1", fac:FacSet.HERO, kind:2,as:1,
|
||||
type:HType.mage,lv:1,hp:160,mp:135,def:0,ap:25,speed:100,skills:[6003,6100],
|
||||
rangeType: SkillRange.Mid,
|
||||
buff:[],tal:[],info:"牧师"},
|
||||
buff:[],info:"牧师"},
|
||||
|
||||
5004:{uuid:5004,name:"火焰法师",icon:"1001",path:"hm1", fac:FacSet.HERO, kind:2,as:1,
|
||||
type:HType.mage,lv:1,hp:150,mp:140,def:0,ap:45,speed:90,skills:[6003,6101],
|
||||
rangeType: SkillRange.Mid,
|
||||
buff:[],tal:[],info:"火焰法师"},
|
||||
buff:[],info:"火焰法师"},
|
||||
|
||||
5006:{uuid:5006,name:"召唤法师",icon:"1001",path:"hz1", fac:FacSet.HERO, kind:2,as:1,
|
||||
type:HType.support,lv:1,hp:200,mp:145,def:0,ap:20,speed:105,skills:[6003,6101],
|
||||
rangeType: SkillRange.Mid,
|
||||
buff:[],tal:[],info:"召唤法师"},
|
||||
buff:[],info:"召唤法师"},
|
||||
|
||||
5007:{uuid:5007,name:"刺客",icon:"1001",path:"hc1", fac:FacSet.HERO, kind:1,as:1,
|
||||
type:HType.assassin,lv:1,hp:140,mp:60,def:0,ap:50,speed:180,skills:[6001,6004],
|
||||
rangeType: SkillRange.Melee,
|
||||
buff:[],tal:[],info:"刺客"},
|
||||
buff:[],info:"刺客"},
|
||||
|
||||
|
||||
|
||||
@@ -198,48 +190,48 @@ export const HeroInfo: Record<number, heroInfo> = {
|
||||
5201:{uuid:5201,name:"兽人战士",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
|
||||
type:HType.warrior,lv:1,hp:60,mp:100,def:0,ap:8,speed:180,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"标准炮灰:确保英雄能完成3次普攻积累天赋计数"},
|
||||
buff:[],info:"标准炮灰:确保英雄能完成3次普攻积累天赋计数"},
|
||||
// 2. 快速突击型
|
||||
5301:{uuid:5301,name:"兽人斥候",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:1.2,
|
||||
type:HType.assassin,lv:1,hp:40,mp:100,def:0,ap:12,speed:400,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"快速突击:极高移速贴脸,检测护盾(7102)刷新率"},
|
||||
buff:[],info:"快速突击:极高移速贴脸,检测护盾(7102)刷新率"},
|
||||
// 3. 重型坦克型
|
||||
5401:{uuid:5401,name:"兽人卫士",icon:"1001",path:"mo3", fac:FacSet.MON, kind:1,as:5.0,
|
||||
type:HType.warrior,lv:1,hp:200,mp:100,def:5,ap:15,speed:60,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"重型坦克:数值墙,检测玩家破甲(7008)与持续输出"},
|
||||
buff:[],info:"重型坦克:数值墙,检测玩家破甲(7008)与持续输出"},
|
||||
|
||||
// 4. 远程骚扰型
|
||||
5501:{uuid:5501,name:"兽人射手",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
|
||||
type:HType.remote,lv:1,hp:50,mp:100,def:0,ap:10,speed:90,skills:[6203],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"远程骚扰:跨屏打击,迫使阵地分散或移动英雄"},
|
||||
buff:[],info:"远程骚扰:跨屏打击,迫使阵地分散或移动英雄"},
|
||||
|
||||
// 5. 特殊机制型
|
||||
5601:{uuid:5601,name:"兽人自爆兵",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
|
||||
type:HType.assassin,lv:1,hp:80,mp:100,def:0,ap:200,speed:220,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"特殊机制:极端伤害,漏怪即秒杀,检测减伤(7103)"},
|
||||
buff:[],info:"特殊机制:极端伤害,漏怪即秒杀,检测减伤(7103)"},
|
||||
// 召唤师:持续召唤小怪(后续可在技能系统中实现 SType.zhaohuan)
|
||||
5602:{uuid:5602,name:"兽人召唤师",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
|
||||
type:HType.mage,lv:1,hp:150,mp:300,def:5,ap:10,speed:100,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"战术目标:持续召唤小怪,检测英雄大招清场频率"},
|
||||
buff:[],info:"战术目标:持续召唤小怪,检测英雄大招清场频率"},
|
||||
// 治疗者:为周围怪物回血(此处以提升治疗效果和生命回复为基础被动)
|
||||
5603:{uuid:5603,name:"兽人祭司",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
|
||||
type:HType.support,lv:1,hp:150,mp:300,def:5,ap:10,speed:105,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"战术目标:为怪群回血,检测玩家沉默(7006)覆盖率"},
|
||||
buff:[],info:"战术目标:为怪群回血,检测玩家沉默(7006)覆盖率"},
|
||||
// 光环怪:为周围怪物提供增益(此处以Buff效果提升与移动速度提升为基础被动)
|
||||
// Attrs.BUFF_UP=60 (RATIO=1),Attrs.SPEED=63 (RATIO=1)
|
||||
5604:{uuid:5604,name:"兽人图腾师",icon:"1001",path:"mo1", fac:FacSet.MON, kind:1,as:3.0,
|
||||
type:HType.support,lv:1,hp:150,mp:250,def:5,ap:10,speed:110,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"战术目标:提供加速光环,改变怪群推进节奏"},
|
||||
buff:[],info:"战术目标:提供加速光环,改变怪群推进节奏"},
|
||||
// 6. 精英/BOSS型
|
||||
5701:{uuid:5701,name:"兽人首领(BOSS)",icon:"1001",path:"mo4", fac:FacSet.MON, kind:1,as:2.5,
|
||||
type:HType.warrior,lv:3,hp:2000,mp:500,def:10,ap:60,speed:120,skills:[6003],
|
||||
rangeType: SkillRange.Long,
|
||||
buff:[],tal:[],info:"终极考验:极高HP,检测大招重置与辐射协同输出"},
|
||||
buff:[],info:"终极考验:极高HP,检测大招重置与辐射协同输出"},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,6 @@ import { GameEvent } from "../common/config/GameEvent";
|
||||
import { getNeAttrs, getAttrs ,Attrs, defaultAttrs} from "../common/config/HeroAttrs";
|
||||
import { HeroSkillsComp } from "./HeroSkills";
|
||||
import { HeroMoveComp } from "./HeroMove";
|
||||
import { TalComp } from "./TalComp";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { HeroMasterComp } from "./HeroMasterComp";
|
||||
/** 角色实体 */
|
||||
@@ -21,7 +20,6 @@ export class Hero extends ecs.Entity {
|
||||
HeroSkills!: HeroSkillsComp;
|
||||
View!: HeroViewComp;
|
||||
HeroMove!: HeroMoveComp;
|
||||
TalComp!: TalComp;
|
||||
debugMode: boolean = false; // 是否启用调试模式
|
||||
protected init() {
|
||||
this.addComponents<ecs.Comp>(
|
||||
@@ -41,7 +39,6 @@ export class Hero extends ecs.Entity {
|
||||
this.remove(HeroViewComp);
|
||||
this.remove(HeroAttrsComp);
|
||||
this.remove(HeroSkillsComp);
|
||||
this.remove(TalComp);
|
||||
this.remove(HeroMasterComp)
|
||||
super.destroy();
|
||||
}
|
||||
@@ -85,16 +82,6 @@ export class Hero extends ecs.Entity {
|
||||
model.is_friend = is_friend
|
||||
model.rangeType = hero.rangeType;
|
||||
// 只有主角才挂载天赋组件
|
||||
if (is_master) {
|
||||
mLogger.log(this.debugMode,`[Hero] 主角创建, uuid=${uuid},is_master=${is_master}`);
|
||||
this.add(TalComp);
|
||||
this.add(HeroMasterComp)
|
||||
const talComp = this.get(TalComp);
|
||||
if (talComp) {
|
||||
talComp.init(uuid);
|
||||
}
|
||||
model.initEvent();
|
||||
}
|
||||
|
||||
// ✅ 初始化技能数据(迁移到 HeroSkillsComp)
|
||||
skillsComp.initSkills(hero.skills, uuid);
|
||||
|
||||
@@ -6,7 +6,6 @@ import { BuffConf, SkillRange } from "../common/config/SkillSet";
|
||||
import { HeroInfo, AttrSet, HType, JobUpConf } from "../common/config/heroSet";
|
||||
import { HeroSkillsComp } from "./HeroSkills";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { AttrCards, PotionCards } from "../common/config/AttrSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { _decorator } from "cc";
|
||||
|
||||
@@ -29,23 +28,14 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
fac: number = 0; // 0:hero 1:monster
|
||||
rangeType:SkillRange = SkillRange.Melee;
|
||||
// ==================== 基础属性(有初始值) ====================
|
||||
base_ap: number = 0; // 基础攻击
|
||||
base_def: number = 5; // 基础防御
|
||||
base_hp: number = 100; // 基础血量
|
||||
base_mp: number = 100; // 基础魔法值
|
||||
base_speed: number = 100; // 基础移动速度
|
||||
base_dis: number = 100; // 基础距离
|
||||
|
||||
// ==================== 动态属性值 ====================
|
||||
hp: number = 100; // 当前血量
|
||||
mp: number = 100; // 当前魔法值
|
||||
ap: number = 0; // 基础攻击
|
||||
hp: number = 100; // 基础血量
|
||||
hp_max: number = 100; // 最大血量
|
||||
speed: number = 100; // 基础移动速度
|
||||
dis: number = 100; // 基础距离
|
||||
shield: number = 0; // 当前护盾
|
||||
Attrs: any = []; // 最终属性数组(经过Buff计算后)
|
||||
NeAttrs: any = []; // 负面状态数组
|
||||
//计数型天赋buff
|
||||
Talents: Record<number, talTrigger> = {};
|
||||
//数值型天赋buff
|
||||
BUFFS_TAL: Record<number, {count:number,BType:BType,attrIndex:number,value: number}> = {};
|
||||
shield_max: number = 0; // 最大护盾值
|
||||
|
||||
|
||||
// ==================== 脏标签标记 ====================
|
||||
dirty_hp: boolean = false; // 血量变更标记
|
||||
@@ -58,11 +48,8 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
|
||||
// ==================== Buff/Debuff 系统 ====================
|
||||
/** 持久型buff数组 - 不会自动过期 */
|
||||
BUFFS: Record<number, Array<{value: number, BType: BType}>> = {};
|
||||
|
||||
/** 临时型buff数组 - 按时间自动过期 */
|
||||
BUFFS_TEMP: Record<number, Array<{value: number, BType: BType, remainTime: number}>> = {};
|
||||
|
||||
BUFFS: Record<number, Array<{value: number, BType: BType,time:number}>> = {};
|
||||
DEBUFFS: Record<number, Array<{value: number, BType: BType,time:number}>> = {};
|
||||
|
||||
// ==================== 标记状态 ====================
|
||||
is_dead: boolean = false;
|
||||
@@ -81,103 +68,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
killed_count:number=0;
|
||||
// 注意:技能数据已迁移到 HeroSkillsComp,不再存储在这里
|
||||
|
||||
initEvent() {
|
||||
// 监听升级事件
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 注册升级事件监听`);
|
||||
oops.message.on(GameEvent.CanUpdateLv, this.onLevelUp, this);
|
||||
// 移除卡牌事件监听,改为由 MissionCardComp 直接调用,避免非主角响应
|
||||
// oops.message.on(GameEvent.UseItemCard, this.onUseItemCard, this);
|
||||
// oops.message.on(GameEvent.UseAttrCard, this.onUseAttrCard, this);
|
||||
}
|
||||
|
||||
removeEvent() {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 移除升级事件监听`);
|
||||
oops.message.off(GameEvent.CanUpdateLv, this.onLevelUp, this);
|
||||
// oops.message.off(GameEvent.UseItemCard, this.onUseItemCard, this);
|
||||
// oops.message.off(GameEvent.UseAttrCard, this.onUseAttrCard, this);
|
||||
}
|
||||
|
||||
onUseAttrCard(event: string, args: any) {
|
||||
if (!this.is_master) return;
|
||||
const uuid = args;
|
||||
const attrCard = AttrCards[uuid];
|
||||
if (attrCard) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 使用属性卡: ${attrCard.desc}`);
|
||||
// 构造 BuffConf,默认使用 BType.VALUE,永久生效 (time: 0)
|
||||
const buffConf: BuffConf = {
|
||||
buff: attrCard.attr,
|
||||
value: attrCard.value,
|
||||
BType: BType.VALUE,
|
||||
time: 0,
|
||||
chance: 1, // 必中
|
||||
};
|
||||
this.addBuff(buffConf);
|
||||
smc.updateHeroInfo(this); // 确保同步到全局数据
|
||||
oops.gui.toast(attrCard.desc);
|
||||
}
|
||||
}
|
||||
|
||||
onUseItemCard(event: string, args: any) {
|
||||
if (!this.is_master) return;
|
||||
const itemId = args;
|
||||
|
||||
// 1. 尝试从 PotionCards 获取 (新版药水)
|
||||
const potion = PotionCards[itemId];
|
||||
if (potion) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 使用药水: ${potion.desc}`);
|
||||
const buffConf: BuffConf = {
|
||||
buff: potion.attr,
|
||||
value: potion.value,
|
||||
BType: BType.RATIO, // 药水默认是百分比加成
|
||||
time: potion.duration,
|
||||
chance: 1,
|
||||
};
|
||||
this.addBuff(buffConf);
|
||||
oops.gui.toast(potion.desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理英雄升级逻辑
|
||||
*/
|
||||
onLevelUp(event: string, args: any) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 收到升级事件: is_master=${this.is_master}, args=${JSON.stringify(args)}`);
|
||||
// 只有主角才响应升级事件
|
||||
if (!this.is_master) return;
|
||||
const newLv = args.lv;
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 英雄升级处理: Lv.${this.lv} -> Lv.${newLv}`);
|
||||
if (newLv > this.lv) {
|
||||
this.lv = newLv;
|
||||
|
||||
// === 属性成长逻辑 ===
|
||||
// 根据职业获取成长属性
|
||||
const jobConf = JobUpConf[this.type as HType] || { hp: 30, ap: 5, def: 1 };
|
||||
const hpGrow = jobConf.hp;
|
||||
const apGrow = jobConf.ap;
|
||||
const defGrow = jobConf.def;
|
||||
|
||||
this.base_hp += hpGrow;
|
||||
this.base_ap += apGrow;
|
||||
this.base_def += defGrow;
|
||||
|
||||
// 重新计算受影响的属性
|
||||
this.recalculateSingleAttr(Attrs.HP_MAX);
|
||||
this.recalculateSingleAttr(Attrs.AP);
|
||||
this.recalculateSingleAttr(Attrs.DEF);
|
||||
|
||||
// 升级福利:回复 20% 最大生命值
|
||||
const healRatio = 0.2;
|
||||
const healAmount = Math.floor(this.Attrs[Attrs.HP_MAX] * healRatio);
|
||||
this.add_hp(healAmount, true);
|
||||
|
||||
// 同步数据到全局
|
||||
smc.updateHeroInfo(this);
|
||||
|
||||
// 简单的UI提示
|
||||
// oops.gui.toast(`升级!HP+${hpGrow} AP+${apGrow}`);
|
||||
}
|
||||
}
|
||||
|
||||
start(){
|
||||
}
|
||||
@@ -189,55 +80,12 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
initAttrs() {
|
||||
// 清空现有 buff/debuff
|
||||
this.BUFFS = {};
|
||||
this.BUFFS_TEMP = {};
|
||||
this.BUFFS_TAL = {};
|
||||
this.Talents = {};
|
||||
|
||||
this.DEBUFFS = {};
|
||||
// 获取英雄配置
|
||||
const heroInfo = HeroInfo[this.hero_uuid];
|
||||
if (!heroInfo) return;
|
||||
|
||||
// 1. 重置为基础值
|
||||
this.Attrs[Attrs.HP_MAX] = this.base_hp;
|
||||
this.Attrs[Attrs.MP_MAX] = this.base_mp;
|
||||
this.Attrs[Attrs.DEF] = this.base_def;
|
||||
this.Attrs[Attrs.AP] = this.base_ap;
|
||||
this.Attrs[Attrs.SPEED] = this.base_speed;
|
||||
|
||||
// 2. 初始化其他属性(无初始值的)
|
||||
for (const attrKey in this.Attrs) {
|
||||
const attrIndex = parseInt(attrKey);
|
||||
if (
|
||||
attrIndex !== Attrs.HP_MAX &&
|
||||
attrIndex !== Attrs.MP_MAX &&
|
||||
attrIndex !== Attrs.DEF &&
|
||||
attrIndex !== Attrs.AP &&
|
||||
attrIndex !== Attrs.SPEED
|
||||
) {
|
||||
this.Attrs[attrIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载初始 buff
|
||||
if (heroInfo.buff && heroInfo.buff.length > 0) {
|
||||
for (const buffConf of heroInfo.buff) {
|
||||
this.addBuff(buffConf);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 应用全局属性加成 (强制重算受影响的属性)
|
||||
if (this.fac === 0 && smc.global_attrs) {
|
||||
for (const key in smc.global_attrs) {
|
||||
const attrIndex = Number(key);
|
||||
this.recalculateSingleAttr(attrIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 初始化状态值 (确保满状态)
|
||||
this.hp = this.Attrs[Attrs.HP_MAX];
|
||||
this.mp = this.Attrs[Attrs.MP_MAX];
|
||||
|
||||
smc.updateHeroInfo(this);
|
||||
|
||||
}
|
||||
/*******************基础属性管理********************/
|
||||
|
||||
@@ -245,7 +93,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
const oldHp = this.hp;
|
||||
let addValue = value;
|
||||
if(!isValue){
|
||||
addValue = value * this.Attrs[Attrs.HP_MAX] / 100;
|
||||
addValue = value * this.hp_max / 100;
|
||||
}
|
||||
|
||||
// ✅ 数据层只负责数据修改,不调用视图层
|
||||
@@ -255,34 +103,16 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
// }
|
||||
|
||||
this.hp += addValue;
|
||||
this.hp = Math.max(0, Math.min(this.hp, this.Attrs[Attrs.HP_MAX]));
|
||||
this.hp = Math.max(0, Math.min(this.hp, this.hp_max));
|
||||
this.dirty_hp = true; // ✅ 仅标记需要更新
|
||||
smc.updateHeroInfo(this);
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` HP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldHp.toFixed(1)} -> ${this.hp.toFixed(1)}`);
|
||||
}
|
||||
add_mp(value:number,isValue:boolean){
|
||||
const oldMp = this.mp;
|
||||
let addValue = value;
|
||||
if(!isValue){
|
||||
addValue = value * this.Attrs[Attrs.MP_MAX] / 100;
|
||||
}
|
||||
|
||||
// ✅ 数据层只负责数据修改,不调用视图层
|
||||
// let heroView = this.ent.get(HeroViewComp);
|
||||
// if(heroView){
|
||||
// heroView.mp_add(addValue);
|
||||
// }
|
||||
|
||||
this.mp += addValue;
|
||||
this.mp = Math.max(0, Math.min(this.mp, this.Attrs[Attrs.MP_MAX]));
|
||||
this.dirty_mp = true; // ✅ 仅标记需要更新
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` MP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldMp.toFixed(1)} -> ${this.mp.toFixed(1)}`);
|
||||
}
|
||||
|
||||
add_shield(value:number,isValue:boolean){
|
||||
const oldShield = this.shield;
|
||||
let addValue = value;
|
||||
if(!isValue){
|
||||
addValue = value * this.Attrs[Attrs.HP_MAX] / 100;
|
||||
addValue = value * this.shield_max / 100;
|
||||
}
|
||||
this.shield += addValue;
|
||||
this.dirty_shield = true; // 标记护盾需要更新
|
||||
@@ -294,268 +124,21 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
* @param buffConf buff 配置 (来自 SkillSet.BuffConf heroSet.buff)
|
||||
*/
|
||||
addBuff(buffConf: BuffConf) {
|
||||
const isPermanent = buffConf.time === 0;
|
||||
const attrIndex = buffConf.buff;
|
||||
|
||||
if (isPermanent) {
|
||||
// 添加持久buff到BUFFS - 直接追加到数组
|
||||
if (!this.BUFFS[attrIndex]) {
|
||||
this.BUFFS[attrIndex] = [];
|
||||
}
|
||||
this.BUFFS[attrIndex].push({ value: buffConf.value, BType: buffConf.BType });
|
||||
} else {
|
||||
// 添加临时buff到BUFFS_TEMP - 直接追加到数组
|
||||
if (!this.BUFFS_TEMP[attrIndex]) {
|
||||
this.BUFFS_TEMP[attrIndex] = [];
|
||||
}
|
||||
this.BUFFS_TEMP[attrIndex].push({
|
||||
value: buffConf.value,
|
||||
BType: buffConf.BType,
|
||||
remainTime: buffConf.time
|
||||
});
|
||||
}
|
||||
|
||||
// 重新计算受影响的属性
|
||||
this.recalculateSingleAttr(attrIndex);
|
||||
smc.updateHeroInfo(this); // 确保同步到全局数据
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================== 属性计算系统 ====================
|
||||
private getBaseValue(attrIndex: number): number {
|
||||
switch (attrIndex) {
|
||||
case Attrs.HP_MAX: return this.base_hp;
|
||||
case Attrs.MP_MAX: return this.base_mp;
|
||||
case Attrs.DEF: return this.base_def;
|
||||
case Attrs.AP: return this.base_ap;
|
||||
case Attrs.SPEED: return this.base_speed;
|
||||
case Attrs.SHIELD_MAX: return 0;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算单个属性
|
||||
* @param attrIndex 属性索引
|
||||
*/
|
||||
recalculateSingleAttr(attrIndex: number) {
|
||||
const oldVal = this.Attrs[attrIndex] || 0;
|
||||
const baseVal = this.getBaseValue(attrIndex);
|
||||
|
||||
// 2. 收集所有数值型 buff/debuff
|
||||
let totalValue = baseVal;
|
||||
|
||||
// 遍历持久buff数组
|
||||
if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) {
|
||||
for (const buff of this.BUFFS[attrIndex]) {
|
||||
if (buff.BType === BType.VALUE) {
|
||||
totalValue += buff.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 遍历临时buff数组
|
||||
if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) {
|
||||
for (const buff of this.BUFFS_TEMP[attrIndex]) {
|
||||
if (buff.BType === BType.VALUE) {
|
||||
totalValue += buff.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in this.BUFFS_TAL) {
|
||||
const buff = this.BUFFS_TAL[Number(key)];
|
||||
if (buff.attrIndex === attrIndex && buff.BType === BType.VALUE) {
|
||||
totalValue += buff.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 收集所有百分比型 buff/debuff
|
||||
let totalRatio = 0;
|
||||
|
||||
// 遍历持久buff数组
|
||||
if (this.BUFFS[attrIndex] && this.BUFFS[attrIndex].length > 0) {
|
||||
for (const buff of this.BUFFS[attrIndex]) {
|
||||
if (buff.BType === BType.RATIO) {
|
||||
totalRatio += buff.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 遍历临时buff数组
|
||||
if (this.BUFFS_TEMP[attrIndex] && this.BUFFS_TEMP[attrIndex].length > 0) {
|
||||
for (const buff of this.BUFFS_TEMP[attrIndex]) {
|
||||
if (buff.BType === BType.RATIO) {
|
||||
totalRatio += buff.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in this.BUFFS_TAL) {
|
||||
const buff = this.BUFFS_TAL[Number(key)];
|
||||
if (buff.attrIndex === attrIndex && buff.BType === BType.RATIO) {
|
||||
totalRatio += buff.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 全局属性加成 (只对英雄生效,怪物不生效)
|
||||
// this.fac === 0 代表英雄 (FacSet.HERO)
|
||||
if (this.fac === 0 && smc.global_attrs && smc.global_attrs[attrIndex]) {
|
||||
const [val, count] = smc.global_attrs[attrIndex];
|
||||
const globalAdd = val * count;
|
||||
totalRatio += globalAdd;
|
||||
// mLogger.log(this.debugMode, 'HeroAttrs', ` 全局加成: ${this.hero_name} Attr=${attrIndex} Val=${val} Count=${count} Add=${globalAdd}%`);
|
||||
}
|
||||
|
||||
// 4. 根据属性类型计算最终值
|
||||
const attrType = AttrsType[attrIndex];
|
||||
const isRatioAttr = attrType === BType.RATIO;
|
||||
|
||||
if (isRatioAttr) {
|
||||
// 百分比型属性:直接加减
|
||||
this.Attrs[attrIndex] = totalValue + totalRatio;
|
||||
} else {
|
||||
// 数值型属性:(基础值+数值) × (1 + 百分比/100)
|
||||
this.Attrs[attrIndex] = Math.floor(totalValue * (1 + totalRatio / 100));
|
||||
}
|
||||
|
||||
// 5. 确保属性值合理
|
||||
this.clampSingleAttr(attrIndex);
|
||||
|
||||
if (oldVal !== this.Attrs[attrIndex]) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 属性重算: ${this.hero_name}, 属性ID=${attrIndex}, ${oldVal} -> ${this.Attrs[attrIndex]} (Base=${baseVal}, Add=${totalValue-baseVal}, Ratio=${totalRatio}%)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保单个属性值合理
|
||||
*/
|
||||
private clampSingleAttr(attrIndex: number) {
|
||||
switch(attrIndex) {
|
||||
case Attrs.HP_MAX:
|
||||
case Attrs.MP_MAX:
|
||||
this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]);
|
||||
break;
|
||||
case Attrs.DEF:
|
||||
case Attrs.AP:
|
||||
this.Attrs[attrIndex] = Math.max(1, this.Attrs[attrIndex]);
|
||||
break;
|
||||
case Attrs.CRITICAL:
|
||||
case Attrs.DODGE:
|
||||
case Attrs.HIT:
|
||||
this.Attrs[attrIndex] = Math.max(0, Math.min(AttrSet.ATTR_MAX, this.Attrs[attrIndex]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 临时 BUFF/DEBUFF 更新 ====================
|
||||
/**
|
||||
* 更新临时 buff/debuff 的剩余时间
|
||||
* @param dt 时间增量
|
||||
*/
|
||||
updateTemporaryBuffsDebuffs(dt: number) {
|
||||
const affectedAttrs = new Set<number>();
|
||||
updateBuffsDebuffs(dt: number) {
|
||||
|
||||
// 更新临时型buff
|
||||
for (const attrIndex in this.BUFFS_TEMP) {
|
||||
const buffs = this.BUFFS_TEMP[attrIndex];
|
||||
for (let i = buffs.length - 1; i >= 0; i--) {
|
||||
const buff = buffs[i];
|
||||
buff.remainTime -= dt;
|
||||
if (buff.remainTime <= 0) {
|
||||
buffs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (buffs.length === 0) {
|
||||
delete this.BUFFS_TEMP[attrIndex];
|
||||
affectedAttrs.add(parseInt(attrIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// 负面状态更新
|
||||
for (const key in this.NeAttrs) {
|
||||
const debuff = this.NeAttrs[key];
|
||||
debuff.remainTime -= dt;
|
||||
if (debuff.remainTime <= 0) {
|
||||
debuff.remainTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 只重新计算受影响的属性
|
||||
affectedAttrs.forEach(attrIndex => {
|
||||
this.recalculateSingleAttr(attrIndex);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== BUFF 辅助方法 ====================
|
||||
/**
|
||||
* 清空buff
|
||||
* @param attrIndex 属性索引,如果为空则清理所有buff
|
||||
* @param isBuff true时清理value>0的增益buff,false时清理value<0的减益buff
|
||||
*/
|
||||
clearBuffs(attrIndex?: number, isBuff: boolean = true): void {
|
||||
if (attrIndex === undefined) {
|
||||
for (const attrIndex in this.BUFFS_TEMP) {
|
||||
this.clearBuffsForAttr(parseInt(attrIndex), isBuff);
|
||||
}
|
||||
} else {
|
||||
this.clearBuffsForAttr(attrIndex, isBuff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定属性的buff
|
||||
* @param attrIndex 属性索引
|
||||
* @param isBuff true清理增益buff,false清理减益buff
|
||||
*/
|
||||
private clearBuffsForAttr(attrIndex: number, isBuff: boolean): void {
|
||||
const buffContainer = this.BUFFS_TEMP;
|
||||
|
||||
if (!buffContainer[attrIndex]) return;
|
||||
|
||||
buffContainer[attrIndex] = buffContainer[attrIndex].filter(buff => {
|
||||
const shouldClear = isBuff ? buff.value > 0 : buff.value < 0;
|
||||
return !shouldClear;
|
||||
});
|
||||
|
||||
if (buffContainer[attrIndex].length === 0) {
|
||||
delete buffContainer[attrIndex];
|
||||
}
|
||||
|
||||
this.recalculateSingleAttr(attrIndex);
|
||||
}
|
||||
|
||||
// ==================== NeAttrs(负面状态)管理 ====================
|
||||
/**
|
||||
* 清理单个NeAttr(负面状态)
|
||||
*/
|
||||
clearNeAttr(neAttrIndex: number): void {
|
||||
if (this.NeAttrs[neAttrIndex]) {
|
||||
this.NeAttrs[neAttrIndex].value = 0;
|
||||
this.NeAttrs[neAttrIndex].time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有NeAttrs(负面状态)
|
||||
*/
|
||||
clearAllNeAttrs(): void {
|
||||
for (const key in this.NeAttrs) {
|
||||
this.NeAttrs[key].value = 0;
|
||||
this.NeAttrs[key].time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否处于眩晕状态
|
||||
*/
|
||||
public isStun(): boolean {
|
||||
return this.NeAttrs[NeAttrs.IN_STUN]?.time > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否处于冰冻状态
|
||||
*/
|
||||
public isFrost(): boolean {
|
||||
return this.NeAttrs[NeAttrs.IN_FROST]?.time > 0;
|
||||
}
|
||||
|
||||
// ==================== 技能距离缓存管理 ====================
|
||||
/**
|
||||
@@ -591,117 +174,23 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
public getCachedMinSkillDistance(): number {
|
||||
return this.minSkillDistance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加数值型天赋buff 数值会进行累加,count 时叠加层数
|
||||
* @param t_uuid 天赋唯一标识
|
||||
* @param attrIndex 属性索引
|
||||
* @param bType buff类型(VALUE:数值型, RATIO:百分比型)
|
||||
* @param value buff值
|
||||
*/
|
||||
addValueTal(t_uuid: number, attrIndex?: number, bType?: BType, value: number = 0) {
|
||||
if (attrIndex === undefined || bType === undefined) return;
|
||||
const buff = this.BUFFS_TAL[t_uuid];
|
||||
if (!buff) {
|
||||
this.BUFFS_TAL[t_uuid] = { count: 1, BType: bType, attrIndex, value };
|
||||
} else {
|
||||
buff.count += 1;
|
||||
buff.value += value;
|
||||
}
|
||||
this.recalculateSingleAttr(attrIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加计数型天赋,每次添加数值已最新额覆盖久的,count为可用次数
|
||||
* @param eff 天赋效果ID
|
||||
* @param value 天赋值
|
||||
*/
|
||||
addCountTal(eff: number, value: number) {
|
||||
const t = this.Talents[eff] || { value: 0, count: 0 };
|
||||
t.value = value;
|
||||
t.count += 1;
|
||||
this.Talents[eff] = t;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用计数型天赋,如风怒 ,必爆等天赋,只需要返回是否暴击的,使用这个方法
|
||||
* @param eff 天赋效果ID
|
||||
* @returns 是否使用成功
|
||||
*/
|
||||
useCountTal(eff: number): boolean {
|
||||
const t = this.Talents[eff];
|
||||
if (!t || t.count <= 0) return false;
|
||||
t.count -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用计数型天赋并返回天赋值,如额外5次 伤害+20% ,5次受伤,免伤20% 使用这个方法
|
||||
* @param eff 天赋效果ID
|
||||
* @returns 天赋值
|
||||
*/
|
||||
useCountValTal(eff: number): number {
|
||||
const t = this.Talents[eff];
|
||||
if (!t || t.value <= 0) return 0;
|
||||
t.count -= 1;
|
||||
return t.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UUID移除数值型天赋buff
|
||||
* @param t_uuid 天赋唯一标识
|
||||
*/
|
||||
useValueTalByUuid(t_uuid: number) {
|
||||
const buff = this.BUFFS_TAL[t_uuid];
|
||||
if (!buff) return;
|
||||
buff.count--;
|
||||
if (buff.count <= 0) {
|
||||
delete this.BUFFS_TAL[t_uuid];
|
||||
}
|
||||
this.recalculateSingleAttr(buff.attrIndex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据属性索引移除数值型天赋buff
|
||||
* @param attrIndex 属性索引
|
||||
*/
|
||||
useValueTalByAttr(attrIndex: number) {
|
||||
let changed = false;
|
||||
for (const key in this.BUFFS_TAL) {
|
||||
const b = this.BUFFS_TAL[Number(key)];
|
||||
if (b && b.attrIndex === attrIndex) {
|
||||
delete this.BUFFS_TAL[Number(key)];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) this.recalculateSingleAttr(attrIndex);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.removeEvent();
|
||||
// 重置为初始状态
|
||||
this.hero_uuid = 1001;
|
||||
this.hero_name = "hero";
|
||||
this.lv = 1;
|
||||
this.type = 0;
|
||||
this.fac = 0;
|
||||
this.base_ap = 0;
|
||||
this.base_def = 5;
|
||||
this.base_hp = 100;
|
||||
this.base_mp = 100;
|
||||
this.base_speed = 100;
|
||||
this.base_dis = 0;
|
||||
this.ap = 0;
|
||||
this.hp = 100;
|
||||
this.speed = 100;
|
||||
this.dis = 0;
|
||||
this.hp = 100;
|
||||
this.mp = 100;
|
||||
this.shield = 0;
|
||||
this.Attrs = [];
|
||||
this.NeAttrs = [];
|
||||
this.shield_max = 0;
|
||||
this.BUFFS = {};
|
||||
this.BUFFS_TEMP = {};
|
||||
this.BUFFS_TAL = {};
|
||||
this.Talents = {};
|
||||
this.DEBUFFS = {};
|
||||
// 重置技能距离缓存
|
||||
this.maxSkillDistance = 0;
|
||||
this.minSkillDistance = 0;
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import { Timer } from "db://oops-framework/core/common/timer/Timer";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { Attrs } from "../common/config/HeroAttrs";
|
||||
import { HRegen } from "../common/config/heroSet";
|
||||
import { HeroSkillsComp } from "./HeroSkills";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { mLogger } from "../common/Logger";
|
||||
/**
|
||||
* ==================== 英雄属性更新系统 ====================
|
||||
*
|
||||
* 按照 ECS 设计理念:
|
||||
* - Component(HeroAttrsComp):存储数据
|
||||
* - System(HeroAttrSystem):处理业务逻辑
|
||||
*
|
||||
* 系统职责:
|
||||
* 1. 每帧更新临时 Buff(时间递减,过期移除)
|
||||
* 2. 每帧更新 HP/MP 自然回复
|
||||
* 3. 限制属性值在合理范围内
|
||||
*
|
||||
/**
|
||||
* 使用方式:
|
||||
* 在 RootSystem 中注册此系统,它会自动每帧更新所有拥有 HeroAttrsComp 的实体
|
||||
*/
|
||||
@ecs.register('HeroAttrSystem')
|
||||
export class HeroAttrSystem extends ecs.ComblockSystem
|
||||
implements ecs.ISystemUpdate, ecs.IEntityEnterSystem, ecs.ISystemFirstUpdate {
|
||||
|
||||
// ==================== 调试统计(可选)====================
|
||||
private entityCount: number = 0; // 本帧处理的实体数
|
||||
private frameCount: number = 0; // 总帧数
|
||||
public debugMode: boolean = false; // 是否启用调试模式
|
||||
private RTimer:Timer=new Timer(5)
|
||||
/**
|
||||
* 过滤器:只处理拥有 HeroAttrsComp 的实体
|
||||
*/
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(HeroAttrsComp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体首次进入系统时调用(每个实体只调用一次)
|
||||
*/
|
||||
entityEnter(e: ecs.Entity): void {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
if (!model) return;
|
||||
|
||||
mLogger.log(this.debugMode, 'HeroAttrSystem', `[HeroAttrSystem] 英雄进入系统: ${model.hero_name} (uuid: ${model.hero_uuid})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统首次更新前调用(整个系统只调用一次)
|
||||
*/
|
||||
firstUpdate(): void {
|
||||
mLogger.log(this.debugMode, 'HeroAttrSystem', "[HeroAttrSystem] 系统首次更新");
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧更新(为每个英雄调用一次)
|
||||
*
|
||||
* ⭐ 关键理解:
|
||||
* - 如果有 3 个英雄,这个方法每帧会被调用 3 次
|
||||
* - 每次调用处理不同的实体 e
|
||||
* - 这是正确的设计,不是 bug
|
||||
*/
|
||||
update(e: ecs.Entity): void {
|
||||
if(!smc.mission.play ) return;
|
||||
if(smc.mission.pause) return
|
||||
const model = e.get(HeroAttrsComp);
|
||||
if (!model || model.is_dead || model.is_reviving) return;
|
||||
|
||||
// 统计:记录本帧处理的实体数
|
||||
this.entityCount++;
|
||||
|
||||
// 调试日志(可选,调试时启用)
|
||||
if (this.debugMode) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrSystem', ` [${this.entityCount}] 更新英雄: ${model.hero_name}, HP: ${model.hp.toFixed(2)}`);
|
||||
}
|
||||
|
||||
// 1. 更新临时 Buff/Debuff(时间递减,过期自动移除)
|
||||
model.updateTemporaryBuffsDebuffs(this.dt);
|
||||
// 记录MP变化前的值
|
||||
const oldMp = model.mp;
|
||||
|
||||
if(this.RTimer.update(this.dt)){
|
||||
|
||||
// 每5秒回血1次
|
||||
const hpRegen = model.Attrs[Attrs.HP_REGEN] || 0;
|
||||
// 回血逻辑 + 视图表现
|
||||
const totalHpRegen = HRegen.HP + hpRegen;
|
||||
if (totalHpRegen > 0) {
|
||||
model.add_hp(totalHpRegen, false);
|
||||
// 触发视图层回血特效
|
||||
const view = e.get(HeroViewComp);
|
||||
if (view) {
|
||||
view.health(totalHpRegen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 限制属性值在合理范围内
|
||||
if (model.mp > model.Attrs[Attrs.MP_MAX]) {
|
||||
model.mp = model.Attrs[Attrs.MP_MAX];
|
||||
}
|
||||
if (model.hp > model.Attrs[Attrs.HP_MAX]) {
|
||||
model.hp = model.Attrs[Attrs.HP_MAX];
|
||||
}
|
||||
|
||||
// 4. 如果MP发生变化,更新最大技能距离缓存(最小距离不受MP影响)
|
||||
if (model.mp !== oldMp) {
|
||||
const skillsComp = e.get(HeroSkillsComp);
|
||||
if (skillsComp) {
|
||||
model.updateSkillDistanceCache(skillsComp);
|
||||
}
|
||||
}
|
||||
|
||||
// 每 60 帧输出一次统计
|
||||
this.frameCount++;
|
||||
if (this.frameCount % 60 === 0 && this.entityCount === 1) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrSystem', `[HeroAttrSystem] 第 ${this.frameCount} 帧,处理 ${this.entityCount} 个英雄`);
|
||||
}
|
||||
|
||||
// 注意:显示更新由 HeroViewComp 负责,这里只处理数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用调试模式(调试时使用)
|
||||
*/
|
||||
enableDebug() {
|
||||
this.debugMode = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用调试模式(正式运行)
|
||||
*/
|
||||
disableDebug() {
|
||||
this.debugMode = false;
|
||||
}
|
||||
}
|
||||
@@ -1,621 +0,0 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { Vec3, v3 } from "cc";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { HSSet, SkillSet, SType, TGroup, SkillConfig } from "../common/config/SkillSet";
|
||||
import { HeroSkillsComp, SkillSlot } from "./HeroSkills";
|
||||
import { Skill } from "../skill/Skill";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { TalComp } from "./TalComp";
|
||||
import { TalEffet, TriType } from "../common/config/TalSet";
|
||||
import { BoxSet, FacSet } from "../common/config/GameSet";
|
||||
import { GameConst } from "../common/config/GameConst";
|
||||
import { Attrs } from "../common/config/HeroAttrs";
|
||||
import { mLogger } from "../common/Logger";
|
||||
|
||||
/**
|
||||
* ==================== 自动施法系统 ====================
|
||||
*
|
||||
* 职责:
|
||||
* 1. 检测可施放的技能
|
||||
* 2. 根据策略自动施法(AI)
|
||||
* 3. 选择目标
|
||||
* 4. 添加施法请求标记
|
||||
*
|
||||
* 设计理念:
|
||||
* - 负责"何时施法"的决策
|
||||
* - 通过添加 CSRequestComp 触发施法
|
||||
* - 可被玩家输入系统或AI系统复用
|
||||
* - 支持多种AI策略
|
||||
*/
|
||||
@ecs.register('SACastSystem')
|
||||
export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
debugMode: boolean = false; // 是否启用调试模式
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp);
|
||||
}
|
||||
|
||||
update(e: ecs.Entity): void {
|
||||
if(!smc.mission.play ) return;
|
||||
if(smc.mission.pause) return
|
||||
const skills = e.get(HeroSkillsComp);
|
||||
if (!skills) return;
|
||||
|
||||
// AI 降频:每0.2秒执行一次
|
||||
skills.ai_timer += this.dt;
|
||||
if (skills.ai_timer < GameConst.Battle.AI_CHECK_INTERVAL) return;
|
||||
skills.ai_timer = 0;
|
||||
|
||||
const heroAttrs = e.get(HeroAttrsComp);
|
||||
const heroView = e.get(HeroViewComp);
|
||||
if (!heroAttrs || !heroView) return;
|
||||
|
||||
// 检查基本条件
|
||||
if (heroAttrs.is_dead || heroAttrs.is_reviving || heroAttrs.isStun() || heroAttrs.isFrost()) return;
|
||||
|
||||
// 移除 is_atking 检查,实现只要距离和CD满足即施法
|
||||
// if (!heroAttrs.is_atking) return;
|
||||
|
||||
const readySkills = skills.getReadySkills();
|
||||
if (readySkills.length === 0) return;
|
||||
|
||||
// 选择第一个可施放的技能(支持伤害/治疗/护盾)
|
||||
for (const s_uuid of readySkills) {
|
||||
const skill = skills.getSkill(s_uuid);
|
||||
if (!skill) continue;
|
||||
if (skill.hset === HSSet.max && !skills.max_auto) continue;
|
||||
|
||||
const config = SkillSet[skill.s_uuid];
|
||||
if (!config) continue;
|
||||
|
||||
// 根据技能类型检查目标
|
||||
if (config.SType === SType.damage) {
|
||||
if (!this.hasEnemyInSkillRange(heroView, heroAttrs, skill.dis)) continue;
|
||||
} else if (config.SType === SType.heal || config.SType === SType.shield) {
|
||||
if (!this.hasTeamInSkillRange(heroView, heroAttrs, skill.dis)) continue;
|
||||
} else if (config.SType === SType.buff) {
|
||||
if (!this.hasBuffTarget(heroView, heroAttrs, skill.dis, config.TGroup)) continue;
|
||||
}
|
||||
|
||||
// ✅ 开始执行施法
|
||||
this.startCast(e, skill, skill.hset);
|
||||
|
||||
// 一次只施放一个技能
|
||||
break;
|
||||
}
|
||||
}
|
||||
private startCast(e: ecs.Entity,skill:SkillSlot,hset:HSSet): boolean {
|
||||
if (!skill||!e) return false
|
||||
const skills = e.get(HeroSkillsComp);
|
||||
const heroAttrs = e.get(HeroAttrsComp);
|
||||
const heroView = e.get(HeroViewComp);
|
||||
// 3. 检查施法条件
|
||||
if (!this.checkCastConditions(skills, heroAttrs, skill.s_uuid)) return false
|
||||
|
||||
// 4. 执行施法
|
||||
const castSucess = this.executeCast(e, skill.s_uuid, heroView,hset);
|
||||
// 5. 扣除资源和重置CD
|
||||
if (castSucess) {
|
||||
// 🔥 怪物不消耗蓝
|
||||
if (heroAttrs.fac !== FacSet.MON) {
|
||||
// 手动更新技能距离缓存
|
||||
heroAttrs.updateSkillDistanceCache(skills);
|
||||
}
|
||||
skills.resetCD(skill.s_uuid);
|
||||
}
|
||||
return castSucess;
|
||||
}
|
||||
public manualCast(e: ecs.Entity, s_uuid: number): boolean {
|
||||
if (!e) return false
|
||||
const skills = e.get(HeroSkillsComp)
|
||||
const heroAttrs = e.get(HeroAttrsComp)
|
||||
const heroView = e.get(HeroViewComp)
|
||||
if (!skills || !heroAttrs || !heroView) return false
|
||||
const slot = skills.getSkill(s_uuid)
|
||||
if (!slot) return false
|
||||
return this.startCast(e, slot, slot.hset)
|
||||
}
|
||||
public manualCastMax(e: ecs.Entity): boolean {
|
||||
const skills = e.get(HeroSkillsComp)
|
||||
if (!skills) return false
|
||||
for (const key in skills.skills) {
|
||||
const s_uuid = Number(key)
|
||||
const slot = skills.getSkill(s_uuid)
|
||||
if (slot && slot.hset === HSSet.max) {
|
||||
return this.manualCast(e, s_uuid)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
/**
|
||||
* 检查施法条件
|
||||
*/
|
||||
private checkCastConditions(skills: HeroSkillsComp, heroAttrs: HeroAttrsComp, s_uuid: number): boolean {
|
||||
// 检查角色状态
|
||||
if (heroAttrs.is_dead) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查控制状态(眩晕、冰冻)
|
||||
if (heroAttrs.isStun() || heroAttrs.isFrost()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查CD
|
||||
if (!skills.canCast(s_uuid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行施法
|
||||
*/
|
||||
private executeCast(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp,hset:HSSet): boolean {
|
||||
const heroAttrs=casterEntity.get(HeroAttrsComp)
|
||||
const config = SkillSet[s_uuid];
|
||||
if (!config) {
|
||||
mLogger.error(this.debugMode, 'SACastSystem', "[SACastSystem] 技能配置不存在:", s_uuid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 播放施法动画
|
||||
heroView.playSkillEffect(s_uuid);
|
||||
/**********************天赋处理*************************************************************************/
|
||||
// 2. 更新攻击类型的天赋触发值,技能和必杀级
|
||||
if(casterEntity.has(TalComp)){
|
||||
const talComp = casterEntity.get(TalComp);
|
||||
if (hset === HSSet.atk) talComp.updateCur(TriType.ATK);
|
||||
if (hset === HSSet.skill) talComp.updateCur(TriType.SKILL);
|
||||
}
|
||||
/**********************天赋处理*************************************************************************/
|
||||
// 根据技能类型执行不同逻辑
|
||||
if (config.SType === SType.heal) {
|
||||
return this.executeHealSkill(casterEntity, s_uuid, heroView, hset);
|
||||
} else if (config.SType === SType.shield) {
|
||||
return this.executeShieldSkill(casterEntity, s_uuid, heroView, hset);
|
||||
} else if (config.SType === SType.buff) {
|
||||
return this.executeBuffSkill(casterEntity, s_uuid, heroView, hset);
|
||||
}
|
||||
|
||||
// 获取目标位置(伤害技能)
|
||||
let targets = this.sTargets(heroView, s_uuid);
|
||||
if (targets.length === 0) {
|
||||
mLogger.warn(this.debugMode, 'SACastSystem', "[SACastSystem] 没有找到有效目标");
|
||||
return false;
|
||||
}
|
||||
// 2.1 普通攻击逻辑
|
||||
if (hset === HSSet.atk){
|
||||
let delay = GameConst.Battle.SKILL_CAST_DELAY
|
||||
let ext_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG);
|
||||
heroView.scheduleOnce(() => {
|
||||
this.createSkill(s_uuid, heroView,targets,ext_dmg);
|
||||
}, delay);
|
||||
//风怒wfuny 只针对 普通攻击起效
|
||||
if (heroAttrs.useCountTal(TalEffet.WFUNY)){
|
||||
let ext2_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG);
|
||||
let delay = GameConst.Battle.SKILL_CAST_DELAY
|
||||
heroView.playSkillEffect(s_uuid);
|
||||
//需要再添加 风怒动画
|
||||
heroView.scheduleOnce(() => {
|
||||
this.createSkill(s_uuid, heroView,targets,ext2_dmg);
|
||||
},delay);
|
||||
}
|
||||
}
|
||||
// 2.2 技能攻击逻辑
|
||||
if(hset === HSSet.skill){
|
||||
let delay = GameConst.Battle.SKILL_CAST_DELAY
|
||||
let ext_dmg = heroAttrs.useCountValTal(TalEffet.SKILL_DMG);
|
||||
heroView.scheduleOnce(() => {
|
||||
this.createSkill(s_uuid, heroView,targets,ext_dmg);
|
||||
}, delay);
|
||||
// 双技能 只针对 技能起效
|
||||
if(heroAttrs.useCountTal(TalEffet.D_SKILL)){
|
||||
let ext2_dmg = heroAttrs.useCountValTal(TalEffet.SKILL_DMG);
|
||||
let delay = GameConst.Battle.SKILL_CAST_DELAY
|
||||
heroView.playSkillEffect(s_uuid);
|
||||
//需要再添加 双技能动画
|
||||
heroView.scheduleOnce(() => {
|
||||
this.createSkill(s_uuid, heroView,targets,ext2_dmg);
|
||||
},delay);
|
||||
}
|
||||
}
|
||||
// 2.3 必杀技能逻辑
|
||||
if(hset === HSSet.max){
|
||||
let delay = GameConst.Battle.SKILL_CAST_DELAY
|
||||
heroView.playSkillEffect(s_uuid);
|
||||
//需要再添加 最大伤害动画
|
||||
heroView.scheduleOnce(() => {
|
||||
this.createSkill(s_uuid, heroView,targets);
|
||||
},delay);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建技能实体
|
||||
*/
|
||||
private createSkill(s_uuid: number, caster: HeroViewComp,targets:Vec3[]=[],ext_dmg:number=0) {
|
||||
// 检查节点有效性
|
||||
if (!caster.node || !caster.node.isValid) {
|
||||
mLogger.warn(this.debugMode, 'SACastSystem', "[SACastSystem] 施法者节点无效");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取场景节点
|
||||
const parent = caster.node.parent;
|
||||
if (!parent) {
|
||||
mLogger.warn(this.debugMode, 'SACastSystem', "[SACastSystem] 场景节点无效");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 创建技能实体
|
||||
const skill = ecs.getEntity<Skill>(Skill);
|
||||
|
||||
// 获取施法者位置作为起始位置
|
||||
const startPos = caster.node.position.clone();
|
||||
|
||||
const targetPos = targets[0]; // 使用第一个目标位置
|
||||
// mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem]: ${s_uuid}, 起始位置: ${startPos}, 目标位置: ${targetPos}`);
|
||||
// 加载技能实体(包括预制体、组件初始化等)
|
||||
skill.load(startPos, parent, s_uuid, targetPos, caster,ext_dmg);
|
||||
|
||||
}
|
||||
/**
|
||||
* 选择目标位置
|
||||
*/
|
||||
private sTargets(caster: HeroViewComp, s_uuid: number): Vec3[] {
|
||||
const heroAttrs = caster.ent.get(HeroAttrsComp);
|
||||
if (!heroAttrs) return [];
|
||||
const config = SkillSet[s_uuid];
|
||||
if (!config) return this.sDefaultTargets(caster, heroAttrs.fac);
|
||||
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, config.t_num ?? 1);
|
||||
const targets = this.sDamageTargets(caster, config, maxTargets);
|
||||
if (targets.length === 0) {
|
||||
targets.push(...this.sDefaultTargets(caster, heroAttrs.fac));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择伤害技能目标
|
||||
*/
|
||||
private sDamageTargets(caster: HeroViewComp, config: SkillConfig, maxTargets: number): Vec3[] {
|
||||
const targets: Vec3[] = [];
|
||||
const heroAttrs = caster.ent.get(HeroAttrsComp);
|
||||
if (!heroAttrs) return targets;
|
||||
|
||||
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
|
||||
const enemyPositions = this.findNearbyEnemies(caster, heroAttrs.fac, range);
|
||||
|
||||
// 选择最多maxTargets个目标
|
||||
for (let i = 0; i < Math.min(maxTargets, enemyPositions.length); i++) {
|
||||
targets.push(enemyPositions[i]);
|
||||
}
|
||||
|
||||
// 如果没有找到敌人,使用默认位置
|
||||
if (targets.length === 0) {
|
||||
targets.push(...this.sDefaultTargets(caster, heroAttrs.fac));
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 选择默认目标
|
||||
*/
|
||||
private sDefaultTargets(caster: HeroViewComp, fac: number): Vec3[] {
|
||||
const targets: Vec3[] = [];
|
||||
const defaultX = fac === 0 ? GameConst.Battle.DEFAULT_TARGET_X_RIGHT : GameConst.Battle.DEFAULT_TARGET_X_LEFT;
|
||||
targets.push(v3(defaultX, BoxSet.GAME_LINE, GameConst.Battle.DEFAULT_TARGET_Z));
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找附近的敌人
|
||||
*/
|
||||
private findNearbyEnemies(caster: HeroViewComp, fac: number, range: number): Vec3[] {
|
||||
const enemies: Vec3[] = [];
|
||||
if (!caster || !caster.node) return enemies;
|
||||
const currentPos = caster.node.position;
|
||||
const results: { pos: Vec3; dist: number; laneBias: number }[] = [];
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!model || !view || !view.node) return false;
|
||||
if (model.is_dead) return false;
|
||||
if (model.fac === fac) return false;
|
||||
const pos = view.node.position.clone();
|
||||
pos.y += GameConst.Battle.SEARCH_Y_OFFSET;
|
||||
const dist = Math.abs(currentPos.x - pos.x);
|
||||
if (dist <= range) {
|
||||
const laneBias = Math.abs(currentPos.y - pos.y);
|
||||
results.push({ pos: pos, dist, laneBias });
|
||||
}
|
||||
return false;
|
||||
});
|
||||
results.sort((a, b) => {
|
||||
if (a.laneBias !== b.laneBias) return a.laneBias - b.laneBias;
|
||||
return a.dist - b.dist;
|
||||
});
|
||||
for (const r of results) enemies.push(r.pos);
|
||||
return enemies;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查技能攻击范围内是否有敌人
|
||||
*/
|
||||
private hasEnemyInSkillRange(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number): boolean {
|
||||
if (!heroView || !heroView.node) return false;
|
||||
|
||||
const currentPos = heroView.node.position;
|
||||
const team = heroAttrs.fac;
|
||||
|
||||
let found = false;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!view || !view.node) return false;
|
||||
const distance = Math.abs(currentPos.x - view.node.position.x);
|
||||
if (model.fac !== team && !model.is_dead) {
|
||||
if (distance <= skillDistance) {
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查技能范围内是否有友军
|
||||
*/
|
||||
private hasTeamInSkillRange(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number): boolean {
|
||||
if (!heroView || !heroView.node) return false;
|
||||
|
||||
const currentPos = heroView.node.position;
|
||||
const team = heroAttrs.fac;
|
||||
|
||||
let found = false;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!view || !view.node) return false;
|
||||
const distance = Math.abs(currentPos.x - view.node.position.x);
|
||||
if (model.fac === team && !model.is_dead) {
|
||||
if (distance <= skillDistance) {
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Buff技能是否有目标
|
||||
*/
|
||||
private hasBuffTarget(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number, tGroup: TGroup): boolean {
|
||||
if (tGroup === TGroup.Self) return true; // 自身Buff总是可以释放
|
||||
|
||||
// 如果是团队Buff,检查范围内是否有队友
|
||||
if (tGroup === TGroup.Team || tGroup === TGroup.Ally) {
|
||||
return this.hasTeamInSkillRange(heroView, heroAttrs, skillDistance);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行Buff技能
|
||||
*/
|
||||
private executeBuffSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
|
||||
const hAttrsCom = casterEntity.get(HeroAttrsComp);
|
||||
const config = SkillSet[s_uuid];
|
||||
if (!config || !config.buffs || config.buffs.length === 0) return false;
|
||||
|
||||
const targets = this.sBuffTargets(casterEntity, heroView, hAttrsCom, config);
|
||||
if (targets.length === 0) return false;
|
||||
|
||||
const delay = GameConst.Battle.SKILL_CAST_DELAY;
|
||||
|
||||
heroView.scheduleOnce(() => {
|
||||
for (const targetEntity of targets) {
|
||||
const targetAttrs = targetEntity.get(HeroAttrsComp);
|
||||
if (!targetAttrs) continue;
|
||||
|
||||
// 应用所有配置的Buff
|
||||
for (const buffConf of config.buffs) {
|
||||
// 检查概率
|
||||
if (buffConf.chance >= 1 || Math.random() < buffConf.chance) {
|
||||
targetAttrs.addBuff(buffConf);
|
||||
mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem] Buff生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, Buff类型=${buffConf.buff}, 值=${buffConf.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, delay);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择Buff目标
|
||||
*/
|
||||
private sBuffTargets(casterEntity: ecs.Entity, casterView: HeroViewComp, heroAttrs: HeroAttrsComp, config: SkillConfig): ecs.Entity[] {
|
||||
const targets: ecs.Entity[] = [];
|
||||
const tGroup = config.TGroup;
|
||||
|
||||
// 1. 自身
|
||||
if (tGroup === TGroup.Self) {
|
||||
targets.push(casterEntity);
|
||||
return targets;
|
||||
}
|
||||
|
||||
// 2. 团队/友军
|
||||
if (tGroup === TGroup.Team || tGroup === TGroup.Ally) {
|
||||
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, Number(config.t_num ?? 1));
|
||||
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
|
||||
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!model || !view || !view.node) return;
|
||||
if (model.fac !== heroAttrs.fac) return; // 必须是同阵营
|
||||
if (model.is_dead) return;
|
||||
|
||||
const distance = Math.abs(casterView.node.position.x - view.node.position.x);
|
||||
if (distance <= range) {
|
||||
targets.push(e);
|
||||
}
|
||||
});
|
||||
|
||||
return targets.slice(0, maxTargets);
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行治疗技能
|
||||
*/
|
||||
private executeHealSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
|
||||
const hAttrsCom = casterEntity.get(HeroAttrsComp);
|
||||
const config = SkillSet[s_uuid];
|
||||
if (!config) return false;
|
||||
|
||||
const targets = this.sHealTargets(heroView, hAttrsCom, config);
|
||||
if (targets.length === 0) return false;
|
||||
|
||||
const healAmount = config.ap * hAttrsCom.Attrs[Attrs.HP_MAX]/100;
|
||||
const delay = GameConst.Battle.SKILL_CAST_DELAY;
|
||||
|
||||
heroView.scheduleOnce(() => {
|
||||
for (const targetEntity of targets) {
|
||||
const targetAttrs = targetEntity.get(HeroAttrsComp);
|
||||
const targetView = targetEntity.get(HeroViewComp);
|
||||
if (!targetAttrs || !targetView) continue;
|
||||
|
||||
targetAttrs.add_hp(healAmount, true);
|
||||
targetView.health(healAmount);
|
||||
mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem] 治疗生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, 治疗量=${healAmount}`);
|
||||
}
|
||||
}, delay);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行护盾技能
|
||||
*/
|
||||
private executeShieldSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
|
||||
const hAttrsCom = casterEntity.get(HeroAttrsComp);
|
||||
const config = SkillSet[s_uuid];
|
||||
if (!config) return false;
|
||||
|
||||
const targets = this.sShieldTargets(heroView, hAttrsCom, config);
|
||||
if (targets.length === 0) return false;
|
||||
|
||||
const shieldAmount = config.ap * hAttrsCom.Attrs[Attrs.HP_MAX]/100;
|
||||
const delay = GameConst.Battle.SKILL_CAST_DELAY;
|
||||
|
||||
heroView.scheduleOnce(() => {
|
||||
for (const targetEntity of targets) {
|
||||
const targetAttrs = targetEntity.get(HeroAttrsComp);
|
||||
const targetView = targetEntity.get(HeroViewComp);
|
||||
if (!targetAttrs || !targetView) continue;
|
||||
|
||||
targetAttrs.add_shield(shieldAmount, true);
|
||||
targetView.add_shield(shieldAmount);
|
||||
mLogger.log(this.debugMode, 'SACastSystem', `[SACastSystem] 护盾生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, 护盾量=${shieldAmount}`);
|
||||
}
|
||||
}, delay);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择治疗目标
|
||||
*/
|
||||
private sHealTargets(caster: HeroViewComp, heroAttrs: HeroAttrsComp, config: SkillConfig): ecs.Entity[] {
|
||||
const targets: ecs.Entity[] = [];
|
||||
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, Number(config.t_num ?? 1));
|
||||
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
|
||||
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!model || !view || !view.node) return;
|
||||
if (model.fac !== heroAttrs.fac) return;
|
||||
if (model.is_dead) return;
|
||||
|
||||
const distance = Math.abs(caster.node.position.x - view.node.position.x);
|
||||
if (distance <= range) {
|
||||
targets.push(e);
|
||||
}
|
||||
});
|
||||
|
||||
targets.sort((a, b) => {
|
||||
const attrsA = a.get(HeroAttrsComp);
|
||||
const attrsB = b.get(HeroAttrsComp);
|
||||
if (!attrsA || !attrsB) return 0;
|
||||
return attrsA.hp - attrsB.hp;
|
||||
});
|
||||
|
||||
return targets.slice(0, maxTargets);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择护盾目标
|
||||
*/
|
||||
private sShieldTargets(caster: HeroViewComp, heroAttrs: HeroAttrsComp, config: SkillConfig): ecs.Entity[] {
|
||||
const targets: ecs.Entity[] = [];
|
||||
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, Number(config.t_num ?? 1));
|
||||
const range = Number(config.dis ?? GameConst.Battle.DEFAULT_SEARCH_RANGE);
|
||||
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
|
||||
const model = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!model || !view || !view.node) return;
|
||||
if (model.fac !== heroAttrs.fac) return;
|
||||
if (model.is_dead) return;
|
||||
|
||||
const distance = Math.abs(caster.node.position.x - view.node.position.x);
|
||||
if (distance <= range) {
|
||||
targets.push(e);
|
||||
}
|
||||
});
|
||||
|
||||
return targets.slice(0, maxTargets);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据位置查找实体
|
||||
*/
|
||||
private findEntityAtPosition(pos: Vec3): ecs.Entity | null {
|
||||
let foundEntity: ecs.Entity | null = null;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
|
||||
const view = e.get(HeroViewComp);
|
||||
if (!view || !view.node) return false;
|
||||
const distance = Vec3.distance(pos, view.node.position);
|
||||
if (distance < 50) {
|
||||
foundEntity = e;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return foundEntity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroSkillsComp } from "./HeroSkills";
|
||||
|
||||
/**
|
||||
* ==================== 技能CD更新系统 ====================
|
||||
*
|
||||
* 职责:
|
||||
* 1. 每帧更新所有角色的技能CD
|
||||
* 2. 自动递减CD时间
|
||||
* 3. 管理技能冷却状态
|
||||
* 4. 优化CD计算性能
|
||||
*
|
||||
* 设计理念:
|
||||
* - 独立的CD管理系统
|
||||
* - 只负责时间递减,不处理施法逻辑
|
||||
* - 支持CD加速和减免效果
|
||||
*/
|
||||
@ecs.register('SkillCDSystem')
|
||||
export class SkillCDSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(HeroSkillsComp);
|
||||
}
|
||||
|
||||
update(e: ecs.Entity): void {
|
||||
if(!smc.mission.play ) return;
|
||||
if(smc.mission.pause) return
|
||||
const skills = e.get(HeroSkillsComp);
|
||||
if (!skills) return;
|
||||
const attrsCom = e.get(HeroAttrsComp);
|
||||
if (!attrsCom) return;
|
||||
if(smc.mission.stop_mon_action) return;
|
||||
if (attrsCom.isStun() || attrsCom.isFrost()) { // 眩晕和冰冻状态不更新CD
|
||||
return;
|
||||
}
|
||||
// 更新所有技能CD
|
||||
skills.updateCDs(this.dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算CD减免效果
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 更新技能就绪状态
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
import { _decorator } from "cc";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { basename } from "path/win32";
|
||||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
import { Attrs, BType } from "../common/config/HeroAttrs";
|
||||
import { BuffConf } from "../common/config/SkillSet";
|
||||
import { TalAttrs, talConf, TalEffet, TalTarget, TriType} from "../common/config/TalSet";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { ITalPts } from "../common/config/heroSet";
|
||||
|
||||
const { property } = _decorator;
|
||||
|
||||
/**
|
||||
* 天赋槽位接口定义
|
||||
* 描述单个天赋的数据结构
|
||||
*/
|
||||
export interface TalSlot {
|
||||
uuid: number; // 天赋唯一标识符
|
||||
name: string; // 天赋名称
|
||||
triType: TriType; // 天赋触发类型
|
||||
target: TalTarget;
|
||||
effet: TalEffet;
|
||||
attrs?:TalAttrs //触发的attrs效果的对应attrs value: number; // 触发的效果数值
|
||||
vType:BType; // 数值型还是百分比型
|
||||
value: number; // 触发的效果数值
|
||||
value_add: number; // 触发的效果数值增量
|
||||
count: number; // 执行次数,及可以触发的次数
|
||||
count_add: number; // 执行次数增量
|
||||
Trigger: number; // 天赋触发阈值
|
||||
Trigger_add: number; // 天赋触发阈值减值
|
||||
desc: string; // 天赋描述(说明触发条件和效果)
|
||||
cur: number; // 当前累积值
|
||||
}
|
||||
/**
|
||||
* 天赋系统组件类
|
||||
* 作为ECS架构中的组件,负责管理英雄的天赋系统
|
||||
*
|
||||
* 核心功能:
|
||||
* - 初始化英雄天赋系统
|
||||
* - 添加新天赋到英雄
|
||||
* - 累积天赋触发进度
|
||||
* - 检查并触发满足条件的天赋
|
||||
* - 管理天赋效果数值
|
||||
*/
|
||||
@ecs.register('TalComp', true)
|
||||
export class TalComp extends ecs.Comp {
|
||||
@property({ tooltip: "是否启用调试日志" })
|
||||
private debugMode: boolean = false;
|
||||
|
||||
/** 天赋集合,以天赋ID为键,存储所有已获得的天赋 */
|
||||
Tals: Record<number, TalSlot> = {};
|
||||
TalPts:Record<number,ITalPts> = {};
|
||||
init(heroUuid: number) {
|
||||
// 初始化天赋集合
|
||||
this.Tals = {};
|
||||
this.TalPts = {};
|
||||
// 监听升级事件
|
||||
oops.message.on(GameEvent.CanUpdateLv, this.onLevelUp, this);
|
||||
// 监听天赋选择事件
|
||||
oops.message.on(GameEvent.UseTalentCard, this.onUseTalentCard, this);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
oops.message.off(GameEvent.CanUpdateLv, this.onLevelUp, this);
|
||||
oops.message.off(GameEvent.UseTalentCard, this.onUseTalentCard, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理天赋选择事件
|
||||
* @param event 事件名
|
||||
* @param args 天赋UUID
|
||||
*/
|
||||
private onUseTalentCard(event: string, args: any) {
|
||||
const uuid = args as number;
|
||||
mLogger.log(this.debugMode, 'TalComp', `[TalComp] 收到天赋选择事件,添加天赋 ID: ${uuid}`);
|
||||
this.addTal(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理英雄升级事件,触发升级类型的天赋
|
||||
*/
|
||||
private onLevelUp(event: string, args: any) {
|
||||
// 只有当前实体是主角时才处理(虽然TalComp只挂载在主角上,但为了安全起见可以再确认,或者直接处理)
|
||||
// GameEvent.CanUpdateLv 事件参数 { lv: number }
|
||||
|
||||
mLogger.log(this.debugMode, 'TalComp', `[TalComp] 监听到升级事件,当前等级: ${args.lv}`);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 为英雄添加一个新天赋
|
||||
* @param uuid 要添加的天赋ID
|
||||
*
|
||||
* 添加流程:
|
||||
* 1. 检查天赋是否已存在
|
||||
* 2. 检查天赋配置是否存在
|
||||
* 3. 创建并初始化天赋数据
|
||||
*/
|
||||
addTal(uuid: number,v_add:number = 0,c_add:number = 0,t_add:number = 0) {
|
||||
// 检查天赋是否已存在
|
||||
if (this.Tals[uuid]) {
|
||||
mLogger.log(this.debugMode, 'TalComp', `[TalComp]天赋已存在,执行叠加逻辑 ID:${uuid}`);
|
||||
const tConf = talConf[uuid];
|
||||
if (tConf) {
|
||||
// 叠加效果数值
|
||||
this.Tals[uuid].value_add += tConf.value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取天赋配置
|
||||
const tConf = talConf[uuid];
|
||||
if (!tConf) {
|
||||
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋配置不存在,天赋ID:${uuid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建并初始化天赋数据
|
||||
this.Tals[uuid] = {
|
||||
uuid: uuid,
|
||||
name: tConf.name,
|
||||
triType: tConf.triType,
|
||||
target: tConf.target,
|
||||
effet: tConf.effet,
|
||||
attrs: tConf.attrs,
|
||||
vType: tConf.vType,
|
||||
value: tConf.value, // 效果数值初始为配置值
|
||||
value_add: v_add, // 效果数值增量初始为0
|
||||
count: tConf.count, // 执行次数,及可以触发的次数
|
||||
count_add: c_add, // 执行次数增量初始为0
|
||||
Trigger: tConf.Trigger, // 触发阈值(后续可从配置中读取)
|
||||
Trigger_add: t_add, // 触发阈值增量初始为0
|
||||
desc: tConf.desc,
|
||||
cur: 0, // 当前累积值初始为0
|
||||
};
|
||||
mLogger.log(this.debugMode, 'TalComp', `[TalComp]添加天赋成功,天赋ID:${uuid}`);
|
||||
}
|
||||
|
||||
checkTal() {
|
||||
return Object.keys(this.Tals).length > 0;
|
||||
}
|
||||
|
||||
getTriggers() {
|
||||
// 存储所有触发的天赋
|
||||
let Triggers: Record<string, TalSlot> = {};
|
||||
// 遍历所有天赋
|
||||
for (let uuid in this.Tals) {
|
||||
const talent = this.Tals[uuid];
|
||||
if (talent.cur >= (talent.Trigger - talent.Trigger_add)) { // 修复触发条件,累积值达到或超过触发阈值时触发
|
||||
mLogger.log(this.debugMode, 'TalComp', `[TalComp]天赋触发,天赋ID:${uuid}`);
|
||||
// 重置累积值
|
||||
talent.cur = 0;
|
||||
// 添加到触发列表
|
||||
Triggers[uuid] = talent;
|
||||
}
|
||||
}
|
||||
// 判断是否有天赋被触发
|
||||
return Triggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新天赋的效果数值
|
||||
* @param uuid 天赋ID
|
||||
* @param val 要增减的数值
|
||||
*
|
||||
* 功能:
|
||||
* - 用于调整天赋的实际效果数值
|
||||
* - 可通过正负数来增加或减少效果
|
||||
*/
|
||||
updateVal(uuid: number, val: number) {
|
||||
// 检查天赋是否存在
|
||||
if (!this.Tals[uuid]) {
|
||||
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋不存在,天赋ID:${uuid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新天赋效果数值
|
||||
this.Tals[uuid].value_add += val;
|
||||
}
|
||||
|
||||
updateTrigger(uuid: number, val: number) {
|
||||
// 检查天赋是否存在
|
||||
if (!this.Tals[uuid]) {
|
||||
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋不存在,天赋ID:${uuid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新天赋触发阈值
|
||||
this.Tals[uuid].Trigger_add += val;
|
||||
if (this.Tals[uuid].Trigger-this.Tals[uuid].Trigger_add <= 1) {
|
||||
this.Tals[uuid].Trigger_add = this.Tals[uuid].Trigger-1;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 更新指定类型天赋的累积值
|
||||
* @param triType 天赋触发类型
|
||||
* @param val 要增加的累积值,默认值为1
|
||||
*
|
||||
* 设计注意:
|
||||
* - 当前实现只会更新第一个匹配类型的天赋
|
||||
* - 累积值用于后续判断是否触发天赋效果
|
||||
*/
|
||||
updateCur(triType: TriType, val: number = 1) {
|
||||
// 遍历所有天赋
|
||||
for (let uuid in this.Tals) {
|
||||
const talent = this.Tals[uuid];
|
||||
|
||||
// 找到所有匹配类型的天赋并更新
|
||||
if (talent.triType == triType) {
|
||||
talent.cur += val;
|
||||
this.checkTrigger(talent.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
checkTrigger(uuid:number){
|
||||
const talent = this.Tals[uuid];
|
||||
if (talent.cur >= (talent.Trigger - talent.Trigger_add)) { // 修复触发条件,累积值达到或超过触发阈值时触发
|
||||
mLogger.log(this.debugMode, 'TalComp', `[TalComp]天赋触发,天赋ID:${uuid}`);
|
||||
for(let i=0;i<(talent.count+talent.count_add);i++){
|
||||
this.doTriggerTal(talent.uuid);
|
||||
}
|
||||
// 重置累积值
|
||||
talent.cur = 0;
|
||||
|
||||
}
|
||||
}
|
||||
//执行天赋触发效果
|
||||
// 功能:
|
||||
// - 根据天赋类型执行相应的效果
|
||||
// - 支持计数型和数值型天赋
|
||||
// --heroAttrs.addTalent 是计数型天赋buff heroAttrs.addTalBuff 是数值型天赋buff
|
||||
doTriggerTal(uuid: number) {
|
||||
// 检查天赋是否存在
|
||||
if (!this.Tals[uuid]) {
|
||||
mLogger.error(this.debugMode, 'TalComp', `[TalComp]天赋不存在,天赋ID:${uuid}`);
|
||||
return;
|
||||
}
|
||||
const talent = this.Tals[uuid];
|
||||
const heroAttrs=this.ent.get(HeroAttrsComp);
|
||||
switch(talent.effet){
|
||||
case TalEffet.ATK_DMG:
|
||||
heroAttrs.addCountTal(TalEffet.ATK_DMG, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.SKILL_DMG:
|
||||
heroAttrs.addCountTal(TalEffet.SKILL_DMG, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.DEF:
|
||||
heroAttrs.addCountTal(TalEffet.DEF, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.HP:
|
||||
heroAttrs.add_hp(talent.value + talent.value_add,talent.vType == BType.VALUE);
|
||||
break;
|
||||
case TalEffet.WFUNY:
|
||||
heroAttrs.addCountTal(TalEffet.WFUNY, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.D_SKILL:
|
||||
heroAttrs.addCountTal(TalEffet.D_SKILL, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.C_ATK:
|
||||
heroAttrs.addCountTal(TalEffet.C_ATK, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.C_SKILL:
|
||||
heroAttrs.addCountTal(TalEffet.C_SKILL, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.C_MSKILL:
|
||||
heroAttrs.addCountTal(TalEffet.C_MSKILL, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.BUFF: //临时buff
|
||||
heroAttrs.addValueTal(talent.uuid, talent.attrs, talent.vType, talent.value + talent.value_add);
|
||||
break;
|
||||
case TalEffet.ATTR: //永久属性
|
||||
// 修正:强制类型转换为 number (Attrs 是数字枚举)
|
||||
let attrIndex = talent.attrs as unknown as number;
|
||||
// 如果 TalAttrs 存在且需要映射,请取消注释下一行
|
||||
// attrIndex = Attrs[TalAttrs[talent.attrs]];
|
||||
|
||||
let b:BuffConf={buff:attrIndex, BType:talent.vType, value:talent.value + talent.value_add, time:0, chance:100 }
|
||||
heroAttrs.addBuff(b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 重置组件状态
|
||||
*/
|
||||
reset() {
|
||||
oops.message.off(GameEvent.CanUpdateLv, this.onLevelUp, this);
|
||||
oops.message.off(GameEvent.UseTalentCard, this.onUseTalentCard, this);
|
||||
this.Tals = {};
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user