refactor: 重构技能触发逻辑,将proc_skill从命中触发改为施法后触发

1.  调整proc_skill触发时机为施法出手时判定,一次施法仅判定一次,与命中/弹道数无关
2.  移除HeroAtkSystem中旧的proc_skill触发逻辑,新增SCastSystem的tryProcSkill方法
3.  更新技能配置注释、编辑器文案与文档说明,同步调整服务端技能描述显示
4.  全局设置Element Plus组件为small尺寸,优化编辑器UI布局
5.  清理冗余的proc_skill快照传递逻辑,移除unused的reviveOptions计算属性
This commit is contained in:
pan
2026-09-08 17:22:41 +08:00
parent fe0b121cb5
commit 799b93817d
14 changed files with 83 additions and 66 deletions

View File

@@ -1,6 +1,5 @@
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { HeroViewComp } from "./HeroViewComp";
import { SkillOverrides } from "../common/config/SkillSet";
/** 命中后对敌 debuff 快照(由施法技能实体自合并后配置提取并透传,HeroAtkSystem 据此结算) */
export interface TimedDebuffSnapshot {
@@ -38,9 +37,6 @@ export interface DamageEvent {
* 跳过 dmgCount 公式直接用该值,且不走击退/受击动画,其余(暴击/冰冻/击晕/护盾/死亡链)与普攻一致
*/
dot_dmg?: number
/** 命中后概率触发额外技能(由施法技能实体快照自合并后配置,HeroAtkSystem 据此判定;
* overrides 字段透传给被触发技能,用于自定义伤害/数值) */
proc_skill?: { s_uuid: number; chance?: number; overrides?: SkillOverrides }
/** 命中后对敌 debuff(由施法技能实体自合并后配置快照透传;缺省时 HeroAtkSystem 回退读 SkillSet 底座) */
timed_debuff?: TimedDebuffSnapshot
}
@@ -79,7 +75,7 @@ export class DamageQueueComp extends ecs.Comp {
/**
* 添加伤害事件到队列
*/
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 {
addDamageEvent(attrs: any, casterEid: number, s_uuid: number, ext_dmg: number = 0, dmg_ratio: number = 1, dot_dmg: number = 0, timed_debuff?: TimedDebuffSnapshot): void {
const timestamp = Date.now();
const damageEvent: DamageEvent = {
@@ -91,7 +87,6 @@ export class DamageQueueComp extends ecs.Comp {
ext_dmg: ext_dmg,
dmg_ratio: dmg_ratio,
dot_dmg: dot_dmg,
proc_skill: proc_skill,
timed_debuff: timed_debuff,
};
@@ -198,7 +193,7 @@ 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 }, timed_debuff?: TimedDebuffSnapshot): 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, timed_debuff?: TimedDebuffSnapshot): void {
let damageQueue = entity.get(DamageQueueComp);
// 如果实体没有伤害队列组件,创建一个
@@ -207,7 +202,7 @@ export class DamageQueueHelper {
}
// 添加伤害事件到队列
damageQueue.addDamageEvent(attrs, casterEid, s_uuid, ext_dmg, dmg_ratio, dot_dmg, proc_skill, timed_debuff);
damageQueue.addDamageEvent(attrs, casterEid, s_uuid, ext_dmg, dmg_ratio, dot_dmg, timed_debuff);
}
/**

View File

@@ -323,29 +323,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
}
}
// 命中概率触发额外技能(proc_skill):配置由施法技能实体快照自合并后配置(含 heroSet overrides/lv_entries),
// 触发走 SkillTriggerHelper/forceCastTriggerSkill 通道(无视 CD、无施法前摇),与 atking 同一通道。
// 防递归:被触发的技能经 forceCastTriggerSkill 不再产生 proc_skill 伤害事件之外的新事件,且不会累加 atk_count。
if (!isDot && damageEvent.proc_skill) {
const proc = damageEvent.proc_skill;
const procChance = proc.chance ?? 100;
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})`);
}
}
// proc_skill 已改为施法后触发(SCastSystem 在出手时判定),不再随伤害事件命中触发
// 检查死亡
if (TAttrsComp.hp <= 0) {

View File

@@ -91,9 +91,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
targetEids = this.collectFriendlyTargetEids(FacSet.HERO, undefined, true); // 获取所有英雄阵营的目标
}
// 召唤数量由技能 prefab 的 SkillView.summon_count 决定;召唤类技能(>1)默认不再参与风怒多发
const summonCount = this.resolveSummonCount(config);
// 多发次数由 overrides.num 提供(技能卡差异化多发),限制在 [0, 2];召唤类技能屏蔽风怒多发
const cNum = summonCount > 1 ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0)));
const castTimes = Math.max(summonCount, 1 + cNum);
@@ -168,6 +166,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
setTimeout(exec, i * FightSet.SUMMON_INTERVAL * 1000);
}
}
// 施法后概率触发额外技能(proc_skill,原命中触发改为出手即判定;卡牌技能无视 CD、无施法主体)
this.tryProcSkill(config, null, null, SkillTriggerType.Atking);
}
/** 专用于卡牌施放的技能实体生成 */
@@ -315,9 +315,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
}
}
// 召唤数量由技能 prefab 的 SkillView.summon_count 决定;召唤类技能(>1)默认不再参与风怒多发
const summonCount = this.resolveSummonCount(config);
// 多发次数由 overrides.num 提供(技能卡差异化多发),限制在 [0, 2];召唤类技能屏蔽风怒多发
const cNum = summonCount > 1 ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0)));
const castTimes = Math.max(summonCount, 1 + cNum);
let val = ""
@@ -342,6 +340,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides);
}, i * FightSet.SUMMON_INTERVAL);
}
// 施法后概率触发额外技能(proc_skill,原命中触发改为出手即判定,无视 CD)
this.tryProcSkill(config, heroAttrs, heroView, triggerType);
}
/**
@@ -397,10 +397,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
config = mergeSkillParams(config, overrides);
// 召唤数量由技能 prefab 的 SkillView.summon_count 决定;召唤类技能(summon_count>1)不再触发风怒
const summonCount = this.resolveSummonCount(config);
const isSummonSkill = summonCount > 1;
// 多发次数由 overrides.num 提供(技能卡差异化多发),限制在 [0, 2];召唤类技能屏蔽风怒多发
const cNum = isSummonSkill ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0)));
const castTimes = Math.max(summonCount, 1 + cNum);
@@ -413,13 +411,16 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
// 因为 castSkill 只由 update 循环调用,处理的必然是 heroAttrs.skills 中的普通技能
// 特殊触发技能(call/dead/atking等)走的是 forceCastTriggerSkill,不会调用 castSkill
// 召唤类技能(summon_count>1)不再计入普攻链,避免触发 atking(风怒类)附加技能
// 召唤类技能(summon_count>1)不再计入普攻链,避免触发 atking 附加技能
const skillIds = heroAttrs.getSkillIds();
if (!isSummonSkill && skillIds.includes(s_uuid)) {
heroAttrs.atk_count++;
this.checkAndTriggerAtkingSkills(heroAttrs, heroView);
}
// 施法后概率触发额外技能(proc_skill,出手即判定,无视 CD,不再依赖命中)
this.tryProcSkill(config, heroAttrs, heroView, SkillTriggerType.Atking);
// 优先使用技能配置的前摇时间,否则使用全局默认值
// 注意:这里仍然是基于时间的延迟,受帧率波动影响。
// 若需精确同步,建议在动画中添加帧事件并在 HeroViewComp 中监听。
@@ -452,6 +453,30 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
SkillTriggerHelper.trigger(SkillTriggerType.Atking, heroAttrs, heroView);
}
/**
* 施法后概率触发额外技能(proc_skill)。
* 语义由“命中触发”调整为“施法出手即判定”,一次施法只判定一次(与召唤/多发的实体数量无关),
* 走 TriggerSkill 通道(forceCastTriggerSkill),无视 CD、无施法前摇。
* @param config 合并后(含 overrides)的技能配置
* @param heroAttrs 施法者属性组件(卡牌技能传 null)
* @param heroView 施法者视图组件(卡牌技能传 null)
* @param triggerType 触发类型语义(仅日志标识用)
*/
private tryProcSkill(config: SkillConfig, heroAttrs: HeroAttrsComp | null, heroView: HeroViewComp | null, triggerType?: string) {
const proc = config.proc_skill;
if (!proc) return;
const chance = proc.chance ?? 100;
const rand = chance >= 100 ? 0 : Math.random() * 100;
const casterName = heroAttrs?.hero_name ?? "卡牌";
if (chance >= 100 || rand < chance) {
if (!heroAttrs || !heroView) return; // 卡牌技能无施法主体,proc 仅支持英雄触发
mLogger.log(this.debugMode, "SCastSystem", `[概率判定] 额外技能「${SkillSet[proc.s_uuid]?.name ?? "?"}」 概率:${chance.toFixed(1)}% 随机:${rand.toFixed(1)} -> ✔成功(技能「${config.name}」施法后触发,由 ${casterName} 释放)`);
SkillTriggerHelper.dispatchSinglePublic(proc.s_uuid, heroAttrs, heroView, SkillTriggerType.Atking, proc.overrides);
} else {
mLogger.log(this.debugMode, "SCastSystem", `[概率判定] 额外技能「${SkillSet[proc.s_uuid]?.name ?? "?"}」 概率:${chance.toFixed(1)}% 随机:${rand.toFixed(1)} -> ✘失败(技能「${config.name}」施法后未触发)`);
}
}
/** 多发/召唤的纵向扇形偏移档位(像素),索引按 中/上/下/上上/下下... 循环 */
private static readonly REPEAT_Y_OFFSETS: number[] = [0, 15, -15, 30, -30, 45, -45, 60, -60];