Files
pixelheros/assets/script/game/hero/BuffSystem.ts
panFD ed9232cf0b feat(skill&shield): 实现护盾持续时间功能,调整魔法盾冷却
1.  为护盾技能添加duration配置,支持永久/限时护盾
2.  新增护盾倒计时逻辑,到期自动清空护盾
3.  调整英雄6301魔法盾的冷却时间为10秒,新增7秒持续时长配置
4.  重构HeroAttrsComp,新增护盾持续时间字段与清空护盾方法
2026-09-13 14:04:00 +08:00

137 lines
6.3 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.
/**
* BuffSystem — 计时性 buff/debuff 的核心结算系统。
*
* 使用 oops-framework 的 Timer 实现固定 0.1s 步长 tick:
* - 遍历 BuffComp.buffs,每层 remaining -= 步长
* - 若配置了 tick 效果(DoT/HoT),累计 tick_acc 并按 interval 结算伤害/治疗
* - 到期的层从数组中移除;数组空则清理 key
*
* 控制类 buff(冰冻/眩晕)不在此处做特殊处理:层存活期间 hasControl 判定
* 即生效,到期移除后控制自然解除。旧 HeroBuffSystem 已下线。
*/
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Timer } from "db://oops-framework/core/common/timer/Timer";
import { BuffList } from "../common/config/BuffSet";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { BuffComp, ActiveBuff } from "./BuffComp";
import { DamageQueueHelper } from "./DamageQueueComp";
import { mLogger } from "../common/Logger";
/** Buff 模块调试日志开关 */
const DEBUG = true;
/** 固定步长(秒) */
const TICK_STEP = 0.1;
@ecs.register('BuffSystem')
export class BuffSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
private timer = new Timer(TICK_STEP);
// HeroAttrsComp 可选,用于 DoT/HoT 的血量结算与护盾倒计时
private processShieldTimer(attrsComp: HeroAttrsComp | null): void {
if (!attrsComp) return;
// 永久护盾(duration<=0)不参与倒计时
if (attrsComp.shield_duration <= 0) return;
attrsComp.shield_duration -= TICK_STEP;
if (attrsComp.shield_duration <= 0) {
attrsComp.clear_shield();
mLogger.log(DEBUG, "BuffSystem", `护盾到期: ${attrsComp.hero_name}(eid:${attrsComp.ent?.eid ?? "-"}) 护盾已清空`);
}
}
// filter 改为 allOf(HeroAttrsComp):护盾倒计时挂在 HeroAttrsComp 上,
// 若按 BuffComp 过滤,无 buff 的实体不会被遍历,护盾到期清空会失效
filter(): ecs.IMatcher {
return ecs.allOf(HeroAttrsComp);
}
update(e: ecs.Entity): void {
if (!this.timer.update(this.dt)) return;
// HeroAttrsComp 必有(filter 保证),用于 DoT/HoT 血量结算与护盾倒计时
const attrsComp = e.get(HeroAttrsComp);
this.processShieldTimer(attrsComp);
// BuffComp 可选:无 buff 时仅做护盾倒计时
const buffComp = e.has(BuffComp) ? e.get(BuffComp) : null;
if (!buffComp) return;
// ---- 处理所有 buff 层 ----
let anyChanged = false;
buffComp.buffs.forEach((layers, buffId) => {
const cfg = BuffList[buffId];
if (!cfg) return;
// 从后往前遍历,安全 splice 移除到期层
for (let i = layers.length - 1; i >= 0; i--) {
const layer = layers[i];
// 周期效果结算(先于到期判断,避免到期帧丢失末次 tick)
if (cfg.tick && attrsComp) {
this.processTick(e, buffId, layer, cfg.tick, attrsComp);
}
// 永久 buff(duration<=0)不递减 remaining
if (cfg.duration > 0) {
layer.remaining -= TICK_STEP;
}
// 到期移除
if (cfg.duration > 0 && layer.remaining <= 0) {
layers.splice(i, 1);
anyChanged = true;
mLogger.log(DEBUG, "BuffSystem", `buff 到期移除: ${cfg.name}(${buffId}) eid=${e.eid} 剩余层数=${layers.length}`);
}
}
// 数组空则清理 key,避免空 Map 项残留
if (layers.length === 0) {
buffComp.buffs.delete(buffId);
anyChanged = true;
}
});
if (anyChanged) {
buffComp.dirty_buffs = true;
}
}
/**
* 处理单层的周期效果(DoT/HoT)。
* 累计 tick_acc 到 interval 时结算一次伤害/治疗。
*
* DoT(负值):转为 DamageEvent 入队,由 HeroAtkSystem.doAttack 走完整结算链
* (护盾抵扣 / 暴击 / 冰冻 / 击晕 / 飘字 / 死亡链),Attrs 取 buff 层的施法者属性快照;
* HoT(正值):保持直接回血,不走伤害链。
*
* @param target 被作用者实体(DoT 入队用)
* @param buffId buff 配置 id(DoT 事件借 s_uuid 字段携带,仅作日志追溯)
* @param layer 当前 buff 层实例
* @param tick 周期效果配置
* @param attrs 被作用者属性组件
*/
private processTick(target: ecs.Entity, buffId: number, layer: ActiveBuff, tick: { interval: number; damage_or_heal: number; scale_by_ap_pct: number }, attrs: HeroAttrsComp): void {
// interval 非正值守卫,避免死循环
if (tick.interval <= 0) return;
layer.tick_acc += TICK_STEP;
while (layer.tick_acc >= tick.interval) {
layer.tick_acc -= tick.interval;
// 按 ap 缩放后取整:floor(dmg * (1 + ap_snapshot * scale_pct / 10000))
// damage_or_heal 优先取药水卡覆写值
const baseAmount = layer.value_override !== undefined ? layer.value_override : tick.damage_or_heal;
const scale = 1 + layer.source_ap_snapshot * tick.scale_by_ap_pct / 10000;
const amount = Math.floor(baseAmount * scale);
const buffName = BuffList[buffId]?.name ?? String(buffId);
if (amount < 0) {
// DoT 走伤害队列:dot_dmg 传正值,doAttack 内识别并走固定伤害分支
mLogger.log(DEBUG, "BuffSystem", `[debuff伤害] 「${buffName}」(buffId:${buffId}) tick 结算 -> ${attrs.hero_name}(eid:${target.eid}) 基础:${baseAmount} ap缩放:${scale.toFixed(3)} 入队伤害:${-amount} 剩余:${layer.remaining.toFixed(1)}s`);
DamageQueueHelper.addDamageToEntity(target, layer.source_attrs_snapshot ?? null, layer.source_uuid, buffId, 0, 1, -amount);
} else if (amount > 0) {
mLogger.log(DEBUG, "BuffSystem", `[buff治疗] 「${buffName}」(buffId:${buffId}) tick 结算 -> ${attrs.hero_name}(eid:${target.eid}) 基础:${baseAmount} ap缩放:${scale.toFixed(3)} 回血:${amount}`);
attrs.add_hp(amount);
}
}
}
}