From be17d18ff3d40c499d5e285f04f927df82409247 Mon Sep 17 00:00:00 2001 From: pan Date: Tue, 8 Sep 2026 16:37:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(skill-system):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=AE=A1=E6=97=B6debuff=E6=9C=BA=E5=88=B6=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8A=80=E8=83=BD=E5=91=BD=E4=B8=AD=E6=95=8C=E6=96=B9?= =?UTF-8?q?=E9=99=84=E5=8A=A0=E5=87=8F=E7=9B=8A=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交完整实现了技能对敌附加debuff的完整流程: 1. 新增TimedDebuffSnapshot数据结构,用于透传debuff配置 2. 在SkillConfig和SkillOverrides中新增debuff相关配置项 3. 实现HeroAtkSystem中debuff的判定与施加逻辑 4. 完善后台配置、前端编辑器的debuff配置支持 5. 调整了buff目标判定逻辑,修复了原有默认目标的语义混淆问题 6. 优化了BuffSystem的调试日志,新增了debuff分类标识 7. 批量更新了现有英雄技能的debuff配置示例 --- assets/script/game/common/config/SkillSet.ts | 18 ++++- assets/script/game/common/config/heroSet.ts | 26 +++---- assets/script/game/hero/BuffManager.ts | 9 ++- assets/script/game/hero/BuffSystem.ts | 5 +- assets/script/game/hero/DamageQueueComp.ts | 79 +++++++++++--------- assets/script/game/hero/HeroAtkSystem.ts | 38 ++++++++-- assets/script/game/hero/SCastSystem.ts | 5 +- assets/script/game/skill/SDataCom.ts | 4 + assets/script/game/skill/Skill.ts | 4 + assets/script/game/skill/SkillView.ts | 1 + put/server/data/heroes.json | 63 ++++++++-------- put/server/data/heroes.json编辑指引.md | 34 +++++---- put/server/index.js | 11 ++- put/web/src/App.vue | 3 + put/web/src/components/OverrideEditor.vue | 26 ++++--- put/web/src/constants.js | 12 ++- 16 files changed, 214 insertions(+), 124 deletions(-) diff --git a/assets/script/game/common/config/SkillSet.ts b/assets/script/game/common/config/SkillSet.ts index f1644bb3..b6eb57a3 100644 --- a/assets/script/game/common/config/SkillSet.ts +++ b/assets/script/game/common/config/SkillSet.ts @@ -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; diff --git a/assets/script/game/common/config/heroSet.ts b/assets/script/game/common/config/heroSet.ts index 8e9583e0..9df26187 100644 --- a/assets/script/game/common/config/heroSet.ts +++ b/assets/script/game/common/config/heroSet.ts @@ -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 = { 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 = { { 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 = { 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 = { 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 = { }, 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 = { 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 = { }, 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 = { }, 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, diff --git a/assets/script/game/hero/BuffManager.ts b/assets/script/game/hero/BuffManager.ts index 2218f558..3776f719 100644 --- a/assets/script/game/hero/BuffManager.ts +++ b/assets/script/game/hero/BuffManager.ts @@ -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 ? "(满层刷新)" : ""}`); } /** diff --git a/assets/script/game/hero/BuffSystem.ts b/assets/script/game/hero/BuffSystem.ts index d63adb25..323be5fc 100644 --- a/assets/script/game/hero/BuffSystem.ts +++ b/assets/script/game/hero/BuffSystem.ts @@ -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); } } diff --git a/assets/script/game/hero/DamageQueueComp.ts b/assets/script/game/hero/DamageQueueComp.ts index 53506c60..d8f662c3 100644 --- a/assets/script/game/hero/DamageQueueComp.ts +++ b/assets/script/game/hero/DamageQueueComp.ts @@ -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; } - + /** * 获取实体的伤害队列统计信息 */ diff --git a/assets/script/game/hero/HeroAtkSystem.ts b/assets/script/game/hero/HeroAtkSystem.ts index c7413f1b..fd106c5b 100644 --- a/assets/script/game/hero/HeroAtkSystem.ts +++ b/assets/script/game/hero/HeroAtkSystem.ts @@ -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 diff --git a/assets/script/game/hero/SCastSystem.ts b/assets/script/game/hero/SCastSystem.ts index 47227f6c..12db6769 100644 --- a/assets/script/game/hero/SCastSystem.ts +++ b/assets/script/game/hero/SCastSystem.ts @@ -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; diff --git a/assets/script/game/skill/SDataCom.ts b/assets/script/game/skill/SDataCom.ts index 6c35e4a5..c3a800d0 100644 --- a/assets/script/game/skill/SDataCom.ts +++ b/assets/script/game/skill/SDataCom.ts @@ -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 } } diff --git a/assets/script/game/skill/Skill.ts b/assets/script/game/skill/Skill.ts index 71f60686..865c2aa4 100644 --- a/assets/script/game/skill/Skill.ts +++ b/assets/script/game/skill/Skill.ts @@ -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(); }; diff --git a/assets/script/game/skill/SkillView.ts b/assets/script/game/skill/SkillView.ts index 609f2f11..003c2401 100644 --- a/assets/script/game/skill/SkillView.ts +++ b/assets/script/game/skill/SkillView.ts @@ -220,6 +220,7 @@ export class SkillView extends CCComp { finalRatio, 0, this.sData.proc_skill, + this.sData.timed_debuff, ); } close_collider() { diff --git a/put/server/data/heroes.json b/put/server/data/heroes.json index fedfedd9..a3307cf7 100644 --- a/put/server/data/heroes.json +++ b/put/server/data/heroes.json @@ -31,10 +31,9 @@ "lv": 2, "s_lv": 2, "overrides": { - "timed_buff_id": 7012, - "buff_value": 30, - "buff_chance": 100, - "buff_duration": 5 + "timed_debuff_id": 7007, + "debuff_value": -10, + "debuff_chance": 100 }, "desc": "普通攻击造成 120% 攻击伤害" } @@ -146,7 +145,7 @@ "cdExpr": null, "overrides": { "ap": 150, - "timed_buff_id": 7002, + "timed_debuff_id": 7002, "icon": "Stat_AttackSpeed_02" }, "desc": "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)", @@ -435,11 +434,11 @@ "lv": 2, "s_lv": 2, "overrides": { - "timed_buff_id": 7007, - "buff_value": 30, - "buff_chance": 30 + "timed_debuff_id": 7007, + "debuff_value": 30, + "debuff_chance": 30 }, - "desc": "普通攻击有{buff_chance}%概率使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" + "desc": "普通攻击有{debuff_chance}%概率使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" } ] }, @@ -458,20 +457,20 @@ "s_lv": 2, "overrides": { "ap": 250, - "timed_buff_id": 7007, - "buff_value": 40 + "timed_debuff_id": 7007, + "debuff_value": 40 }, - "desc": "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" + "desc": "高速斩击{target},造成攻击力{ap}%,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" }, { "lv": 16, "s_lv": 3, "overrides": { "ap": 350, - "timed_buff_id": 7007, - "buff_value": 60 + "timed_debuff_id": 7007, + "debuff_value": 60 }, - "desc": "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" + "desc": "高速斩击{target},造成攻击力{ap}%,附带 50% 概率麻痹,并使目标出血(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" } ] } @@ -1479,11 +1478,11 @@ "lv": 2, "s_lv": 2, "overrides": { - "timed_buff_id": 7005, - "buff_value": 30, - "buff_chance": 30 + "timed_debuff_id": 7005, + "debuff_value": 30, + "debuff_chance": 30 }, - "desc": "普通攻击有{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" + "desc": "普通攻击有{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" } ] }, @@ -1494,12 +1493,12 @@ "cdExpr": null, "overrides": { "ap": 150, - "timed_buff_id": 7005, - "buff_value": 30, - "buff_chance": 50, + "timed_debuff_id": 7005, + "debuff_value": 30, + "debuff_chance": 50, "icon": "4-Apply-Poison" }, - "desc": "毒箭如雨,对{target}造成攻击力{ap}%伤害,{buff_chance}%概率使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)", + "desc": "毒箭如雨,对{target}造成攻击力{ap}%伤害,{debuff_chance}%概率使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)", "lv_entries": [ { "lv": 8, @@ -1534,11 +1533,11 @@ "t_num": 3, "overrides": { "ap": 80, - "timed_buff_id": 7005, - "buff_value": 40, - "buff_chance": 100 + "timed_debuff_id": 7005, + "debuff_value": 40, + "debuff_chance": 100 }, - "desc": "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" + "desc": "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒(每0.5秒受到{debuff_value}点伤害,持续{dur}秒)" } ] }, @@ -1726,10 +1725,10 @@ "lv": 2, "s_lv": 2, "overrides": { - "timed_buff_id": 7008, - "buff_chance": 30 + "timed_debuff_id": 7008, + "debuff_chance": 30 }, - "desc": "普通攻击有{buff_chance}%概率使目标破甲(防御降低,持续{dur}秒)" + "desc": "普通攻击有{debuff_chance}%概率使目标破甲(防御降低,持续{dur}秒)" } ] }, @@ -1740,7 +1739,7 @@ "cdExpr": null, "overrides": { "ap": 150, - "timed_buff_id": 7008 + "timed_debuff_id": 7008 }, "desc": "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)", "lv_entries": [ @@ -1775,7 +1774,7 @@ "t_num": 1, "overrides": { "ap": 150, - "timed_buff_id": 7008 + "timed_debuff_id": 7008 }, "desc": "战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒)" } diff --git a/put/server/data/heroes.json编辑指引.md b/put/server/data/heroes.json编辑指引.md index f78f38f2..52d74111 100644 --- a/put/server/data/heroes.json编辑指引.md +++ b/put/server/data/heroes.json编辑指引.md @@ -87,7 +87,7 @@ ## 6. overrides 白名单与特殊存储格式 可用键(对照 `SkillSet.ts` 的 `SkillOverrides` 接口;编辑器 UI 白名单 `OVERRIDE_KEYS` 是其子集,手动改 JSON 可用全集): -`ap, hit_count, hitcd, crt, frz, stun, para, bck, bck_chance, TGroup, buff_type, timed_buff_id, buff_value, buff_duration, buff_chance, buff_target, call_hero, summon_count, is_accel, num, revive_hp, revive_count, proc_skill, icon` +`ap, hit_count, hitcd, crt, frz, stun, para, bck, bck_chance, TGroup, buff_type, timed_buff_id, buff_value, buff_duration, buff_chance, buff_target, timed_debuff_id, debuff_value, debuff_duration, debuff_chance, call_hero, summon_count, is_accel, num, revive_hp, revive_count, proc_skill, icon` 特殊存储格式(导出时由 server 还原为 TS 枚举表达式): - `TGroup`:JSON 存字符串(如 `"Team"`),导出还原 `TGroup.Team`;`null` 表示不覆写; @@ -96,27 +96,35 @@ - `icon`:字符串图标帧名(uicons 图集),非数值; - 其余一律直存数值。 -引用校验:`timed_buff_id` 必须 ∈ `BuffList`;`proc_skill.s_uuid` / 触发组 key / 召唤类引用必须 ∈ `SkillSet`。 +引用校验:`timed_buff_id` / `timed_debuff_id` 必须 ∈ `BuffList`;`proc_skill.s_uuid` / 触发组 key / 召唤类引用必须 ∈ `SkillSet`。 -### 6.1 timed_buff_id(计时 buff 注入) +### 6.1 timed_buff_id / timed_debuff_id(计时 buff / debuff 注入) -**编辑器 UI**:overrides 添加 `timed_buff_id 计时buff` 行后,值栏为 BuffList 下拉(`id 名称` + 右侧 buff 效果摘要,数据来自 `/api/meta` 的 `BuffList`);meta 未加载时可手输数字 id(allow-create,保存时自动归一化为数字)。选定 buff id 后编辑器自动补齐 `buff_value` / `buff_chance` / `buff_duration` 三行(已存在不重复),数值预填 BuffList 默认值。 +**语义划分(新规则)**: +- `timed_buff_id`:**增益语义**。释放/命中时给友方或自身挂 buff;伤害技能缺省挂**施法者自身**(如吸血、攻击后强化),辅助技能缺省挂**友方目标**。可用 `buff_target` 覆写目标。 +- `timed_debuff_id`:**减益语义**。命中敌人后给**被命中者**挂 debuff(目标固定,不可覆写);仅伤害路径(HeroAtkSystem)结算,DoT 不触发。 + +**编辑器 UI**:overrides 添加 `timed_buff_id 计时buff` 或 `timed_debuff_id 计时debuff` 行后,值栏为 BuffList 下拉(`id 名称` + 右侧 buff 效果摘要,数据来自 `/api/meta` 的 `BuffList`);meta 未加载时可手输数字 id(allow-create,保存时自动归一化为数字)。选定 id 后编辑器自动补齐配套三行(buff 补 `buff_value`/`buff_chance`/`buff_duration`,debuff 补 `debuff_value`/`debuff_chance`/`debuff_duration`,已存在不重复),数值预填 BuffList 默认值。 **配套键**(同一 overrides 内按需添加): | 键 | 说明 | 缺省 | |---|---|---| -| `buff_value` | 覆写修饰数值(modifiers.value / tick.damage_or_heal,单位同 BuffList) | 回退 BuffList 默认 | -| `buff_duration` | 覆写持续时间(秒) | 回退 BuffList.duration | -| `buff_chance` | 触发概率 %(命中时掷骰) | 100(必触发) | -| `buff_target` | 施加目标:0 被命中者(Hit) / 1 施法者(Self) / 2 全体友方(Ally) / 3 全体敌方(Enemy) | 按主效果推导:伤害→被命中者,辅助→友方 | +| `buff_value` | 覆写 buff 修饰数值(modifiers.value / tick.damage_or_heal,单位同 BuffList) | 回退 BuffList 默认 | +| `buff_duration` | 覆写 buff 持续时间(秒) | 回退 BuffList.duration | +| `buff_chance` | buff 触发概率 %(命中时掷骰) | 100(必触发) | +| `buff_target` | buff 施加目标:0 被命中者(Hit) / 1 施法者(Self) / 2 全体友方(Ally) / 3 全体敌方(Enemy) | 按主效果推导:伤害→施法者自身,辅助→友方目标 | +| `debuff_value` | 覆写 debuff 修饰数值 | 回退 BuffList 默认 | +| `debuff_duration` | 覆写 debuff 持续时间(秒) | 回退 BuffList.duration | +| `debuff_chance` | debuff 触发概率 %(命中时掷骰) | 100(必触发) | **生效路径(重要)**: -- ✅ 友方/辅助技能(TGroup 为 Self/Team/Ally,或 6300/6500 段辅助底座):走 SCastSystem merged config,overrides 注入完整生效;`buff_target: 3`(Enemy)可把 buff 分流挂到敌方网格,实现对敌 debuff; -- ✅ 对敌技能的命中附带 debuff:以 SkillSet **底座配置**为准(HeroAtkSystem 按 `SkillSet[s_uuid]` 读取 `timed_buff_id`/`buff_chance` 等),对敌 debuff 写入底座即可起效; +- ✅ 友方/辅助技能(TGroup 为 Self/Team/Ally,或 6300/6500 段辅助底座):走 SCastSystem merged config,`timed_buff_id` 注入完整生效;`buff_target: 3`(Enemy)可把 buff 分流挂到敌方网格(如群体减甲); +- ✅ 对敌伤害技能的命中附带 debuff:用 `timed_debuff_id`(可写在 SkillSet 底座或 heroSet overrides),HeroAtkSystem 命中结算时挂被命中者; +- ✅ 对敌伤害技能的命中附带增益(吸血/自强化):用 `timed_buff_id`,缺省挂施法者自身; - 同一 overrides 不要既配 `timed_buff_id` 又依赖永久 `buff_type` 加成:timed 路径生效后永久分支被跳过。 -**语义陷阱**:`buff_target` 缺省为被命中者——若想"攻击回血/吸血",必须显式配 `"buff_target": 1`,否则 buff 挂到被打的敌人身上(如 7010 回血会奶敌人)。 +**语义陷阱**:`timed_buff_id` 在伤害技能上缺省挂**施法者自身**——若想"命中给敌人挂减益",必须用 `timed_debuff_id`,否则会把减益挂到自己身上。 ### 6.2 proc_skill(命中概率触发主动技能) @@ -166,5 +174,5 @@ - 普攻用数值 cd 而不给 `cdExpr` → 丢失攻速档位语义(编辑器攻速下拉失效); - 档位 overrides 全量复制基础档 → 浅合并下冗余且后续改基础值不生效; - desc 与 overrides 数值脱节 → desc 模板只写 `{ap}` 等占位符,不要硬编码数字; -- 对敌技能 debuff 写在 overrides → 命中附带判定按 SkillSet 底座读取,应写入底座配置(详见 §6.1); -- 想做吸血但漏配 `buff_target:1` → 缺省挂被命中者,回血 buff 会奶敌人。 +- 对敌技能 debuff 误用 `timed_buff_id` → buff 缺省挂施法者自身,会把减益挂到自己身上;应改用 `timed_debuff_id`(详见 §6.1); +- 想做吸血但把 buff 写到 `timed_debuff_id` → debuff 目标固定为被命中者,会奶敌人;吸血/自强化请用 `timed_buff_id`(缺省即挂自身)。 diff --git a/put/server/index.js b/put/server/index.js index 347994b0..51cbdbe9 100644 --- a/put/server/index.js +++ b/put/server/index.js @@ -257,10 +257,17 @@ function overrideShort(ov, { includeAp = true } = {}) { const buffName = cache?.BuffList?.[ov.timed_buff_id]?.name || '' const val = ov.buff_value != null ? `${ov.buff_value}` : '' const chance = ov.buff_chance != null ? `buff_chance${ov.buff_chance}` : '' - // buff_target 非默认(被命中者)时标注目标,便于识别"挂自己/挂敌方"等跨阵营 buff - const tgtLabel = ov.buff_target != null && ov.buff_target !== 0 ? `→${BUFF_TARGET_NAMES[ov.buff_target]}` : '' + // buff_target 非默认(施法者自身)时标注目标,便于识别"挂敌方/挂队友"等跨阵营 buff + const tgtLabel = ov.buff_target != null && ov.buff_target !== 1 ? `→${BUFF_TARGET_NAMES[ov.buff_target]}` : '' parts.push(`附加${val}${buffName}${ov.timed_buff_id}${chance}${tgtLabel}`) } + // timed_debuff_id:命中被攻击者后施加(目标固定,无需标注) + if (ov.timed_debuff_id != null) { + const debuffName = cache?.BuffList?.[ov.timed_debuff_id]?.name || '' + const val = ov.debuff_value != null ? `${ov.debuff_value}` : '' + const chance = ov.debuff_chance != null ? `debuff_chance${ov.debuff_chance}` : '' + parts.push(`命中附加${val}${debuffName}${ov.timed_debuff_id}${chance}`) + } // proc_skill:命中后概率触发额外技能,标注被触发技能名/概率/覆写 ap(若有) if (ov.proc_skill?.s_uuid != null) { const procName = cache?.SkillSet?.[ov.proc_skill.s_uuid]?.name || '' diff --git a/put/web/src/App.vue b/put/web/src/App.vue index 6cccb03a..705bdb66 100644 --- a/put/web/src/App.vue +++ b/put/web/src/App.vue @@ -280,6 +280,9 @@ const OV_LABELS = { timed_buff_id: 'buff', buff_value: 'val', buff_chance: 'chance', + timed_debuff_id: 'debuff', + debuff_value: 'dval', + debuff_chance: 'dchance', summon_count: '数', thorns_dmg: 'thorns', thorns_rate: 'thorns', diff --git a/put/web/src/components/OverrideEditor.vue b/put/web/src/components/OverrideEditor.vue index 2be30617..9e267748 100644 --- a/put/web/src/components/OverrideEditor.vue +++ b/put/web/src/components/OverrideEditor.vue @@ -55,7 +55,7 @@ - + + 添加覆盖 @@ -197,22 +197,24 @@ function onBuffValueChange(item) { if (typeof item.value === 'string' && item.value.trim() !== '' && !isNaN(Number(item.value))) { item.value = Number(item.value) } - ensureBuffCompanionRows(item.value) + ensureBuffCompanionRows(item.value, item.key === 'timed_debuff_id') } -// 挂载 timed_buff_id 后自动补齐配套三键(已存在的不重复添加): +// 挂载 timed_buff_id / timed_debuff_id 后自动补齐配套三键(已存在的不重复添加): // 数值预填 BuffList 默认值(modifiers[0].value / tick.damage_or_heal / duration),避免导出 0 值覆盖 buff 语义 -function ensureBuffCompanionRows(buffId) { +// isDebuff=true 时补 debuff_* 键,否则补 buff_* 键 +function ensureBuffCompanionRows(buffId, isDebuff = false) { const buff = typeof buffId === 'number' ? props.buffOptions.find((b) => b.id === buffId) : null + const prefix = isDebuff ? 'debuff' : 'buff' const has = (key) => rows.value.some((r) => r.key === key) - if (!has('buff_value')) { - rows.value.push({ key: 'buff_value', value: buff?.modifiers?.[0]?.value ?? buff?.tick?.damage_or_heal ?? 0 }) + if (!has(`${prefix}_value`)) { + rows.value.push({ key: `${prefix}_value`, value: buff?.modifiers?.[0]?.value ?? buff?.tick?.damage_or_heal ?? 0 }) } - if (!has('buff_chance')) { - rows.value.push({ key: 'buff_chance', value: 100 }) + if (!has(`${prefix}_chance`)) { + rows.value.push({ key: `${prefix}_chance`, value: 100 }) } - if (!has('buff_duration')) { - rows.value.push({ key: 'buff_duration', value: buff?.duration ?? 5 }) + if (!has(`${prefix}_duration`)) { + rows.value.push({ key: `${prefix}_duration`, value: buff?.duration ?? 5 }) } } diff --git a/put/web/src/constants.js b/put/web/src/constants.js index c2e52f59..e1a04d52 100644 --- a/put/web/src/constants.js +++ b/put/web/src/constants.js @@ -109,19 +109,25 @@ export const OVERRIDE_KEYS = [ { key: 'bck', label: 'bck 击退' }, { key: 'bck_chance', label: 'bck_chance 击退概率' }, // buff 型覆写:值为 BuffList 配置 id,前端用 meta.BuffList 渲染下拉(支持 allow-create 手输兜底) + // buff 语义为增益,缺省挂施法者自身;debuff 语义为对敌减益,缺省挂被命中者(目标不可覆写) { key: 'timed_buff_id', label: 'timed_buff_id 计时buff', type: 'buff' }, { key: 'buff_value', label: 'buff_value 修饰数值' }, { key: 'buff_duration', label: 'buff_duration 持续时间' }, { key: 'buff_chance', label: 'buff_chance 附加概率' }, { key: 'thorns_dmg', label: 'thorns_dmg 反伤固定值' }, { key: 'thorns_rate', label: 'thorns_rate 反伤比例(%)' }, - // 枚举型覆写:计时 buff 施加目标(缺省跟随技能主效果目标,见 SkillSet.BuffTarget) + // 枚举型覆写:计时 buff 施加目标(缺省按技能主效果推导:伤害→施法者自身,辅助→友方目标) { key: 'buff_target', label: 'buff_target buff施加目标', type: 'enum', options: [ - { value: 0, label: '0 被命中者(Hit/默认)' }, - { value: 1, label: '1 施法者自身(Self)' }, + { value: 0, label: '0 被命中者(Hit)' }, + { value: 1, label: '1 施法者自身(Self/默认)' }, { value: 2, label: '2 全体友方(Ally)' }, { value: 3, label: '3 全体敌方(Enemy)' }, ]}, + // debuff 型覆写:值为 BuffList 配置 id,命中被攻击者后施加;目标固定为被命中者,不可覆写 + { key: 'timed_debuff_id', label: 'timed_debuff_id 计时debuff', type: 'buff' }, + { key: 'debuff_value', label: 'debuff_value 修饰数值' }, + { key: 'debuff_duration', label: 'debuff_duration 持续时间' }, + { key: 'debuff_chance', label: 'debuff_chance 附加概率' }, // 枚举型覆写:技能目标群体(JSON 存字符串,导出还原 TGroup.X;proc_skill.overrides 可借此改变被触发技能的释放对象) // 注:不提供 All —— SCastSystem 未对 TGroup.All 做友/敌分流(会落到敌方分支),避免误配 { key: 'TGroup', label: 'TGroup 目标群体', type: 'enum', options: [