4 Commits

Author SHA1 Message Date
pan
fe0b121cb5 refactor(hero skill/editor): 调整技能配置并新增配置提示
1. 更新英雄普攻技能的overrides配置与描述文本
2. 为属性编辑器新增debuff和buff的语义提示弹窗
3. 优化编辑器提示框的样式适配
2026-09-08 16:57:54 +08:00
pan
be17d18ff3 feat(skill-system): 新增计时debuff机制,支持技能命中敌方附加减益效果
本次提交完整实现了技能对敌附加debuff的完整流程:
1.  新增TimedDebuffSnapshot数据结构,用于透传debuff配置
2.  在SkillConfig和SkillOverrides中新增debuff相关配置项
3.  实现HeroAtkSystem中debuff的判定与施加逻辑
4.  完善后台配置、前端编辑器的debuff配置支持
5.  调整了buff目标判定逻辑,修复了原有默认目标的语义混淆问题
6.  优化了BuffSystem的调试日志,新增了debuff分类标识
7.  批量更新了现有英雄技能的debuff配置示例
2026-09-08 16:37:13 +08:00
pan
f5908a9adf feat(editor): 完善技能覆写编辑器功能,优化配置体验
1. 为OverrideEditor新增baseSkillUuid参数,支持对敌技能debuff配置校验
2. 优化proc_skill和timed_buff_id的编辑体验,自动补全关联配置项
3. 更新常量注释与英雄技能配置,修正overrides字段配置
4. 新增对敌技能buff配置警示提示,引导正确配置方式
2026-09-08 10:38:18 +08:00
pan
4d7e54f833 refactor(debug-log): 优化全模块调试日志格式与默认开关
1.  将BuffManager的静态DEBUG开关改为运行时可配置的debug属性,默认开启
2.  统一所有模块的调试日志输出格式,增加技能/ buff名称、UUID、目标信息和判定详情
3.  为HeroAtkSystem的概率判定添加统一日志,修复击退判定逻辑并补充日志
4.  开启SkillView和HeroAtkSystem的默认调试模式
2026-09-08 10:03:40 +08:00
18 changed files with 434 additions and 132 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"
@@ -544,9 +544,9 @@ export const HeroInfo: Record<number, heroInfo> = {
5001: {
uuid: 5001, name: "叠甲骑士", path: "hk1", fac: FacSet.HERO, rarity: 1, lv: 1, type: HType.Melee, grow_type: HGrowType.Tank, role: HRole.Tank, hp: HERO_ROLE_BASE[HRole.Tank].hp + 20, ap: HERO_ROLE_BASE[HRole.Tank].ap, def: HERO_ROLE_BASE[HRole.Tank].def + 10,
skills: {
6004: { uuid: 6004, name: "强化普攻", lv: 1, cd: AtkSpeedSet[AtkSpeedLv.Slow].cd, ccd: 0, overrides: {"ap":100,"timed_buff_id":7010,"buff_chance":50}, desc: "挥剑对{target}造成攻击力{ap}%伤害",
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: {"ap":120}, desc: "普通攻击造成 120% 攻击伤害" },
{ lv: 2, s_lv: SkillLv.Lv2, overrides: {"ap":120}, 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,11 +11,9 @@
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";
/** Buff 模块调试日志开关 */
const DEBUG = false;
/** applyBuff 的覆盖参数(预留按需覆写持续时间 / 修饰数值) */
export interface BuffApplyOverrides {
duration?: number;
@@ -26,6 +24,9 @@ export interface BuffApplyOverrides {
}
class BuffManagerImpl {
/** 调试日志开关:运行时可通过 buffManager.debug = false 关闭(测试期默认开启) */
public debug: boolean = true;
/**
* 对目标施加一个 buff 实例。
*
@@ -46,10 +47,12 @@ class BuffManagerImpl {
mLogger.warn(true, "BuffManager", `applyBuff: buffId=${buffId} 配置不存在,跳过`);
return;
}
mLogger.log(DEBUG, "BuffManager", `applyBuff: buff=${cfg.name}(${buffId}) target_eid=${target.eid} duration=${overrides?.duration ?? cfg.duration} value_override=${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

@@ -47,7 +47,7 @@ interface FinalData {
@ecs.register('HeroAtkSystem')
export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
private debugMode: boolean = false; // 是否启用调试模式
private debugMode: boolean = true; // 是否启用调试模式
/**
* 过滤器:处理拥有伤害队列的实体
@@ -145,7 +145,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
// 暴击判定
// 使用施法者的暴击率属性(damageEvent.Attrs 快照)- 被攻击者的暴击抗性 + 目标出血层数附加的被暴击率
const criticalChance = (damageEvent.Attrs[Attrs.critical] || 0) - TAttrsComp.getFinalCriticalRes() + this.getBleedCritBonus(target);
const isCrit = this.checkChance(criticalChance);
const isCrit = this.checkChance(criticalChance, `暴击->${TAttrsComp.hero_name}`);
// 计算基础伤害:DoT 直接用固定 tick 值,普攻走 dmgCount 公式
let damage = isDot ? (damageEvent.dot_dmg ?? 0) : this.dmgCount(damageEvent, TAttrsComp);
@@ -214,15 +214,15 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
// 冰冻判定
const freezeChance = (damageEvent.Attrs[Attrs.freeze_chance] || 0) - (TAttrsComp.freeze_res || 0);
const isFrost = !TAttrsComp.isFrost() && this.checkChance(freezeChance);
const isFrost = !TAttrsComp.isFrost() && this.checkChance(freezeChance, `冰冻->${TAttrsComp.hero_name}`);
// 击晕判定
const stunChance = (damageEvent.Attrs[Attrs.stun_chance] || 0) - TAttrsComp.getFinalStunRes();
const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance);
const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance, `击晕->${TAttrsComp.hero_name}`);
// 麻痹判定(无麻痹抗性,仅按概率)
const paralyzeChance = damageEvent.Attrs[Attrs.paralyze_chance] || 0;
const isParalyze = !TAttrsComp.isParalyze() && this.checkChance(paralyzeChance);
const isParalyze = !TAttrsComp.isParalyze() && this.checkChance(paralyzeChance, `麻痹->${TAttrsComp.hero_name}`);
// ✅ 触发视图层表现(伤害数字、受击动画、冰冻、击晕、击退)
if (targetView) {
@@ -234,7 +234,11 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
// 击退概率判定:概率来自技能配置 bck_chance(heroSet overrides 可覆写),未配置视为 0 不击退。
// 不复用 checkChance:其 70% 上限封顶会把 100% 击退(近战普攻 6004)削成 70%
const knockbackChance = damageEvent.Attrs[Attrs.knockback_skill_chance] || 0;
const isKnockback = knockbackChance >= 100 || (knockbackChance > 0 && Math.random() * 100 < knockbackChance);
const knockbackRand = knockbackChance >= 100 ? 0 : Math.random() * 100;
const isKnockback = knockbackChance >= 100 || (knockbackChance > 0 && knockbackRand < knockbackChance);
if (knockbackChance > 0) {
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 击退->${TAttrsComp.hero_name} 概率:${knockbackChance.toFixed(1)}% 随机:${knockbackRand.toFixed(1)} -> ${isKnockback ? "✔成功" : "✘失败"}`);
}
// 击退距离增量 = 施法者击退强化(原击退概率)- 被攻击者击退抗性 + 击退距离强化(仅命中击退时生效)
const knockbackBonus = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.getFinalKnockbackRes() || 0);
@@ -275,17 +279,47 @@ 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;
if (buffChance >= 100 || Math.random() * 100 < buffChance) {
const buffRand = buffChance >= 100 ? 0 : Math.random() * 100;
if (buffChance >= 100 || buffRand < buffChance) {
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;
for (const buffTarget of this.resolveBuffTargets(target, casterEid, TAttrsComp.fac, skillConf.buff_target)) {
const buffTargets = this.resolveBuffTargets(target, casterEid, TAttrsComp.fac, skillConf.buff_target);
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 附带buff「${BuffList[skillConf.timed_buff_id]?.name ?? "?"}」 概率:${buffChance.toFixed(1)}% 随机:${buffRand.toFixed(1)} -> ✔成功(技能「${skillConf.name}」命中 ${TAttrsComp.hero_name},${buffTargets.length} 个目标)`);
for (const buffTarget of buffTargets) {
buffManager.applyBuff(buffTarget, skillConf.timed_buff_id, casterEid, damageEvent.Attrs[Attrs.ap] || 0, buffOverrides);
}
} else {
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 附带buff「${BuffList[skillConf.timed_buff_id]?.name ?? "?"}」 概率:${buffChance.toFixed(1)}% 随机:${buffRand.toFixed(1)} -> ✘失败(技能「${skillConf.name}」命中 ${TAttrsComp.hero_name})`);
}
}
// 命中附带 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})`);
}
}
}
@@ -295,16 +329,20 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
if (!isDot && damageEvent.proc_skill) {
const proc = damageEvent.proc_skill;
const procChance = proc.chance ?? 100;
if (procChance >= 100 || Math.random() * 100 < procChance) {
const procRand = procChance >= 100 ? 0 : Math.random() * 100;
if (procChance >= 100 || procRand < procChance) {
const caster = ecs.getEntityByEid(casterEid);
if (caster) {
const casterAttrs = caster.get(HeroAttrsComp);
const casterView = caster.get(HeroViewComp);
if (casterAttrs && casterView && !casterAttrs.is_dead && !casterAttrs.is_reviving) {
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 额外技能「${SkillSet[proc.s_uuid]?.name ?? "?"}」 概率:${procChance.toFixed(1)}% 随机:${procRand.toFixed(1)} -> ✔成功(技能「${skillConf.name}」命中 ${TAttrsComp.hero_name},由 ${casterAttrs.hero_name} 释放)`);
// proc_skill.overrides 透传给被触发技能(如自定义 ap 伤害),与被触发技能自身 overrides 叠加(本次传入优先)
SkillTriggerHelper.dispatchSinglePublic(proc.s_uuid, casterAttrs, casterView, SkillTriggerType.Atking, proc.overrides);
}
}
} else {
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] 额外技能「${SkillSet[proc.s_uuid]?.name ?? "?"}」 概率:${procChance.toFixed(1)}% 随机:${procRand.toFixed(1)} -> ✘失败(技能「${skillConf.name}」命中 ${TAttrsComp.hero_name})`);
}
}
@@ -507,11 +545,15 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
*
* @important 概率为0或负数时直接返回false,避免不必要的随机数计算
*/
private checkChance(rate: number): boolean {
private checkChance(rate: number, label?: string): boolean {
if (rate <= 0) return false;
if (rate >= 70) rate = 70
const capped = Math.min(rate, 70); // 70% 概率封顶
const r = Math.random() * 100;
return r < rate;
const ok = r < capped;
if (label !== undefined) {
mLogger.log(this.debugMode, 'HeroAtkSystem', `[概率判定] ${label} 概率:${rate.toFixed(1)}%(封顶${capped}%) 随机:${r.toFixed(1)} -> ${ok ? "✔成功" : "✘失败"}`);
}
return ok;
}
@@ -546,7 +588,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
private checkDodge(damageEvent: DamageEvent, TAttrsComp: HeroAttrsComp): boolean {
const dodgeChance = TAttrsComp.getFinalDodge() - (damageEvent.Attrs[Attrs.accuracy] || 0);
if (dodgeChance <= 0) return false;
return this.checkChance(dodgeChance);
return this.checkChance(dodgeChance, `闪避:${TAttrsComp.hero_name}`);
}
/**
@@ -619,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";
@@ -531,7 +531,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (!target.ent) return;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) return;
mLogger.log(this.debugMode, "SCastSystem", `applyActualFriendlyEffect: skill=${config.uuid} kind=${kind} buff_type=${config.buff_type} timed_buff_id=${config.timed_buff_id} buff_value=${config.buff_value} target=${model.hero_name}`);
mLogger.log(this.debugMode, "SCastSystem", `[友方效果] 技能「${config.name}」(s_uuid:${config.uuid}) -> ${model.hero_name} kind:${kind} buff_type:${config.buff_type ?? "-"} timed_buff_id:${config.timed_buff_id ?? "-"} buff_value:${config.buff_value ?? "-"}`);
if (config.endAnm && config.endAnm !== "") {
target.playEnd(config.endAnm);
@@ -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

@@ -58,7 +58,7 @@ export class SkillView extends CCComp {
@property({ type: CCFloat, slide: true, range: [0, 500, 5], tooltip: "抛射物高度增量(叠加到贝塞尔弹道中点高度)" })
height_offset: number = 0;
private debugMode: boolean = false;
private debugMode: boolean = true;
anim: Animation = null;
group: number = 0;
@@ -135,10 +135,9 @@ export class SkillView extends CCComp {
this.sData.max_hit_count++;
// 概率衰减(减去固定的 PUNCTURE_DOWN 值,比如 50)
this.sData.Attrs[Attrs.puncture_chance] = Math.max(0, punctureChance - FightSet.PUNCTURE_DOWN);
if (this.debugMode) {
mLogger.log(this.debugMode, 'SkillView', `[SkillView] 触发穿透!剩余概率: ${this.sData.Attrs[Attrs.puncture_chance]}%`);
}
mLogger.log(this.debugMode, 'SkillView', `[概率判定] 穿透 概率:${punctureChance.toFixed(1)}% 随机:${rand.toFixed(1)} -> ✔成功(衰减后剩余:${this.sData.Attrs[Attrs.puncture_chance]}%)`);
} else {
mLogger.log(this.debugMode, 'SkillView', `[概率判定] 穿透 概率:${punctureChance.toFixed(1)}% 随机:${rand.toFixed(1)} -> ✘失败,投射物销毁`);
// 未触发穿透,正常销毁
this.handle_collision_limit();
}
@@ -221,6 +220,7 @@ export class SkillView extends CCComp {
finalRatio,
0,
this.sData.proc_skill,
this.sData.timed_debuff,
);
}
close_collider() {

View File

@@ -23,9 +23,7 @@
"cd": 1.2,
"cdExpr": "AtkSpeedSet[AtkSpeedLv.Slow].cd",
"overrides": {
"ap": 100,
"timed_buff_id": 7010,
"buff_chance": 50
"ap": 100
},
"desc": "挥剑对{target}造成攻击力{ap}%伤害",
"lv_entries": [
@@ -35,7 +33,7 @@
"overrides": {
"ap": 120
},
"desc": "普通攻击造成 120% 攻击伤害"
"desc": "普通攻击 造成 120% 伤害"
}
]
},
@@ -145,7 +143,7 @@
"cdExpr": null,
"overrides": {
"ap": 150,
"timed_buff_id": 7002,
"timed_debuff_id": 7002,
"icon": "Stat_AttackSpeed_02"
},
"desc": "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)",
@@ -434,11 +432,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}秒)"
}
]
},
@@ -457,20 +455,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}秒)"
}
]
}
@@ -1478,11 +1476,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}秒)"
}
]
},
@@ -1493,12 +1491,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,
@@ -1533,11 +1531,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}秒)"
}
]
},
@@ -1725,10 +1723,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}秒)"
}
]
},
@@ -1739,7 +1737,7 @@
"cdExpr": null,
"overrides": {
"ap": 150,
"timed_buff_id": 7008
"timed_debuff_id": 7008
},
"desc": "释放碎甲能量波,对{target}造成攻击力{ap}%伤害,并使目标破甲(防御降低,持续{dur}秒)",
"lv_entries": [
@@ -1774,7 +1772,7 @@
"t_num": 1,
"overrides": {
"ap": 150,
"timed_buff_id": 7008
"timed_debuff_id": 7008
},
"desc": "战斗开始时掷出蚀甲酸瓶,对{target}造成攻击力{ap}%伤害,并必定使其破甲(防御降低,持续{dur}秒)"
}

View File

@@ -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,7 +96,63 @@
- `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 / timed_debuff_id(计时 buff / debuff 注入)
**语义划分(新规则)**:
- `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` | 覆写 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,`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 路径生效后永久分支被跳过。
**语义陷阱**:`timed_buff_id` 在伤害技能上缺省挂**施法者自身**——若想"命中给敌人挂减益",必须用 `timed_debuff_id`,否则会把减益挂到自己身上。
### 6.2 proc_skill(命中概率触发主动技能)
**定位**:命中后概率触发一个**主动技能**(大招/触发技底座,如 6100+/6200+ 段伤害技能),以施法者身份完整释放(含伤害/弹道/特效)。它是技能触发器,不是挂 buff 的方式——debuff 注入请用 `timed_buff_id`(见 §6.1)。
**编辑器 UI**:overrides 添加 `proc_skill 命中概率触发主动技能` 行后展开结构化编辑:
1. `被触发技能 uuid`:下拉列出全部 SkillSet(`uuid 名称`);
2. `概率%`:0~100 数字输入,默认 100;
3. `被触发技能覆写`:内嵌 OverrideEditor(同套白名单),可递归覆写被触发技能的 `ap`/`TGroup`/`hit_count` 等。
选中被触发技能后自动在其内嵌 overrides 补 `TGroup` 行(取底座值,已存在不覆盖),便于直改释放对象并联动对敌断链警示。
选中 `s_uuid` 前该行不输出(防止半成品配置导出),编辑中状态会被保留。
**JSON 格式**:
```json
"proc_skill": { "s_uuid": 6201, "chance": 20, "overrides": { "ap": 150 } }
```
**运行时行为**:
- 触发链路:技能命中 → 掷 `chance` 骰(≥100 直接成功不掷骰;无 70% 封顶)→ 以施法者身份走 `forceCastTriggerSkill`(无视 CD、无施法前摇,触发类型记为 atking);
- `overrides` 与被触发技能自身 heroSet overrides 叠加,本次传入优先;
- DoT 伤害不触发 proc;被触发的技能不再累加普攻计数(防递归);
- 内嵌 `TGroup` 可改变被触发技能的释放对象(如触发一个友方增益)。
**选型指引**(对敌附带效果):
- 挂 debuff(冰冻/中毒/易伤等)→ `timed_buff_id`:写入 SkillSet 底座,或用友方技能 + `buff_target: 3` 分流挂敌;
- 命中后追发一个主动技能(陨石/火球等完整释放)→ `proc_skill`。
## 7. 新增英雄操作清单
@@ -117,4 +173,6 @@
- 触发技/光环解锁写 lv6、大招档位写 lv9 → 现行惯例 lv4 / lv8+lv16;
- 普攻用数值 cd 而不给 `cdExpr` → 丢失攻速档位语义(编辑器攻速下拉失效);
- 档位 overrides 全量复制基础档 → 浅合并下冗余且后续改基础值不生效;
- desc 与 overrides 数值脱节 → desc 模板只写 `{ap}` 等占位符,不要硬编码数字。
- desc 与 overrides 数值脱节 → desc 模板只写 `{ap}` 等占位符,不要硬编码数字;
- 对敌技能 debuff 误用 `timed_buff_id` → buff 缺省挂施法者自身,会把减益挂到自己身上;应改用 `timed_debuff_id`(详见 §6.1);
- 想做吸血但把 buff 写到 `timed_debuff_id` → debuff 目标固定为被命中者,会奶敌人;吸血/自强化请用 `timed_buff_id`(缺省即挂自身)。

View File

@@ -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 || ''

View File

@@ -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',

View File

@@ -36,6 +36,7 @@
size="small"
style="width: 220px"
filterable
@change="onProcSkillChange(item)"
>
<el-option v-for="s in skillOptions" :key="s.uuid" :label="`${s.uuid} ${s.name}`" :value="s.uuid" />
</el-select>
@@ -50,11 +51,11 @@
/>
<div class="proc-skill-overrides">
<span class="proc-skill-label">被触发技能覆写:</span>
<OverrideEditor v-model="item.value.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="item.value.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="item.value.s_uuid" />
</div>
</div>
</template>
<!-- buff 型覆写(timed_buff_id):BuffList 下拉,副文本展示 buff info;allow-create 兼容 meta 未加载时手输 id -->
<!-- buff/debuff 型覆写(timed_buff_id/timed_debuff_id):BuffList 下拉,副文本展示 buff info;allow-create 兼容 meta 未加载时手输 id -->
<el-select
v-else-if="isBuffKey(item.key)"
v-model="item.value"
@@ -74,12 +75,44 @@
<el-input-number v-else v-model="item.value" size="small" :controls="false" style="width: 120px" />
<el-button type="danger" size="small" text @click="remove(idx)">删除</el-button>
</div>
<!-- 选中 debuff 后的取值语义提示:依 BuffList 配置(tick/modifier/易伤/控制)说明 debuff_value 的正负与生效方式 -->
<el-alert
v-if="debuffHint"
type="warning"
:closable="false"
show-icon
class="hint-alert"
style="margin-bottom: 6px"
:title="debuffHint.title"
:description="debuffHint.desc"
/>
<!-- 选中 buff(增益)后的取值语义提示:缺省挂施法者自身;tick 类正=回血/负=自伤,modifier 类正=增强 -->
<el-alert
v-if="buffHint"
type="success"
:closable="false"
show-icon
class="hint-alert"
style="margin-bottom: 6px"
:title="buffHint.title"
:description="buffHint.desc"
/>
<el-alert
v-if="showEnemyBuffWarn"
type="info"
:closable="false"
show-icon
class="hint-alert"
style="margin-bottom: 6px"
title="buff/debuff 配置提示"
description="buff 为增益语义:缺省挂施法者自身(如吸血/攻击后强化),可用 buff_target 改为全体友方;debuff 为对敌减益语义:缺省挂被命中者(目标不可覆写),对敌技能请用 timed_debuff_id。proc_skill 用于命中后概率触发主动技能,不是挂 buff 的方式。"
/>
<el-button size="small" @click="add">+ 添加覆盖</el-button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ref, watch, computed } from 'vue'
import { OVERRIDE_KEYS, SKILL_ICON_OPTIONS } from '../constants'
const props = defineProps({
@@ -88,9 +121,87 @@ const props = defineProps({
skillOptions: { type: Array, default: () => [] },
// 全部 buff 候选(timed_buff_id 下拉用),由 App 从 meta.BuffList 计算后透传
buffOptions: { type: Array, default: () => [] },
// 宿主技能 uuid:用于判定对敌技能(TGroup=Enemy)注入 timed_buff_id 的断链警示
baseSkillUuid: { type: Number, default: null },
})
const emit = defineEmits(['update:modelValue'])
// 宿主技能是否走对敌路径:有效 TGroup = overrides.TGroup 覆写值 ?? 底座 TGroup(Enemy=3;meta 未加载时不判定)
const isEnemyHost = computed(() => {
if (props.baseSkillUuid == null) return false
const base = props.skillOptions.find((s) => s.uuid === props.baseSkillUuid)
const tgroupRow = rows.value.find((r) => r.key === 'TGroup')
const effective = tgroupRow?.value ?? base?.TGroup
return effective === 3 || effective === 'Enemy'
})
// 对敌技能且注入了 timed_buff_id → 运行时断链不生效,警示引导改用 proc_skill / 底座表
const showEnemyBuffWarn = computed(() => isEnemyHost.value && rows.value.some((r) => r.key === 'timed_buff_id'))
// 易伤 buff id(与游戏侧 BuffSet.VULNERABLE_BUFF_ID 对应),取值语义特殊需单独提示
const VULNERABLE_ID = 7002
// 当前选中的 debuff 配置(timed_debuff_id 行)
const selectedDebuff = computed(() => {
const row = rows.value.find((r) => r.key === 'timed_debuff_id')
const id = typeof row?.value === 'number' ? row.value : Number(row?.value)
if (!id || isNaN(id)) return null
return props.buffOptions.find((b) => b.id === id) ?? null
})
// 依 BuffList 配置推导 debuff_value 取值语义提示(正负号 / 生效方式 / 叠层)
const debuffHint = computed(() => {
const buff = selectedDebuff.value
if (!buff) return null
const stackTxt = buff.max_stack === 0 ? '无限叠层' : buff.max_stack > 1 ? `可叠 ${buff.max_stack} 层` : '不可叠层'
// 控制类:无数值语义
if (buff.is_control) {
return { title: `「${buff.name}」为控制类 debuff`, desc: `无需配置 debuff_value(不生效)。${stackTxt},持续 ${buff.duration}s。` }
}
// 易伤:正值,被打时按层累加附加伤害,可被易伤抗性减免
if (buff.id === VULNERABLE_ID) {
return { title: `「${buff.name}」debuff_value 用正值`, desc: `含义=每层被击中时附加的固定伤害。取值 10 → 目标每次被打多掉 10 血,${stackTxt}时按层累加(N 层=N×value),可被目标易伤抗性减免。负值无效(被拦截为 0)。结算在暴击后、护盾前,不吃暴击。` }
}
// tick 类(DoT/HoT):负=伤害 正=治疗,每 interval 结算,可按 ap 缩放
if (buff.tick) {
const scaleTxt = buff.tick.scale_by_ap_pct > 0 ? `,并按施法者 ap 缩放(每100 ap +${buff.tick.scale_by_ap_pct}%)` : ',不随 ap 缩放'
return { title: `「${buff.name}」为周期伤害 debuff,debuff_value 用负值表伤害`, desc: `含义=每 ${buff.tick.interval}s 一次的基础伤害(负值=掉血,正值=回血)。取值 -10 → 每跳约 10 点伤害${scaleTxt}。${stackTxt},持续 ${buff.duration}s。` }
}
// modifier 类(属性修饰):正负表增减,直接改属性
if (buff.modifiers?.length) {
const mod = buff.modifiers[0]
return { title: `「${buff.name}」为属性修饰 debuff`, desc: `debuff_value 覆写修饰数值(负=削减,正=增强),作用于属性 ${mod.attr}(${mod.op === 1 ? '百分比' : '固定值'})。${stackTxt},持续 ${buff.duration}s。` }
}
return { title: `「${buff.name}」`, desc: `${stackTxt},持续 ${buff.duration}s。` }
})
// 当前选中的 buff 配置(timed_buff_id 行)
const selectedBuff = computed(() => {
const row = rows.value.find((r) => r.key === 'timed_buff_id')
const id = typeof row?.value === 'number' ? row.value : Number(row?.value)
if (!id || isNaN(id)) return null
return props.buffOptions.find((b) => b.id === id) ?? null
})
// buff(增益)取值语义提示:缺省挂施法者自身;tick 类正=回血/负=自伤,modifier 类正=增强
const buffHint = computed(() => {
const buff = selectedBuff.value
if (!buff) return null
const stackTxt = buff.max_stack === 0 ? '无限叠层' : buff.max_stack > 1 ? `可叠 ${buff.max_stack} 层` : '不可叠层'
const targetTxt = '缺省挂施法者自身,可用 buff_target 改全体友方'
if (buff.is_control) {
return { title: `「${buff.name}」为控制类`, desc: `无需配置 buff_value(不生效)。${targetTxt}。${stackTxt},持续 ${buff.duration}s。` }
}
if (buff.tick) {
const scaleTxt = buff.tick.scale_by_ap_pct > 0 ? `,并按自身 ap 缩放(每100 ap +${buff.tick.scale_by_ap_pct}%)` : ',不随 ap 缩放'
return { title: `「${buff.name}」为周期效果 buff`, desc: `buff_value 正值=每 ${buff.tick.interval}s 回血,负值=自伤(如自残出血)。取值 10 → 每跳约回 10 血${scaleTxt}。${targetTxt}。${stackTxt},持续 ${buff.duration}s。` }
}
if (buff.modifiers?.length) {
const mod = buff.modifiers[0]
return { title: `「${buff.name}」为属性修饰 buff`, desc: `buff_value 覆写修饰数值(正=增强,负=削弱),作用于属性 ${mod.attr}(${mod.op === 1 ? '百分比' : '固定值'})。${targetTxt}。${stackTxt},持续 ${buff.duration}s。` }
}
return { title: `「${buff.name}」`, desc: `${targetTxt}。${stackTxt},持续 ${buff.duration}s。` }
})
// 本地行数组,允许 key 为空
const rows = ref(Object.entries(props.modelValue || {}).map(([key, value]) => ({ key, value })))
@@ -155,11 +266,44 @@ function isBuffKey(key) {
return OVERRIDE_KEYS.find((k) => k.key === key)?.type === 'buff'
}
// 选中 proc_skill 被触发技能后,在其内嵌 overrides 自动补 TGroup 行(已存在不覆盖):
// 值取该技能底座的 TGroup,让内嵌编辑器能立刻按有效目标群体做对敌断链警示等判定,也便于直改释放对象
function onProcSkillChange(item) {
const overrides = item.value?.overrides
if (!overrides) return
if (overrides.TGroup !== undefined) return
const base = props.skillOptions.find((s) => s.uuid === item.value.s_uuid)
const name = TGROUP_NAMES[base?.TGroup]
if (name) overrides.TGroup = name
}
// 底座 TGroup 数值 → 字符串名(编辑器 TGroup 下拉 value 为字符串,导出时还原 TGroup.X 枚举表达式)
const TGROUP_NAMES = ['Self', 'Ally', 'Team', 'Enemy', 'All']
// allow-create 手输的 buff id 是字符串,归一化为数字,保证导出 TS 配置类型正确
function onBuffValueChange(item) {
if (typeof item.value === 'string' && item.value.trim() !== '' && !isNaN(Number(item.value))) {
item.value = Number(item.value)
}
ensureBuffCompanionRows(item.value, item.key === 'timed_debuff_id')
}
// 挂载 timed_buff_id / timed_debuff_id 后自动补齐配套三键(已存在的不重复添加):
// 数值预填 BuffList 默认值(modifiers[0].value / tick.damage_or_heal / duration),避免导出 0 值覆盖 buff 语义
// 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(`${prefix}_value`)) {
rows.value.push({ key: `${prefix}_value`, value: buff?.modifiers?.[0]?.value ?? buff?.tick?.damage_or_heal ?? 0 })
}
if (!has(`${prefix}_chance`)) {
rows.value.push({ key: `${prefix}_chance`, value: 100 })
}
if (!has(`${prefix}_duration`)) {
rows.value.push({ key: `${prefix}_duration`, value: buff?.duration ?? 5 })
}
}
// 对象型覆写行选中时初始化默认结构,避免 v-model 绑定到 undefined 子字段
@@ -192,4 +336,9 @@ function remove(idx) {
.proc-skill-editor { display: flex; gap: 8px; align-items: flex-start; flex-wrap: wrap; flex: 1; }
.proc-skill-overrides { width: 100%; padding-left: 12px; border-left: 2px solid var(--el-border-color); }
.proc-skill-label { font-size: 12px; color: var(--el-text-color-secondary); }
/* debuff 取值提示:压缩内边距并用 12px 小字,与编辑器其他辅助文本一致 */
.hint-alert { padding: 4px 8px; }
.hint-alert :deep(.el-alert__title) { font-size: 12px; line-height: 1.4; }
.hint-alert :deep(.el-alert__description) { font-size: 12px; line-height: 1.4; margin-top: 2px; }
.hint-alert :deep(.el-alert__icon) { font-size: 14px; width: 14px; }
</style>

View File

@@ -25,7 +25,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="skill.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="skill.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="skill.uuid" />
</el-form-item>
<!-- 分档编辑:大招(is-ult)全档编辑;普攻(basic-lv2)仅 lv2 强化档(客户端独立技能图标消费) -->
@@ -47,7 +47,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="e.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="e.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="skill.uuid" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="e.desc" type="textarea" :rows="2" />

View File

@@ -23,7 +23,7 @@
</el-form-item>
</div>
<el-form-item label="overrides">
<OverrideEditor v-model="entry.overrides" :skill-options="skillOptions" :buff-options="buffOptions" />
<OverrideEditor v-model="entry.overrides" :skill-options="skillOptions" :buff-options="buffOptions" :base-skill-uuid="Number(sid)" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="entry.desc" type="textarea" :rows="2" />

View File

@@ -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: [
@@ -135,8 +141,8 @@ export const OVERRIDE_KEYS = [
{ key: 'call_hero', label: 'call_hero 召唤英雄id' },
{ key: 'revive_hp', label: 'revive_hp 复活回血比例(%)' },
{ key: 'revive_count', label: 'revive_count 复活次数上限' },
// 对象型覆写:命中后概率触发额外技能(技能级触发器),含 s_uuid/chance/overrides 子字段
{ key: 'proc_skill', label: 'proc_skill 命中概率触发技能', type: 'object' },
// 对象型覆写:命中后概率触发主动技能(大招/触发技),含 s_uuid/chance/overrides 子字段
{ key: 'proc_skill', label: 'proc_skill 命中概率触发主动技能', type: 'object' },
// 字符串型覆写:value 为图标ID(uicons 图集帧名),非数值
{ key: 'icon', label: 'icon 英雄语境图标(覆写 SkillSet.icon)', type: 'string' },
]