- 在 HeroAttrsComp 中新增 combat_target_eid 和 enemy_in_cast_range 字段,用于跟踪当前战斗目标 - 修改 MoveSystem 在移动时同步更新战斗目标状态,并清理无效目标 - 重构 SCastSystem 的自动施法逻辑,优先使用已锁定的战斗目标而非重新搜索 - 调整技能 6005 和 6006 的 hit_count 参数,分别改为 2 和 3 次打击 - 为友方技能施法添加事件派发机制,通知其他系统技能释放
619 lines
21 KiB
TypeScript
619 lines
21 KiB
TypeScript
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||
import { Attrs, BType } from "../common/config/HeroAttrs";
|
||
import { BuffConf, BuffRunType } from "../common/config/SkillSet";
|
||
import { HeroDisVal, HeroInfo, HType } from "../common/config/heroSet";
|
||
import { mLogger } from "../common/Logger";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
import { HeroViewComp } from "./HeroViewComp";
|
||
import { _decorator } from "cc";
|
||
|
||
|
||
/**
|
||
* 可撤销的属性型 Buff/Debuff 实例
|
||
* - 仅用于 Timed 类型
|
||
* - 进入 BUFFS/DEBUFFS 后会在到期时自动反向回滚
|
||
*/
|
||
interface ActiveBuffState {
|
||
id: number
|
||
attr: Attrs
|
||
sourceUuid: number
|
||
value: number
|
||
BType: BType
|
||
time: number
|
||
}
|
||
/**
|
||
* 间隔触发型效果实例
|
||
* - 不直接进入 BUFFS/DEBUFFS
|
||
* - 由 HeroBuffSystem 每帧推进 tick/remain 并触发执行
|
||
*/
|
||
interface IntervalBuffState {
|
||
id: number
|
||
attr: Attrs
|
||
sourceUuid: number
|
||
value: number
|
||
BType: BType
|
||
interval: number
|
||
remain: number
|
||
tick: number
|
||
}
|
||
@ecs.register('HeroAttrs')
|
||
export class HeroAttrsComp extends ecs.Comp {
|
||
public debugMode: boolean = false;
|
||
|
||
Ebus:any=null!
|
||
// ==================== 角色基础信息 ====================
|
||
hero_uuid: number = 1001;
|
||
hero_name: string = "hero";
|
||
lv: number = 1;
|
||
type: number = 0; // 0近战 1远程 2辅助
|
||
fac: number = 0; // 0:hero 1:monster
|
||
// ==================== 基础属性(有初始值) ====================
|
||
ap: number = 0; // 基础攻击
|
||
hp: number = 100; // 基础血量
|
||
hp_max: number = 100; // 最大血量
|
||
speed: number = 100; // 基础移动速度
|
||
dis: number = 100; // 基础距离
|
||
shield: number = 0; // 当前护盾
|
||
shield_max: number = 0; // 最大护盾值
|
||
|
||
// ==================== 攻击属性 (补充) ====================
|
||
a_cd: number = 0; // 攻击计时
|
||
s_cd: number = 0; // 技能计时
|
||
a_cd_max: number = 0; // 攻击CD
|
||
s_cd_max: number = 0; // 技能CD
|
||
// ==================== 暴击与命中属性 ====================
|
||
critical: number = 0; // 暴击率
|
||
critical_dmg: number = 0; // 暴击伤害
|
||
|
||
// ==================== 特殊效果属性 ====================
|
||
freeze_chance: number = 0; // 冰冻概率
|
||
stun_chance: number = 0; // 眩晕概率
|
||
back_chance: number = 0; // 击退概率
|
||
slow_chance: number = 0; // 减速概率
|
||
|
||
// ==================== 武器进化相关 ====================
|
||
puncture: number = 0; // 穿刺次数
|
||
puncture_dmg: number = 0; // 穿刺伤害
|
||
wfuny: number = 0; // 风怒
|
||
|
||
// ==================== 增益效果属性 ====================
|
||
revive_count: number = 0; // 复活次数
|
||
revive_time: number = 0; // 复活时间
|
||
invincible_time: number = 0;// 无敌时间
|
||
|
||
|
||
in_stun=false
|
||
in_frost=false
|
||
|
||
boom: boolean = false; // 自爆怪
|
||
|
||
|
||
// ==================== 脏标签标记 ====================
|
||
dirty_hp: boolean = false; // 血量变更标记
|
||
dirty_shield: boolean = false; // 护盾变更标记
|
||
|
||
// ==================== 技能距离缓存 ====================
|
||
maxSkillDistance: number = 0; // 最远技能攻击距离(缓存,受MP影响)
|
||
minSkillDistance: number = 0; // 最近技能攻击距离(缓存,不受MP影响,用于停止位置判断)
|
||
|
||
// ==================== Buff/Debuff 系统 ====================
|
||
/** 持久型buff数组 - 不会自动过期 */
|
||
BUFFS: Record<number, ActiveBuffState[]> = {};
|
||
DEBUFFS: Record<number, ActiveBuffState[]> = {};
|
||
INTERVAL_EFFECTS: IntervalBuffState[] = [];
|
||
private buffInstanceId = 0;
|
||
|
||
// ==================== 标记状态 ====================
|
||
is_dead: boolean = false;
|
||
is_count_dead: boolean = false;
|
||
is_atking: boolean = false; // 是否正在攻击
|
||
is_stop: boolean = false; // 是否正在停止
|
||
is_boss: boolean = false;
|
||
is_big_boss: boolean = false;
|
||
is_master: boolean = false;
|
||
is_friend: boolean = false;
|
||
is_kalami: boolean = false;
|
||
is_reviving: boolean = false; // 是否正在复活中
|
||
// ==================== 计数统计 ====================
|
||
atk_count: number = 0; // 攻击次数
|
||
atked_count: number = 0; // 被攻击次数
|
||
killed_count:number=0;
|
||
atk_id:number=0; //普通攻击技能id
|
||
skill_id:number=0; //技能攻击技能id
|
||
can_atk=false
|
||
can_skill=false
|
||
combat_target_eid: number = -1;
|
||
enemy_in_cast_range: boolean = false;
|
||
start(){
|
||
}
|
||
// ==================== BUFF 系统初始化 ====================
|
||
/**
|
||
* 初始化角色的 buff debuff
|
||
* 从 HeroInfo 读取初始配置,建立属性系统
|
||
*/
|
||
initAttrs() {
|
||
// 清空现有 buff/debuff
|
||
this.BUFFS = {};
|
||
this.DEBUFFS = {};
|
||
this.INTERVAL_EFFECTS = [];
|
||
this.buffInstanceId = 0;
|
||
// 获取英雄配置
|
||
const heroInfo = HeroInfo[this.hero_uuid];
|
||
if (!heroInfo) return;
|
||
|
||
|
||
}
|
||
/*******************基础属性管理********************/
|
||
|
||
add_hp(value:number,isValue:boolean){
|
||
const oldHp = this.hp;
|
||
let addValue = value;
|
||
if(!isValue){
|
||
addValue = value * this.hp_max / 100;
|
||
}
|
||
|
||
// ✅ 数据层只负责数据修改,不调用视图层
|
||
// let heroView = this.ent.get(HeroViewComp);
|
||
// if(heroView && addValue > 0){
|
||
// heroView.health(addValue);
|
||
// }
|
||
|
||
this.hp += addValue;
|
||
this.hp = Math.max(0, Math.min(this.hp, this.hp_max));
|
||
this.dirty_hp = true; // ✅ 仅标记需要更新
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', ` HP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldHp.toFixed(1)} -> ${this.hp.toFixed(1)}`);
|
||
}
|
||
}
|
||
|
||
add_shield(value:number,isValue:boolean){
|
||
const oldShield = this.shield;
|
||
let addValue = value;
|
||
if(!isValue){
|
||
addValue = value * this.hp_max / 100;
|
||
}
|
||
this.shield += addValue;
|
||
this.shield_max += addValue;
|
||
if (this.shield < 0) this.shield = 0;
|
||
if (this.shield_max < 0) this.shield_max = 0;
|
||
this.dirty_shield = true; // 标记护盾需要更新
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldShield.toFixed(1)} -> ${this.shield.toFixed(1)}`);
|
||
}
|
||
}
|
||
// ==================== BUFF 管理 ====================
|
||
/**
|
||
* 添加 buff 效果
|
||
* @param buffConf buff 配置
|
||
*/
|
||
addBuff(buffConf: BuffConf) {
|
||
const normalized = this.normalizeBuffValue(buffConf);
|
||
const runType = this.resolveRunType(buffConf);
|
||
// Interval:按间隔触发,写入 INTERVAL_EFFECTS,由系统统一执行
|
||
if (runType === BuffRunType.Interval) {
|
||
const interval = buffConf.interval && buffConf.interval > 0 ? buffConf.interval : 1;
|
||
const remain = buffConf.time > 0 ? buffConf.time : interval;
|
||
this.INTERVAL_EFFECTS.push({
|
||
id: ++this.buffInstanceId,
|
||
attr: buffConf.buff,
|
||
sourceUuid: buffConf.uuid,
|
||
value: normalized.value,
|
||
BType: normalized.BType,
|
||
interval,
|
||
remain,
|
||
tick: interval
|
||
});
|
||
return;
|
||
}
|
||
// Permanent:一次性生效,不写入 BUFFS/DEBUFFS,不存在自动撤销
|
||
if (runType === BuffRunType.Permanent) {
|
||
this.applyAttrChange(buffConf.buff, normalized.value, normalized.BType);
|
||
return;
|
||
}
|
||
// Timed:先应用,再记录,后续 updateList 到期后自动反向移除
|
||
const duration = buffConf.time > 0 ? buffConf.time : 1;
|
||
|
||
const targetList = buffConf.isDebuff ? this.DEBUFFS : this.BUFFS;
|
||
const attrKey = buffConf.buff as unknown as number; // 强制转换 key 类型以适配 Record
|
||
if (!targetList[attrKey]) {
|
||
targetList[attrKey] = [];
|
||
}
|
||
|
||
const currentBuffs = targetList[attrKey];
|
||
currentBuffs.push({
|
||
id: ++this.buffInstanceId,
|
||
attr: buffConf.buff,
|
||
sourceUuid: buffConf.uuid,
|
||
value: normalized.value,
|
||
BType: normalized.BType,
|
||
time: duration
|
||
});
|
||
|
||
this.applyAttrChange(buffConf.buff, normalized.value, normalized.BType);
|
||
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', `添加Buff: ${buffConf.name}, 属性:${buffConf.buff}, 值:${normalized.value}, 时间:${duration}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* runType 解析优先级:
|
||
* 1) 显式配置 runType
|
||
* 2) 兼容旧配置:存在 interval 视为 Interval
|
||
* 3) time>0 视为 Timed,否则 Permanent
|
||
*/
|
||
private resolveRunType(buffConf: BuffConf): BuffRunType {
|
||
if (buffConf.runType !== undefined) return buffConf.runType;
|
||
if (buffConf.interval && buffConf.interval > 0) return BuffRunType.Interval;
|
||
return buffConf.time > 0 ? BuffRunType.Timed : BuffRunType.Permanent;
|
||
}
|
||
|
||
/**
|
||
* 把配置值统一转换为“可直接写入容器和结算”的数值
|
||
* - RATIO 会在写入前转换为 VALUE
|
||
* - BOOLEAN 保持原类型
|
||
*/
|
||
private normalizeBuffValue(buffConf: BuffConf): { value: number; BType: BType } {
|
||
if (buffConf.BType === BType.BOOLEAN) {
|
||
return { value: buffConf.value, BType: BType.BOOLEAN };
|
||
}
|
||
return {
|
||
value: this.resolveBuffValue(buffConf.buff, buffConf.value, buffConf.BType),
|
||
BType: BType.VALUE
|
||
};
|
||
}
|
||
|
||
private resolveBuffValue(attr: Attrs, value: number, type: BType): number {
|
||
if (type !== BType.RATIO) return value;
|
||
if (attr === Attrs.hp || attr === Attrs.shield) {
|
||
return this.hp_max * value / 100;
|
||
}
|
||
if (attr === Attrs.hp_max || attr === Attrs.shield_max) {
|
||
return this[attr] * value / 100;
|
||
}
|
||
if (typeof this[attr] === "number") {
|
||
return (this[attr] as number) * value / 100;
|
||
}
|
||
return value;
|
||
}
|
||
|
||
/**
|
||
* 通用属性修改应用
|
||
* @param attr 属性名
|
||
* @param value 变化值
|
||
* @param type 数值类型 (0:固定值, 1:百分比)
|
||
* @param reverse 是否反向应用 (用于移除 buff)
|
||
*/
|
||
private applyAttrChange(attr: Attrs, value: number, type: BType, reverse: boolean = false) {
|
||
let finalValue = value;
|
||
|
||
// 如果是移除 buff,取反
|
||
if (reverse) {
|
||
finalValue = -value;
|
||
}
|
||
|
||
// 护盾特殊处理:同时修改当前值和最大值
|
||
if (attr === Attrs.shield && type === BType.VALUE) {
|
||
this.shield += finalValue;
|
||
this.shield_max += finalValue;
|
||
this.shield = Math.max(0, this.shield);
|
||
this.shield_max = Math.max(0, this.shield_max);
|
||
this.dirty_shield = true;
|
||
return;
|
||
}
|
||
|
||
if (type === BType.RATIO) {
|
||
finalValue = this.resolveBuffValue(attr, finalValue, BType.RATIO);
|
||
if (typeof this[attr] === 'number') {
|
||
this[attr] = (this[attr] as number) + finalValue;
|
||
}
|
||
} else if (type === BType.BOOLEAN) {
|
||
// 布尔型/状态型:value > 0 为 true/计数+1,移除时 -1
|
||
// 这里使用计数器方式来支持多个同类状态叠加
|
||
// 例如 IN_FROST 是一个状态,不是属性。
|
||
// 需要在 HeroAttrsComp 中定义对应的状态计数器,或者利用 DEBUFFS 列表的存在性判断状态
|
||
// 暂时直接修改属性(如果属性是 boolean)
|
||
if (typeof this[attr] === 'boolean') {
|
||
this[attr] = !reverse; // 添加设为 true, 移除设为 false (不仅确,多个冰冻会出问题)
|
||
// 正确做法:updateBuffsDebuffs 中根据列表是否为空来设置状态
|
||
}
|
||
} else {
|
||
// VALUE 固定值
|
||
if (typeof this[attr] === 'number') {
|
||
this[attr] = (this[attr] as number) + finalValue;
|
||
}
|
||
}
|
||
|
||
// 标记脏数据(如果有对应的 dirty 标记)
|
||
if (attr === Attrs.hp) this.dirty_hp = true;
|
||
if (attr === Attrs.shield) this.dirty_shield = true;
|
||
}
|
||
//======更新cd========//
|
||
updateCD(dt: number){
|
||
if(this.atk_id !=0&&!this.can_atk){
|
||
this.a_cd+=dt
|
||
if(this.a_cd >= this.a_cd_max) this.can_atk = true
|
||
}
|
||
if(this.skill_id !=0&&!this.can_skill){
|
||
this.s_cd+=dt
|
||
if(this.s_cd >= this.s_cd_max) this.can_skill = true
|
||
}
|
||
}
|
||
isStun(): boolean {
|
||
return this.in_stun;
|
||
}
|
||
isFrost(): boolean {
|
||
return this.in_frost;
|
||
}
|
||
triggerAtkCD() {
|
||
this.a_cd = 0;
|
||
this.can_atk = false;
|
||
}
|
||
triggerSkillCD() {
|
||
this.s_cd = 0;
|
||
this.can_skill = false;
|
||
}
|
||
// ==================== 临时 BUFF/DEBUFF 更新 ====================
|
||
/**
|
||
* 更新临时 buff/debuff 的剩余时间
|
||
* @param dt 时间增量
|
||
*/
|
||
updateBuffsDebuffs(dt: number) {
|
||
this.updateList(this.BUFFS, dt);
|
||
this.updateList(this.DEBUFFS, dt);
|
||
|
||
// 更新状态标记 (根据 DEBUFFS 列表是否存在有效项)
|
||
this.updateStatusFlags();
|
||
}
|
||
|
||
/**
|
||
* 仅做“采集触发事件”,不做数值执行
|
||
* 实际执行由 HeroBuffSystem.applyIntervalEffect 统一处理
|
||
*/
|
||
collectIntervalEffectsBySystem(dt: number): IntervalBuffState[] {
|
||
const triggered: IntervalBuffState[] = [];
|
||
for (let i = this.INTERVAL_EFFECTS.length - 1; i >= 0; i--) {
|
||
const state = this.INTERVAL_EFFECTS[i];
|
||
state.remain -= dt;
|
||
state.tick -= dt;
|
||
while (state.tick <= 0 && state.remain > 0) {
|
||
triggered.push({
|
||
id: state.id,
|
||
attr: state.attr,
|
||
sourceUuid: state.sourceUuid,
|
||
value: state.value,
|
||
BType: state.BType,
|
||
interval: state.interval,
|
||
remain: state.remain,
|
||
tick: state.tick
|
||
});
|
||
state.tick += state.interval;
|
||
}
|
||
if (state.remain <= 0) {
|
||
this.INTERVAL_EFFECTS.splice(i, 1);
|
||
}
|
||
}
|
||
return triggered;
|
||
}
|
||
applyStoredEffect(attr: Attrs, value: number, type: BType) {
|
||
this.applyAttrChange(attr, value, type);
|
||
}
|
||
|
||
/**
|
||
* Timed Buff/Debuff 到期更新
|
||
* - 到期时调用 applyAttrChange(reverse=true) 回滚
|
||
* - 该函数只处理 BUFFS/DEBUFFS,不处理 INTERVAL_EFFECTS
|
||
*/
|
||
private updateList(list: Record<number, ActiveBuffState[]>, dt: number) {
|
||
for (const attrKey in list) {
|
||
const buffs = list[attrKey];
|
||
if (!buffs || buffs.length === 0) continue;
|
||
|
||
// 倒序遍历以便移除
|
||
for (let i = buffs.length - 1; i >= 0; i--) {
|
||
const buff = buffs[i];
|
||
buff.time -= dt;
|
||
|
||
if (buff.time <= 0) {
|
||
this.applyAttrChange(buff.attr, buff.value, buff.BType, true);
|
||
|
||
buffs.splice(i, 1);
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', `Buff过期: 属性:${buff.attr}, 恢复值:${buff.value}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果该属性的 buff 列表空了,可以清理 key (可选)
|
||
if (buffs.length === 0) {
|
||
delete list[attrKey];
|
||
}
|
||
}
|
||
}
|
||
|
||
private updateStatusFlags() {
|
||
// 检查特定状态的 Debuff 列表是否为空,来更新 boolean 标志
|
||
this.checkStatus(Attrs.IN_FROST, 'in_frost');
|
||
this.checkStatus(Attrs.IN_STUN, 'in_stun');
|
||
}
|
||
|
||
private checkStatus(attr: Attrs, flagName: string) {
|
||
const key = attr as unknown as number;
|
||
const hasBuff = this.DEBUFFS[key] && this.DEBUFFS[key].length > 0;
|
||
|
||
// 只有当状态改变时才赋值,避免每帧赋值
|
||
if (this[flagName] !== hasBuff) {
|
||
this[flagName] = hasBuff;
|
||
// 状态变化日志
|
||
if (this.debugMode) {
|
||
mLogger.log(this.debugMode, 'HeroAttrs', `状态变更: ${this.hero_name}, ${flagName} = ${hasBuff}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ==================== 技能距离缓存管理 ====================
|
||
/**
|
||
* 更新技能距离缓存
|
||
* 在技能初始化、新增技能、MP变化时调用
|
||
* @param skillsComp 技能组件
|
||
*/
|
||
public updateSkillDistanceCache(skill_id:number): void {
|
||
void skill_id;
|
||
const rangeType = this.type as HType.Melee | HType.Mid | HType.Long;
|
||
const maxRange = HeroDisVal[rangeType];
|
||
let minRange = 0;
|
||
if (rangeType === HType.Mid) {
|
||
minRange = HeroDisVal[HType.Melee];
|
||
} else if (rangeType === HType.Long) {
|
||
minRange = HeroDisVal[HType.Mid];
|
||
}
|
||
this.maxSkillDistance = maxRange;
|
||
this.minSkillDistance = minRange;
|
||
}
|
||
|
||
/**
|
||
* 获取缓存的最远技能攻击距离
|
||
* @returns 最远攻击距离
|
||
*/
|
||
public getCachedMaxSkillDistance(): number {
|
||
return this.maxSkillDistance;
|
||
}
|
||
|
||
/**
|
||
* 获取缓存的最近技能攻击距离
|
||
* @returns 最近攻击距离
|
||
*/
|
||
public getCachedMinSkillDistance(): number {
|
||
return this.minSkillDistance;
|
||
}
|
||
|
||
reset() {
|
||
// 重置为初始状态
|
||
this.hero_uuid = 1001;
|
||
this.hero_name = "hero";
|
||
this.lv = 1;
|
||
this.type = 0;
|
||
this.fac = 0;
|
||
this.ap = 0;
|
||
this.hp = 100;
|
||
this.hp_max = 100;
|
||
this.speed = 100;
|
||
this.dis = 100;
|
||
this.shield = 0;
|
||
this.shield_max = 0;
|
||
|
||
// 重置新增属性
|
||
this.a_cd = 0;
|
||
this.s_cd = 0;
|
||
this.a_cd_max = 0;
|
||
this.s_cd_max = 0;
|
||
this.critical = 0;
|
||
this.critical_dmg = 0;
|
||
this.freeze_chance = 0;
|
||
this.stun_chance = 0;
|
||
this.back_chance = 0;
|
||
this.slow_chance = 0;
|
||
this.revive_count = 0;
|
||
this.revive_time = 0;
|
||
this.invincible_time = 0;
|
||
this.puncture = 0;
|
||
this.puncture_dmg = 0;
|
||
this.wfuny = 0;
|
||
this.boom = false;
|
||
|
||
this.in_frost = false;
|
||
this.in_stun = false;
|
||
|
||
this.BUFFS = {};
|
||
this.DEBUFFS = {};
|
||
this.INTERVAL_EFFECTS = [];
|
||
this.buffInstanceId = 0;
|
||
// 重置技能距离缓存
|
||
this.maxSkillDistance = 0;
|
||
this.minSkillDistance = 0;
|
||
|
||
this.is_dead = false;
|
||
this.is_count_dead = false;
|
||
this.is_atking = false;
|
||
this.is_stop = false;
|
||
this.is_boss = false;
|
||
this.is_big_boss = false;
|
||
this.is_friend = false;
|
||
this.is_kalami = false;
|
||
this.is_reviving = false;
|
||
|
||
this.atk_count = 0;
|
||
this.atked_count = 0;
|
||
this.killed_count =0;
|
||
this.atk_id = 0;
|
||
this.skill_id = 0;
|
||
this.can_atk=false
|
||
this.can_skill=false
|
||
this.combat_target_eid = -1;
|
||
this.enemy_in_cast_range = false;
|
||
// 重置脏标签
|
||
this.dirty_hp = false;
|
||
this.dirty_shield = false;
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
@ecs.register('HeroBuffSystem')
|
||
export class HeroBuffSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||
filter(): ecs.IMatcher {
|
||
return ecs.allOf(HeroAttrsComp);
|
||
}
|
||
|
||
update(e: ecs.Entity): void {
|
||
if (!smc.mission.play || smc.mission.pause) return;
|
||
const attrs = e.get(HeroAttrsComp);
|
||
if (!attrs || attrs.is_dead) return;
|
||
// 1) 推进 Timed Buff/Debuff 生命周期
|
||
attrs.updateBuffsDebuffs(this.dt);
|
||
// 2) 收集本帧到点的 Interval 触发
|
||
const triggered = attrs.collectIntervalEffectsBySystem(this.dt);
|
||
if (triggered.length === 0) return;
|
||
const view = e.get(HeroViewComp);
|
||
// 3) 统一执行数值与表现
|
||
for (const effect of triggered) {
|
||
this.applyIntervalEffect(attrs, view, effect);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 间隔效果统一执行入口
|
||
* - 先做数值结算
|
||
* - 再按实际变化触发表现,避免无效动画
|
||
*/
|
||
private applyIntervalEffect(attrs: HeroAttrsComp, view: HeroViewComp | null, effect: IntervalBuffState) {
|
||
if (effect.attr === Attrs.hp) {
|
||
const oldHp = attrs.hp;
|
||
attrs.add_hp(effect.value, true);
|
||
const delta = attrs.hp - oldHp;
|
||
if (view && delta !== 0) {
|
||
view.playIntervalEffect(effect.attr, delta, effect.sourceUuid);
|
||
}
|
||
return;
|
||
}
|
||
if (effect.attr === Attrs.shield) {
|
||
const oldShield = attrs.shield;
|
||
attrs.add_shield(effect.value, true);
|
||
const delta = attrs.shield - oldShield;
|
||
if (view && delta !== 0) {
|
||
view.playIntervalEffect(effect.attr, delta, effect.sourceUuid);
|
||
}
|
||
return;
|
||
}
|
||
attrs.applyStoredEffect(effect.attr, effect.value, effect.BType);
|
||
if (view) {
|
||
view.playIntervalEffect(effect.attr, effect.value, effect.sourceUuid);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|