refactor(hero): 移除天赋系统和相关属性,简化英雄架构
- 删除 SCDSystem、HeroAttrSystem 等独立系统,将功能整合到现有组件 - 移除 TalComp 天赋组件及相关配置(TalSet、AttrSet、CardSet) - 清理 HeroAttrs 中未使用的属性枚举,保留核心战斗属性 - 简化 Hero 实体创建逻辑,不再为主角挂载天赋组件 - 移除 SingletonModuleComp 中与天赋、经验、收集相关的数据管理
This commit is contained in:
@@ -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