feat: 实现DoT灼烧技能与debuff图标系统

本次提交完成了以下核心改动:
1.  调整fb-1~fb-6技能预制件的攻击偏移坐标,优化技能弹道位置
2.  新增DoT伤害机制,支持周期伤害/治疗的独立结算
3.  为Buff系统添加施法者属性快照,让DoT暴击、控制判定更准确
4.  新增顶栏debuff图标显示功能,支持灼烧等负面状态可视化
5.  为破晓先知等英雄替换为新的灼烧火球技能6104
6.  修复fb-2预制件的配置字段顺序与多余属性问题
This commit is contained in:
panFD
2026-08-15 14:36:38 +08:00
parent 8e5c0d3852
commit e8c6a7ca6a
17 changed files with 4118 additions and 970 deletions

View File

@@ -18,6 +18,7 @@ import { SkillTriggerHelper } from "./SkillTriggerHelper";
import { getMonsterGoldDrop } from "../map/RogueConfig";
import { MissionEconomy } from "../map/MissionEconomy";
import { MoveComp } from "./MoveComp";
import { buffManager, BuffApplyOverrides } from "./BuffManager";
/** 最终伤害数据接口
* 用于封装一次攻击计算的所有结果数据
@@ -119,10 +120,14 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
if (!TAttrsComp || TAttrsComp.is_dead || TAttrsComp.is_reviving) return reDate;
const casterEid = damageEvent.casterEid;
// DoT 事件dot_dmg>0 时伤害为固定 tick 值,无对应 SkillSet 配置s_uuid 位置是 buffId
const isDot = (damageEvent.dot_dmg ?? 0) > 0;
// DoT 事件的 Attrs 来自 ActiveBuff.source_attrs_snapshot可能为空兜底空对象避免取属性报错
if (isDot && !damageEvent.Attrs) damageEvent.Attrs = {};
// 获取技能配置
const skillConf = SkillSet[damageEvent.s_uuid];
if (!skillConf) return reDate;
if (!skillConf && !isDot) return reDate;
// 触发被攻击事件
this.onAttacked(target);
@@ -132,8 +137,8 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
const criticalChance = (damageEvent.Attrs[Attrs.critical] || 0) - TAttrsComp.getFinalCriticalRes();
const isCrit = this.checkChance(criticalChance);
// 计算基础伤害
let damage = this.dmgCount(damageEvent, TAttrsComp);
// 计算基础伤害DoT 直接用固定 tick 值,普攻走 dmgCount 公式
let damage = isDot ? (damageEvent.dot_dmg ?? 0) : this.dmgCount(damageEvent, TAttrsComp);
mLogger.log(this.debugMode, 'HeroAtkSystem', " dmgCount", damage)
if (isCrit) {
@@ -172,9 +177,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
}
}
// 受击计数:只有实际造成伤害才 +1被格挡不计数
TAttrsComp.atked_count++;
this.checkAndTriggerAtkedSkills(TAttrsComp, targetView);
// 受击计数:只有实际造成伤害才 +1被格挡不计数DoT 不计受击、不触发受击技
if (!isDot) {
TAttrsComp.atked_count++;
this.checkAndTriggerAtkedSkills(TAttrsComp, targetView);
}
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 eid:${casterEid} 的 伤害 ${damage},${isCrit ? "暴击" : "普通"}攻击,技能ID ${damageEvent.s_uuid}`);
@@ -186,24 +193,29 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
const stunChance = (damageEvent.Attrs[Attrs.stun_chance] || 0) - TAttrsComp.getFinalStunRes();
const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance);
// 击退:受击默认后退,击退属性不再作为概率,而是转换为后退距离的增量
// 击退增量 = 施法者击退强化(原击退概率)- 被攻击者击退抗性 + 击退距离强化
const knockbackBonus = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.getFinalKnockbackRes() || 0);
const knockbackExtra = Math.max(0, knockbackBonus + (damageEvent.Attrs[Attrs.knockback_distance] || 0));
// ✅ 触发视图层表现(伤害数字、受击动画、冰冻、击晕、击退)
if (targetView) {
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, knockbackExtra);
targetView.playEnd(skillConf.endAnm);
if (isDot) {
// DoT只飘伤害数字 + 刷新血条,不播受击动画/音效/击退
targetView.showDamage(damage, isCrit);
} else {
// 击退:受击默认后退,击退属性不再作为概率,而是转换为后退距离的增量
// 击退增量 = 施法者击退强化(原击退概率)- 被攻击者击退抗性 + 击退距离强化
const knockbackBonus = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.getFinalKnockbackRes() || 0);
const knockbackExtra = Math.max(0, knockbackBonus + (damageEvent.Attrs[Attrs.knockback_distance] || 0));
// 击退位移下沉到 MoveSystem直接写入 knockbackVelocity由 MoveSystem 每帧衰减并叠加位移
const moveComp = target.get(MoveComp);
if (moveComp) {
const dist = FightSet.BACK_RANG + knockbackExtra;
const isHero = TAttrsComp.fac === FacSet.HERO;
// 击退初速度0.1 秒时间常数衰减,总位移约等于 dist
const knockbackSpeed = dist * 16;
moveComp.knockbackVelocity = isHero ? -knockbackSpeed : knockbackSpeed;
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, knockbackExtra);
targetView.playEnd(skillConf.endAnm);
// 击退位移下沉到 MoveSystem直接写入 knockbackVelocity由 MoveSystem 每帧衰减并叠加位移
const moveComp = target.get(MoveComp);
if (moveComp) {
const dist = FightSet.BACK_RANG + knockbackExtra;
const isHero = TAttrsComp.fac === FacSet.HERO;
// 击退初速度0.1 秒时间常数衰减,总位移约等于 dist
const knockbackSpeed = dist * 16;
moveComp.knockbackVelocity = isHero ? -knockbackSpeed : knockbackSpeed;
}
}
if (isFrost) {
@@ -222,6 +234,15 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
}
}
// 命中附带 debuff如灼烧 DoT仅普攻路径复用友方 timed_buff_id 机制;
// 施法者 ap/完整属性快照取自伤害事件,供 BuffSystem tick 缩放与暴击/冰冻/击晕判定
if (!isDot && skillConf.timed_buff_id) {
const buffOverrides: BuffApplyOverrides = { source_attrs: damageEvent.Attrs };
if (skillConf.buff_value !== undefined) buffOverrides.value = skillConf.buff_value;
if (skillConf.buff_duration !== undefined) buffOverrides.duration = skillConf.buff_duration;
buffManager.applyBuff(target, skillConf.timed_buff_id, casterEid, damageEvent.Attrs[Attrs.ap] || 0, buffOverrides);
}
// 检查死亡
if (TAttrsComp.hp <= 0) {