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 = {}; DEBUFFS: Record = {}; 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, 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); } } }