Files
pixelheros/assets/script/game/hero/HeroAttrsComp.ts
panw 887ba6064c feat(技能系统): 扩展Buff运行类型并修复治疗与护盾配置
- 扩展BuffRunType枚举,新增Permanent和Timed类型,明确区分永久、定时和间隔效果
- 在HeroAttrsComp中重构addBuff方法,根据配置智能解析运行类型
- 为治疗(10301)和护盾(10302)配置显式添加runType: Permanent,确保逻辑一致性
- 修复定时Buff的持续时间处理,避免time为0时使用默认值1
2026-03-13 10:41:47 +08:00

578 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Attrs, BType } from "../common/config/HeroAttrs";
import { BuffConf, BuffRunType, SkillDisVal, SkillRange } from "../common/config/SkillSet";
import { HeroInfo, HType } from "../common/config/heroSet";
import { mLogger } from "../common/Logger";
import { smc } from "../common/SingletonModuleComp";
import { HeroViewComp } from "./HeroViewComp";
import { _decorator } from "cc";
interface ActiveBuffState {
id: number
attr: Attrs
sourceUuid: number
value: number
BType: BType
time: number
}
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 = true;
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
rangeType:SkillRange = SkillRange.Melee;
// ==================== 基础属性(有初始值) ====================
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
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; // ✅ 仅标记需要更新
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; // 标记护盾需要更新
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);
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;
}
if (runType === BuffRunType.Permanent) {
this.applyAttrChange(buffConf.buff, normalized.value, normalized.BType);
return;
}
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);
mLogger.log(this.debugMode, 'HeroAttrs', `添加Buff: ${buffConf.name}, 属性:${buffConf.buff}, 值:${normalized.value}, 时间:${duration}`);
}
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;
}
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();
}
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);
}
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);
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;
let rangeType = this.rangeType;
if (rangeType === undefined || rangeType === null) {
if (this.type === HType.remote) {
rangeType = SkillRange.Long;
} else if (this.type === HType.mage || this.type === HType.support) {
rangeType = SkillRange.Mid;
} else {
rangeType = SkillRange.Melee;
}
}
const maxRange = SkillDisVal[rangeType];
let minRange = 0;
if (rangeType === SkillRange.Mid) {
minRange = SkillDisVal[SkillRange.Melee];
} else if (rangeType === SkillRange.Long) {
minRange = SkillDisVal[SkillRange.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.rangeType = SkillRange.Melee;
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_master = 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.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;
attrs.updateBuffsDebuffs(this.dt);
const triggered = attrs.collectIntervalEffectsBySystem(this.dt);
if (triggered.length === 0) return;
const view = e.get(HeroViewComp);
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);
}
}
}