/** * BuffManager — buff/debuff 的应用、驱散、查询管理器。 * * 设计说明: * - 非 ECS 组件,是普通 class 单例(参考 smc 的单例暴露模式)。 * - 对外提供 applyBuff / dispel / getStack / hasControl 四组核心能力。 * - 控制类 buff 采用"双写"策略:既写入 BuffComp(新框架),也同步调用 * HeroAttrsComp.toFrost/toStun(旧框架),保证过渡期表现一致。 */ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { BuffCategory, BuffConfig, BuffList } from "../common/config/BuffSet"; import { FightSet } from "../common/config/GameSet"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { BuffComp, ActiveBuff } from "./BuffComp"; /** applyBuff 的覆盖参数(预留按需覆写持续时间) */ export interface BuffApplyOverrides { duration?: number; } class BuffManagerImpl { /** * 对目标施加一个 buff 实例。 * * 叠层规则: * - max_stack === 0:无限叠加,始终 push 新层 * - existing.length < max_stack:push 新层 * - 已满:替换最老层(数组末尾) * * 控制类(is_control)额外双写旧字段(frost/stun)。 * * @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) return; const buffComp = this.ensureBuffComp(target); const duration = overrides?.duration ?? cfg.duration; let layers = buffComp.buffs.get(buffId); if (!layers) { layers = []; buffComp.buffs.set(buffId, layers); } const newLayer = new ActiveBuff(buffId, duration, sourceUuid, sourceAp); if (cfg.max_stack === 0 || layers.length < cfg.max_stack) { layers.push(newLayer); } else { // 已满,移除最老层(数组首部)后追加新层,保持 FIFO 刷新语义 layers.shift(); layers.push(newLayer); } // 控制类双写旧字段 if (cfg.is_control) { this.applyControlEffect(target, cfg, duration); } buffComp.dirty_buffs = true; } /** * 驱散目标身上匹配的 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; const controlKindsCleared: ('frost' | 'stun')[] = []; 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); // 记录被驱散的控制类型,用于后续重算 if (cfg.is_control && cfg.control_kind && cfg.control_kind !== 'none') { controlKindsCleared.push(cfg.control_kind); } }); // 控制类驱散后重算旧字段 if (controlKindsCleared.length > 0) { this.recomputeControlState(target, controlKindsCleared); } 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 和旧字段,兼容过渡期。 */ hasControl(target: ecs.Entity, kind: 'frost' | 'stun'): boolean { // 新框架:检查 BuffComp 中是否有对应控制类 buff if (target.has(BuffComp)) { 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; } } // 旧字段兜底 if (target.has(HeroAttrsComp)) { const attrs = target.get(HeroAttrsComp); if (kind === 'frost') return attrs.isFrost(); if (kind === 'stun') return attrs.isStun(); } return false; } // ==================== 私有方法 ==================== /** * 确保目标实体拥有 BuffComp,没有则添加。 */ private ensureBuffComp(target: ecs.Entity): BuffComp { if (!target.has(BuffComp)) { target.add(BuffComp); } return target.get(BuffComp); } /** * 控制效果双写:将控制类 buff 同步写入旧字段。 * duration 会被换算成 toFrost/toStun 所需的倍数参数。 */ private applyControlEffect(target: ecs.Entity, cfg: BuffConfig, duration: number): void { if (!target.has(HeroAttrsComp)) return; const attrs = target.get(HeroAttrsComp); if (cfg.control_kind === 'frost') { // toFrost(time) 内部会乘以 FROST_TIME,这里反除得到倍数 attrs.toFrost(duration / FightSet.FROST_TIME); } else if (cfg.control_kind === 'stun') { attrs.toStun(duration / FightSet.STUN_TIME); } } /** * 驱散控制 buff 后重算旧字段。 * 如果对应类型的控制 buff 已全部清除,则立即将旧字段归零。 * * @param target 目标实体 * @param clearedKinds 本次被驱散的控制类型列表 */ private recomputeControlState(target: ecs.Entity, clearedKinds: ('frost' | 'stun')[]): void { if (!target.has(HeroAttrsComp)) return; const attrs = target.get(HeroAttrsComp); // 检查是否还有同类型控制 buff 残留 const hasFrostInBuff = this.hasControlInBuff(target, 'frost'); const hasStunInBuff = this.hasControlInBuff(target, 'stun'); if (clearedKinds.includes('frost') && !hasFrostInBuff) { attrs.frost_end_time = 0; } if (clearedKinds.includes('stun') && !hasStunInBuff) { attrs.stun_end_time = 0; } } /** * 仅检查 BuffComp 中是否存在指定控制类型(不查旧字段)。 */ private hasControlInBuff(target: ecs.Entity, kind: 'frost' | 'stun'): 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; } } /** Buff 管理器单例(全局唯一实例) */ export const buffManager = new BuffManagerImpl();