Files
pixelheros/assets/script/game/hero/BuffManager.ts
pan be17d18ff3 feat(skill-system): 新增计时debuff机制,支持技能命中敌方附加减益效果
本次提交完整实现了技能对敌附加debuff的完整流程:
1.  新增TimedDebuffSnapshot数据结构,用于透传debuff配置
2.  在SkillConfig和SkillOverrides中新增debuff相关配置项
3.  实现HeroAtkSystem中debuff的判定与施加逻辑
4.  完善后台配置、前端编辑器的debuff配置支持
5.  调整了buff目标判定逻辑,修复了原有默认目标的语义混淆问题
6.  优化了BuffSystem的调试日志,新增了debuff分类标识
7.  批量更新了现有英雄技能的debuff配置示例
2026-09-08 16:37:13 +08:00

146 lines
5.8 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.
/**
* BuffManager — buff/debuff 的应用、驱散、查询管理器。
*
* 设计说明:
* - 非 ECS 组件,是普通 class 单例(参考 smc 的单例暴露模式)。
* - 对外提供 applyBuff / dispel / getStack / hasControl 四组核心能力。
* - 控制类 buff(冰冻/眩晕)统一写入 BuffComp,由 BuffSystem 计时到期;
* 旧字段 frost_end_time/stun_end_time 已随 HeroBuffSystem 一并下线。
*/
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { BuffCategory, BuffList } from "../common/config/BuffSet";
import { BuffComp, ActiveBuff } from "./BuffComp";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { mLogger } from "../common/Logger";
/** applyBuff 的覆盖参数(预留按需覆写持续时间 / 修饰数值) */
export interface BuffApplyOverrides {
duration?: number;
/** 覆写计时 buff 的修饰数值(modifiers.value / tick.damage_or_heal),由药水卡 buff_value 自定义档位 */
value?: number;
/** 施法者完整属性快照(DoT tick 判定暴击/冰冻/击晕用),结构同 DamageEvent.Attrs */
source_attrs?: any;
}
class BuffManagerImpl {
/** 调试日志开关:运行时可通过 buffManager.debug = false 关闭(测试期默认开启) */
public debug: boolean = true;
/**
* 对目标施加一个 buff 实例。
*
* 叠层规则:
* - max_stack === 0:无限叠加,始终 push 新层
* - existing.length < max_stack:push 新层
* - 已满:移除最老层(数组首部)后追加新层,保持 FIFO 刷新语义
*
* @param target 目标实体
* @param buffId BuffList 配置 id
* @param sourceUuid 施法者 uuid(0=系统/无主)
* @param sourceAp 施法者攻击力快照(用于 DoT 缩放)
* @param overrides 可选覆写参数
*/
applyBuff(target: ecs.Entity, buffId: number, sourceUuid: number = 0, sourceAp: number = 0, overrides?: BuffApplyOverrides): void {
const cfg = BuffList[buffId];
if (!cfg) {
mLogger.warn(true, "BuffManager", `applyBuff: buffId=${buffId} 配置不存在,跳过`);
return;
}
const buffComp = this.ensureBuffComp(target);
const duration = overrides?.duration ?? cfg.duration;
const targetName = target.get(HeroAttrsComp)?.hero_name ?? `eid:${target.eid}`;
// buff/debuff 类别标签,便于日志区分挂载语义
const categoryTag = cfg.category === BuffCategory.Debuff ? "debuff" : cfg.category === BuffCategory.Control ? "control" : "buff";
let layers = buffComp.buffs.get(buffId);
if (!layers) {
layers = [];
buffComp.buffs.set(buffId, layers);
}
const newLayer = new ActiveBuff(buffId, duration, sourceUuid, sourceAp, overrides?.value, overrides?.source_attrs);
let refreshed = false;
if (cfg.max_stack === 0 || layers.length < cfg.max_stack) {
layers.push(newLayer);
} else {
// 满层 FIFO:移除最老层后追加新层
layers.shift();
layers.push(newLayer);
refreshed = true;
}
buffComp.dirty_buffs = true;
mLogger.log(this.debug, "BuffManager", `[挂${categoryTag}] 「${cfg.name}」(buffId:${buffId}) -> ${targetName} 时长:${duration}s 数值:${overrides?.value ?? "-"} 来源ap:${sourceAp} 当前层数:${layers.length}${cfg.max_stack > 0 ? `/${cfg.max_stack}` : ""}${refreshed ? "(满层刷新)" : ""}`);
}
/**
* 驱散目标身上匹配的 buff。
*
* @param target 目标实体
* @param category 仅驱散该类别(不传=所有类别)
* @param excludeIds 排除的 buff id 列表
* @returns 移除的 buff 种类数
*/
dispel(target: ecs.Entity, category?: BuffCategory, excludeIds?: number[]): number {
if (!target.has(BuffComp)) return 0;
const buffComp = target.get(BuffComp);
let removedKinds = 0;
buffComp.buffs.forEach((layers, buffId) => {
const cfg = BuffList[buffId];
if (!cfg) return;
if (excludeIds && excludeIds.includes(buffId)) return;
if (category !== undefined && cfg.category !== category) return;
if (layers.length > 0) removedKinds++;
buffComp.buffs.delete(buffId);
});
buffComp.dirty_buffs = true;
return removedKinds;
}
/**
* 获取目标身上指定 buff 的当前叠层数。
* @returns 层数(未命中返回 0)
*/
getStack(target: ecs.Entity, buffId: number): number {
if (!target.has(BuffComp)) return 0;
const buffComp = target.get(BuffComp);
return buffComp.buffs.get(buffId)?.length ?? 0;
}
/**
* 判断目标是否处于指定类型的控制状态下。
* 唯一判定来源:BuffComp 中是否存在对应 control_kind 的活跃控制 buff。
*/
hasControl(target: ecs.Entity, kind: 'frost' | 'stun' | 'paralyze'): boolean {
if (!target.has(BuffComp)) return false;
const buffComp = target.get(BuffComp);
for (const [buffId, layers] of buffComp.buffs) {
if (layers.length === 0) continue;
const cfg = BuffList[buffId];
if (cfg?.is_control && cfg.control_kind === kind) return true;
}
return false;
}
// ==================== 私有方法 ====================
/**
* 确保目标实体拥有 BuffComp,没有则添加。
*/
private ensureBuffComp(target: ecs.Entity): BuffComp {
if (!target.has(BuffComp)) {
target.add(BuffComp);
}
return target.get(BuffComp);
}
}
/** Buff 管理器单例(全局唯一实例) */
export const buffManager = new BuffManagerImpl();