feat(skill-system): 新增计时debuff机制,支持技能命中敌方附加减益效果

本次提交完整实现了技能对敌附加debuff的完整流程:
1.  新增TimedDebuffSnapshot数据结构,用于透传debuff配置
2.  在SkillConfig和SkillOverrides中新增debuff相关配置项
3.  实现HeroAtkSystem中debuff的判定与施加逻辑
4.  完善后台配置、前端编辑器的debuff配置支持
5.  调整了buff目标判定逻辑,修复了原有默认目标的语义混淆问题
6.  优化了BuffSystem的调试日志,新增了debuff分类标识
7.  批量更新了现有英雄技能的debuff配置示例
This commit is contained in:
pan
2026-09-08 16:37:13 +08:00
parent f5908a9adf
commit be17d18ff3
16 changed files with 214 additions and 124 deletions

View File

@@ -22,8 +22,8 @@ export enum DTType {
/**
* 计时 buff(timed_buff_id)的施加目标。
* 缺省(undefined)时按技能主效果推导:伤害技能挂被命中者,辅助技能挂友方目标。
* 显式配置后打破 TGroup 默认绑定,实现"伤害技能命中后给自己回血"等跨阵营效果。
* 缺省(undefined)时按技能主效果推导:伤害技能挂施法者自身(增益语义),辅助技能挂友方目标。
* 显式配置后可切换到全体友方/全体敌方等;命中后对敌减益请用 timed_debuff_id(目标固定为被命中者)。
*/
export enum BuffTarget {
Hit = 0, // 被命中者(敌方技能的默认行为)
@@ -169,8 +169,13 @@ export interface SkillConfig {
buff_value?: number, // 覆写计时 buff 的修饰数值(modifiers.value / tick.damage_or_heal),由药水卡自定义档位
buff_duration?: number, // 覆写计时 buff 的持续时间(秒),不配置则用 BuffList 默认值
buff_chance?: number, // 计时 buff 施加概率(百分点,缺省 100)
/** 计时 buff 施加目标覆写:缺省按技能主效果推导(伤害→被命中者,辅助→友方) */
/** 计时 buff 施加目标覆写:缺省按技能主效果推导(伤害→施法者自身,辅助→友方目标) */
buff_target?: BuffTarget,
// ---- 命中敌方后施加的计时 debuff(仅伤害路径 HeroAtkSystem 结算,目标固定为被命中者) ----
timed_debuff_id?: number, // 计时 debuff 配置 id(指向 BuffList),命中敌人后施加
debuff_value?: number, // 覆写 debuff 的修饰数值(modifiers.value / tick.damage_or_heal)
debuff_duration?: number, // 覆写 debuff 的持续时间(秒),不配置则用 BuffList 默认值
debuff_chance?: number, // debuff 施加概率(百分点,缺省 100)
call_hero?: number, // 召唤技能召唤英雄id(可选)
summon_count?: number, // 单次施法召唤的技能实体个数(未配置时回退 prefab 上 SkillView.summon_count)
is_accel?: boolean, // 是否逐渐加速飞行
@@ -202,8 +207,13 @@ export interface SkillOverrides {
buff_value?: number;
buff_duration?: number;
buff_chance?: number;
/** 计时 buff 施加目标覆写:缺省按技能主效果推导(伤害→被命中者,辅助→友方) */
/** 计时 buff 施加目标覆写:缺省按技能主效果推导(伤害→施法者自身,辅助→友方目标) */
buff_target?: BuffTarget;
/** 命中敌方后施加的计时 debuff id(指向 BuffList),目标固定为被命中者,仅伤害路径结算 */
timed_debuff_id?: number;
debuff_value?: number;
debuff_duration?: number;
debuff_chance?: number;
call_hero?: number;
/** 单次施法召唤的技能实体个数(覆盖 prefab 上 SkillView.summon_count;>1 视为召唤类,不再参与风怒等额外触发) */
summon_count?: number;

View File

@@ -1,4 +1,4 @@
import { v3 } from "cc"
import { v3 } from "cc"
import { BoxSet, FacSet, FightSet } from "./GameSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet"
import { BuffList, BuffConfig, ModOp } from "./BuffSet"
@@ -546,7 +546,7 @@ export const HeroInfo: Record<number, heroInfo> = {
skills: {
6004: { uuid: 6004, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: {"ap":100}, desc: "挥剑对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7012,"buff_value":30,"buff_chance":100,"buff_duration":5}, desc: "普通攻击造成 120% 攻击伤害" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7007,"debuff_value":-10,"debuff_chance":100}, desc: "普通攻击造成 120% 攻击伤害" },
],
},
6301: { uuid: 6301, name: "魔法盾", lv: 1, cd: 8, ccd: 0, overrides: {"ap":30}, desc: "{target}获得生命最大值{ap}%的护盾",
@@ -575,7 +575,7 @@ export const HeroInfo: Record<number, heroInfo> = {
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120}, desc: "普通攻击造成 120% 攻击伤害" },
],
},
6101: { uuid: 6101, name: "气斩", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150,"timed_buff_id":7002,"icon":"Stat_AttackSpeed_02"}, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)",
6101: { uuid: 6101, name: "气斩", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7002,"icon":"Stat_AttackSpeed_02"}, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"buff_value":20} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":180,"bck":60,"buff_value":40}, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck},并使目标易伤(持续{dur}秒)" },
@@ -646,13 +646,13 @@ export const HeroInfo: Record<number, heroInfo> = {
skills: {
6002: { uuid: 6002, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Fast].cd, ccd: 0, overrides: {"ap":100}, desc: "挥刀对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7007,"buff_value":30,"buff_chance":30}, desc: "普通攻击有{buff_chance}%概率使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7007,"debuff_value":30,"debuff_chance":30}, desc: "普通攻击有{debuff_chance}%概率使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
6103: { uuid: 6103, name: "闪电斩", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150}, desc: "高速斩击{target},造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250,"timed_buff_id":7007,"buff_value":40}, desc: "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350,"timed_buff_id":7007,"buff_value":60}, desc: "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":250,"timed_debuff_id":7007,"debuff_value":40}, desc: "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":350,"timed_debuff_id":7007,"debuff_value":60}, desc: "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
},
@@ -896,10 +896,10 @@ export const HeroInfo: Record<number, heroInfo> = {
skills: {
6007: { uuid: 6007, name: "淬毒箭", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0, overrides: {"ap":100}, desc: "射出箭矢,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7005,"buff_value":30,"buff_chance":30}, desc: "普通攻击有{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7005,"debuff_value":30,"debuff_chance":30}, desc: "普通攻击有{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
6150: { uuid: 6150, name: "万箭齐发·毒", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150,"timed_buff_id":7005,"buff_value":30,"buff_chance":50,"icon":"4-Apply-Poison"}, desc: "毒箭如雨,对{target}造成攻击力{ap}%伤害,{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)",
6150: { uuid: 6150, name: "万箭齐发·毒", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7005,"debuff_value":30,"debuff_chance":50,"icon":"4-Apply-Poison"}, desc: "毒箭如雨,对{target}造成攻击力{ap}%伤害,{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200,"buff_value":40,"buff_chance":60} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"buff_value":60,"buff_chance":80,"hit_count":2}, desc: "毒箭如雨,对{target}造成攻击力{ap}%伤害,{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒),每支箭可命中{hit_count}个目标" },
@@ -908,7 +908,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
atking: {
6007: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "蚀骨毒箭", t_num: 3, overrides: {"ap":80,"timed_buff_id":7005,"buff_value":40,"buff_chance":100}, desc: "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
{ lv: 4, s_lv: SkillLv.Lv1, name: "蚀骨毒箭", t_num: 3, overrides: {"ap":80,"timed_debuff_id":7005,"debuff_value":40,"debuff_chance":100}, desc: "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" },
],
},
field: [
@@ -954,10 +954,10 @@ export const HeroInfo: Record<number, heroInfo> = {
skills: {
6012: { uuid: 6012, name: "蚀甲酸瓶", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Normal].cd, ccd: 0, overrides: {"ap":100}, desc: "掷出酸瓶,对{target}造成攻击力{ap}%伤害",
lv_entries: [
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_buff_id":7008,"buff_chance":30}, desc: "普通攻击有{buff_chance}%概率使目标破甲(防御降低,持续{dur}秒)" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"timed_debuff_id":7008,"debuff_chance":30}, desc: "普通攻击有{debuff_chance}%概率使目标破甲(防御降低,持续{dur}秒)" },
],
},
6203: { uuid: 6203, name: "碎甲能量波", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150,"timed_buff_id":7008}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)",
6203: { uuid: 6203, name: "碎甲能量波", lv: 1, cd: 8, ccd: 0, overrides: {"ap":150,"timed_debuff_id":7008}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)",
lv_entries: [
{ lv: 8, s_lv: SkillLv.Lv2, overrides: {"ap":200} },
{ lv: 16, s_lv: SkillLv.Lv3, overrides: {"ap":250,"buff_value":-70}, desc: "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲加深(防御大幅降低,持续{dur}秒)" },
@@ -966,7 +966,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
fstart: {
6012: [
{ lv: 4, s_lv: SkillLv.Lv1, name: "先手蚀甲", t_num: 1, overrides: {"ap":150,"timed_buff_id":7008}, desc: "战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒)" },
{ lv: 4, s_lv: SkillLv.Lv1, name: "先手蚀甲", t_num: 1, overrides: {"ap":150,"timed_debuff_id":7008}, desc: "战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒)" },
],
},
field: [
@@ -1103,7 +1103,7 @@ export const HeroInfo: Record<number, heroInfo> = {
},
6105: {
uuid: 6105, name: "亡灵首领-法师", path: "mb5", fac: FacSet.MON, lv: 6, rarity: 5, type: HType.Long, grow_type: HGrowType.Mage, role: HRole.Mage, monType: MonType.LongBoss, hp: HERO_ROLE_BASE[HRole.Mage].hp, ap: HERO_ROLE_BASE[HRole.Mage].ap, def: HERO_ROLE_BASE[HRole.Mage].def, speed: 110,
skills: { 6011: { uuid: 6011, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100, hit_count: 2, timed_buff_id: 7002 } } }, info: "后排高AP Boss"
skills: { 6011: { uuid: 6011, lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: { ap: 100, hit_count: 2, timed_debuff_id: 7002 } } }, info: "后排高AP Boss"
},
6106: {
uuid: 6106, name: "亡灵首领-骑马战士", path: "mb6", fac: FacSet.MON, lv: 6, rarity: 5, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, monType: MonType.MeleeBoss, hp: HERO_ROLE_BASE[HRole.Tank].hp, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def, speed: 130,

View File

@@ -11,6 +11,7 @@
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 的覆盖参数(预留按需覆写持续时间 / 修饰数值) */
@@ -46,10 +47,12 @@ class BuffManagerImpl {
mLogger.warn(true, "BuffManager", `applyBuff: buffId=${buffId} 配置不存在,跳过`);
return;
}
mLogger.log(this.debug, "BuffManager", `[挂buff] 「${cfg.name}」(buffId:${buffId}) -> target_eid:${target.eid} duration:${overrides?.duration ?? cfg.duration} value:${overrides?.value ?? "-"} source_ap:${sourceAp}`);
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) {
@@ -59,14 +62,18 @@ class BuffManagerImpl {
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 ? "(满层刷新)" : ""}`);
}
/**

View File

@@ -19,7 +19,7 @@ import { DamageQueueHelper } from "./DamageQueueComp";
import { mLogger } from "../common/Logger";
/** Buff 模块调试日志开关 */
const DEBUG = false;
const DEBUG = true;
/** 固定步长(秒) */
const TICK_STEP = 0.1;
@@ -106,10 +106,13 @@ export class BuffSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
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);
}
}

View File

@@ -2,6 +2,14 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { HeroViewComp } from "./HeroViewComp";
import { SkillOverrides } from "../common/config/SkillSet";
/** 命中后对敌 debuff 快照(由施法技能实体自合并后配置提取并透传,HeroAtkSystem 据此结算) */
export interface TimedDebuffSnapshot {
id: number;
value?: number;
duration?: number;
chance?: number;
}
/**
* ==================== 伤害事件数据 ====================
*
@@ -10,29 +18,31 @@ import { SkillOverrides } from "../common/config/SkillSet";
export interface DamageEvent {
/** 伤害属性数据 */
Attrs: any;
/** 施法者ID */
casterEid: number;
/** 技能UUID */
s_uuid: number;
/** 伤害创建时间戳(用于排序和调试) */
timestamp: number;
/** 伤害事件ID(用于去重和调试) */
eventId: string;
ext_dmg:number //额外伤害
dmg_ratio:number//伤害比例
ext_dmg: number //额外伤害
dmg_ratio: number//伤害比例
/**
* DoT 固定伤害值(>0 时本事件为 DoT tick 结算):
* 跳过 dmgCount 公式直接用该值,且不走击退/受击动画,其余(暴击/冰冻/击晕/护盾/死亡链)与普攻一致
*/
dot_dmg?:number
dot_dmg?: number
/** 命中后概率触发额外技能(由施法技能实体快照自合并后配置,HeroAtkSystem 据此判定;
* overrides 字段透传给被触发技能,用于自定义伤害/数值) */
proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides }
/** 命中后对敌 debuff(由施法技能实体自合并后配置快照透传;缺省时 HeroAtkSystem 回退读 SkillSet 底座) */
timed_debuff?: TimedDebuffSnapshot
}
/**
@@ -48,16 +58,16 @@ export class DamageQueueComp extends ecs.Comp {
/** 伤害事件队列 */
damageEvents: DamageEvent[] = [];
private readIndex: number = 0;
/** 队列创建时间 */
createTime: number = 0;
/** 是否正在处理中 */
isProcessing: boolean = false;
/** 已处理的伤害数量 */
processedCount: number = 0;
reset() {
this.damageEvents.length = 0;
this.readIndex = 0;
@@ -65,33 +75,34 @@ export class DamageQueueComp extends ecs.Comp {
this.isProcessing = false;
this.processedCount = 0;
}
/**
* 添加伤害事件到队列
*/
addDamageEvent(attrs: any, casterEid: number, s_uuid: number,ext_dmg:number=0,dmg_ratio:number=1,dot_dmg:number=0,proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides }): void {
addDamageEvent(attrs: any, casterEid: number, s_uuid: number, ext_dmg: number = 0, dmg_ratio: number = 1, dot_dmg: number = 0, proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides }, timed_debuff?: TimedDebuffSnapshot): void {
const timestamp = Date.now();
const damageEvent: DamageEvent = {
Attrs: attrs || null,
casterEid: casterEid,
s_uuid: s_uuid,
timestamp: timestamp,
eventId: "",
ext_dmg:ext_dmg,
dmg_ratio:dmg_ratio,
dot_dmg:dot_dmg,
proc_skill:proc_skill,
ext_dmg: ext_dmg,
dmg_ratio: dmg_ratio,
dot_dmg: dot_dmg,
proc_skill: proc_skill,
timed_debuff: timed_debuff,
};
this.damageEvents.push(damageEvent);
// 如果是第一个伤害事件,记录创建时间
if (this.getRemainingCount() === 1) {
this.createTime = timestamp;
}
}
/**
* 获取下一个待处理的伤害事件
*/
@@ -105,21 +116,21 @@ export class DamageQueueComp extends ecs.Comp {
}
return event || null;
}
/**
* 获取队列中剩余的伤害事件数量
*/
getRemainingCount(): number {
return this.damageEvents.length - this.readIndex;
}
/**
* 检查队列是否为空
*/
isEmpty(): boolean {
return this.readIndex >= this.damageEvents.length;
}
/**
* 清空队列
*/
@@ -129,7 +140,7 @@ export class DamageQueueComp extends ecs.Comp {
this.processedCount = 0;
this.isProcessing = false;
}
/**
* 按时间戳排序伤害事件(可选功能)
*/
@@ -140,7 +151,7 @@ export class DamageQueueComp extends ecs.Comp {
}
this.damageEvents.sort((a, b) => a.timestamp - b.timestamp);
}
/**
* 去除重复的伤害事件(基于eventId)
*/
@@ -158,7 +169,7 @@ export class DamageQueueComp extends ecs.Comp {
return true;
});
}
/**
* 获取队列统计信息(用于调试)
*/
@@ -187,18 +198,18 @@ export class DamageQueueHelper {
* 为实体添加伤害事件
* 如果实体没有伤害队列组件,会自动创建
*/
static addDamageToEntity(entity: ecs.Entity, attrs: any, casterEid: number, s_uuid: number,ext_dmg:number=0,dmg_ratio:number=1,dot_dmg:number=0,proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides }): void {
static addDamageToEntity(entity: ecs.Entity, attrs: any, casterEid: number, s_uuid: number, ext_dmg: number = 0, dmg_ratio: number = 1, dot_dmg: number = 0, proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides }, timed_debuff?: TimedDebuffSnapshot): void {
let damageQueue = entity.get(DamageQueueComp);
// 如果实体没有伤害队列组件,创建一个
if (!damageQueue) {
damageQueue = entity.add(DamageQueueComp);
}
// 添加伤害事件到队列
damageQueue.addDamageEvent(attrs, casterEid, s_uuid,ext_dmg,dmg_ratio,dot_dmg,proc_skill);
damageQueue.addDamageEvent(attrs, casterEid, s_uuid, ext_dmg, dmg_ratio, dot_dmg, proc_skill, timed_debuff);
}
/**
* 检查实体是否有待处理的伤害
*/
@@ -206,7 +217,7 @@ export class DamageQueueHelper {
const damageQueue = entity.get(DamageQueueComp);
return damageQueue ? !damageQueue.isEmpty() : false;
}
/**
* 获取实体的伤害队列统计信息
*/

View File

@@ -279,7 +279,8 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
}
}
// 命中附带 buff:仅普攻路径;buff_target 缺省挂被命中者,显式配置可改为挂施法者/全体友方/全体敌方
// 命中附带 buff:仅普攻路径;buff 语义为增益,缺省挂施法者自身(如吸血/攻击后强化),
// 显式配置 buff_target 可改为全体友方等;buff 也可挂自身 debuff 类(如自残出血)
// 施法者 ap/完整属性快照取自伤害事件,供 BuffSystem tick 缩放与暴击/冰冻/击晕判定
if (!isDot && skillConf.timed_buff_id) {
const buffChance = skillConf.buff_chance ?? 100;
@@ -298,6 +299,30 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
}
}
// 命中附带 debuff:仅普攻路径;debuff 语义为对敌减益,目标固定为被命中者(不可覆写)
// 优先取伤害事件透传的合并后快照(heroSet overrides/lv_entries 注入),缺省回退 SkillSet 底座配置
// debuff_value/debuff_duration 覆写 BuffList 默认数值与持续时间
// 注:DoT 事件无 SkillSet 配置(skillConf 可能为 undefined),必须在 !isDot 保护内读取
if (!isDot && skillConf) {
const snapDebuff = damageEvent.timed_debuff;
const debuffId = snapDebuff?.id ?? skillConf.timed_debuff_id;
if (debuffId) {
const debuffChance = snapDebuff?.chance ?? skillConf.debuff_chance ?? 100;
const debuffRand = debuffChance >= 100 ? 0 : Math.random() * 100;
if (debuffChance >= 100 || debuffRand < debuffChance) {
const debuffOverrides: BuffApplyOverrides = { source_attrs: damageEvent.Attrs };
const debuffValue = snapDebuff?.value ?? skillConf.debuff_value;
const debuffDuration = snapDebuff?.duration ?? skillConf.debuff_duration;
if (debuffValue !== undefined) debuffOverrides.value = debuffValue;
if (debuffDuration !== undefined) debuffOverrides.duration = debuffDuration;
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 附带debuff「${BuffList[debuffId]?.name ?? "?"}」 概率:${debuffChance.toFixed(1)}% 随机:${debuffRand.toFixed(1)} -> ✔成功(技能「${skillConf.name}」命中 ${TAttrsComp.hero_name})`);
buffManager.applyBuff(target, debuffId, casterEid, damageEvent.Attrs[Attrs.ap] || 0, debuffOverrides);
} else {
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 附带debuff「${BuffList[debuffId]?.name ?? "?"}」 概率:${debuffChance.toFixed(1)}% 随机:${debuffRand.toFixed(1)} -> ✘失败(技能「${skillConf.name}」命中 ${TAttrsComp.hero_name})`);
}
}
}
// 命中概率触发额外技能(proc_skill):配置由施法技能实体快照自合并后配置(含 heroSet overrides/lv_entries),
// 触发走 SkillTriggerHelper/forceCastTriggerSkill 通道(无视 CD、无施法前摇),与 atking 同一通道。
// 防递归:被触发的技能经 forceCastTriggerSkill 不再产生 proc_skill 伤害事件之外的新事件,且不会累加 atk_count。
@@ -636,20 +661,21 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
/**
* 解析 timed_buff_id 的施加目标实体列表。
* 缺省(undefined / BuffTarget.Hit)返回被命中者;显式配置可切换到施法者自身、全体友方或全体敌方。
* 缺省(undefined / BuffTarget.Self)返回施法者自身(buff 为增益语义,如吸血/攻击后强化);
* 显式配置可切换到被命中者、全体友方或全体敌方。
* @param hitTarget 被命中实体(伤害事件的 target)
* @param casterEid 施法者 eid
* @param targetFac 被命中者阵营(用于推导敌我网格)
* @param buffTarget 技能配置上的目标覆写
*/
private resolveBuffTargets(hitTarget: ecs.Entity, casterEid: number, targetFac: number, buffTarget?: BuffTarget): ecs.Entity[] {
if (buffTarget === undefined || buffTarget === BuffTarget.Hit) {
return [hitTarget];
}
if (buffTarget === BuffTarget.Self) {
if (buffTarget === undefined || buffTarget === BuffTarget.Self) {
const caster = ecs.getEntityByEid(casterEid);
return caster ? [caster] : [];
}
if (buffTarget === BuffTarget.Hit) {
return [hitTarget];
}
// 全体友方/敌方:以施法者阵营为基准(命中者必为敌方,施法方网格即友方)
const casterFac = targetFac === FacSet.HERO ? FacSet.MON : FacSet.HERO;
const grid = buffTarget === BuffTarget.Ally

View File

@@ -2,7 +2,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { Vec3, Prefab, instantiate, tween, Node } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { DTType, RType, SkillConfig, SkillKind, SkillSet, TGroup, SkillOverrides, mergeSkillParams, BuffTarget } from "../common/config/SkillSet";
import { DTType, RType, SkillConfig, SkillKind, SkillSet, TGroup, SkillOverrides, mergeSkillParams } from "../common/config/SkillSet";
import { Skill } from "../skill/Skill";
import { SkillView } from "../skill/SkillView";
import { smc } from "../common/SingletonModuleComp";
@@ -556,8 +556,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (config.buff_type !== undefined) {
// 计时性 buff:配置了 timed_buff_id 时走 buffManager 限时路径,
// 同一技能不应既永久加成又限时加成,故提前 return 跳过下方永久 add_* 逻辑
// buff_target=Enemy 时 timed_buff_id 已在 applyFriendlySkillEffects 入口分流到敌方网格,此处跳过避免误挂友方
if (config.timed_buff_id && config.buff_target !== BuffTarget.Enemy) {
if (config.timed_buff_id) {
// 汇总 buff 覆写参数:value 覆写修饰数值,duration 覆写持续时间
const buffOverrides: { value?: number; duration?: number } = {};
if (config.buff_value !== undefined) buffOverrides.value = config.buff_value;

View File

@@ -1,6 +1,7 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { BoxSet } from "../common/config/GameSet";
import { SkillOverrides } from "../common/config/SkillSet";
import { TimedDebuffSnapshot } from "../hero/DamageQueueComp";
/** 业务层对象 */
//技能数据
@@ -20,6 +21,8 @@ export class SDataCom extends ecs.Comp {
/** 命中后概率触发额外技能(由 Skill.load 从合并后配置快照;DamageEvent 透传给 HeroAtkSystem 判定;
* overrides 字段透传给被触发技能,用于自定义伤害/数值) */
proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides };
/** 命中后对敌 debuff(由 Skill.load 从合并后配置快照;DamageEvent 透传给 HeroAtkSystem 结算) */
timed_debuff?: TimedDebuffSnapshot;
reset() {
this.Attrs=null
this.group=BoxSet.HERO
@@ -32,6 +35,7 @@ export class SDataCom extends ecs.Comp {
this.ext_dmg=0
this.dmg_ratio=1
this.proc_skill=undefined
this.timed_debuff=undefined
}
}

View File

@@ -245,6 +245,10 @@ export class Skill extends ecs.Entity {
sDataCom.max_hit_count = Math.max(1, sHit)
// 快照合并后(基础配置+heroSet overrides/lv_entries)的 proc_skill,命中时由 HeroAtkSystem 判定触发
sDataCom.proc_skill = config.proc_skill;
// 快照合并后的对敌 debuff 配置,命中时由 HeroAtkSystem 结算(heroSet overrides/lv_entries 注入的 debuff 依赖此透传)
sDataCom.timed_debuff = config.timed_debuff_id
? { id: config.timed_debuff_id, value: config.debuff_value, duration: config.debuff_duration, chance: config.debuff_chance }
: undefined;
SView.init();
};

View File

@@ -220,6 +220,7 @@ export class SkillView extends CCComp {
finalRatio,
0,
this.sData.proc_skill,
this.sData.timed_debuff,
);
}
close_collider() {