1. 新增技能触发概率配置与保底叠加机制,仅对攻击/受击触发生效 2. 前端编辑器新增t_chance概率与pity_add保底增量配置项 3. 技能描述文案支持展示触发概率 4. 运行时新增概率判定与保底缓存逻辑,兼容存量配置
229 lines
10 KiB
TypeScript
229 lines
10 KiB
TypeScript
import { oops } from "db://oops-framework/core/Oops";
|
||
import { GameEvent } from "../common/config/GameEvent";
|
||
import { ResolvedTriggerEntry, SkillTriggerType } from "../common/config/heroSet";
|
||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||
import { HeroViewComp } from "./HeroViewComp";
|
||
import { FacSet } from "../common/config/GameSet";
|
||
import { FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
import { FieldSkillHelper } from "./FieldSkillHelper";
|
||
|
||
|
||
/**
|
||
* 特殊触发技能的统一中介者 (Mediator)
|
||
*
|
||
* 核心职责:
|
||
* 1. 统一管理英雄/怪物的各类生命周期触发技能(召唤、死亡、战前、战后、攻受击)。
|
||
* 2. 集中处理触发次数的计算(如:局内羁绊加成、局外天赋加成)。
|
||
* 3. 作为唯一出口,通过事件总线向 SCastSystem 派发 GameEvent.TriggerSkill 事件。
|
||
*
|
||
* 优势:
|
||
* - 极大地解耦了具体业务组件(Hero/Mon/HeroAtkSystem/MissionComp)与技能系统。
|
||
* - 避免了原先散落在各处的判定逻辑和冗余的事件派发代码。
|
||
*/
|
||
export class SkillTriggerHelper {
|
||
/**
|
||
* 统一的技能触发入口
|
||
* 根据传入的触发类型,自动分发到对应的处理函数中执行判定和加成逻辑。
|
||
*
|
||
* @param type 触发类型枚举 (SkillTriggerType)
|
||
* @param model 施法者的属性数据组件 (HeroAttrsComp)
|
||
* @param view 施法者的视图表现组件 (HeroViewComp)
|
||
*/
|
||
public static trigger(type: SkillTriggerType, model: HeroAttrsComp, view: HeroViewComp) {
|
||
// 健壮性检查:若实体或节点已被销毁,直接阻断,防止空指针报错
|
||
if (!model || !view || !view.node || !view.node.isValid) return;
|
||
|
||
switch (type) {
|
||
case SkillTriggerType.Call:
|
||
this.handleCall(model, view);
|
||
break;
|
||
case SkillTriggerType.Dead:
|
||
this.handleDead(model, view);
|
||
break;
|
||
case SkillTriggerType.FStart:
|
||
this.handleArrayTrigger(model.runtime_fstart, model, view, type);
|
||
break;
|
||
case SkillTriggerType.FEnd:
|
||
this.handleArrayTrigger(model.runtime_fend, model, view, type);
|
||
break;
|
||
case SkillTriggerType.Atking:
|
||
this.handleAtking(model, view);
|
||
break;
|
||
case SkillTriggerType.Atked:
|
||
this.handleAtked(model, view);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理召唤(落地)触发技能
|
||
* 支持受【召唤强化羁绊 (SummonCount)】的多次触发加成。
|
||
*/
|
||
private static handleCall(model: HeroAttrsComp, view: HeroViewComp) {
|
||
if (!model.runtime_call || model.runtime_call.length === 0) return;
|
||
|
||
let triggerCount = 1;
|
||
// 仅英雄享受加成,怪物始终只触发 1 次
|
||
if (model.fac === FacSet.HERO) {
|
||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.SummonCount);
|
||
}
|
||
triggerCount = Math.max(1, Math.floor(triggerCount)); // 确保最少触发 1 次
|
||
|
||
for (let i = 0; i < triggerCount; i++) {
|
||
this.dispatchArray(model.runtime_call, model, view, SkillTriggerType.Call);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理死亡触发技能
|
||
* 支持受【亡语强化羁绊 (DeadCount)】的多次触发加成。
|
||
*/
|
||
private static handleDead(model: HeroAttrsComp, view: HeroViewComp) {
|
||
if (!model.runtime_dead || model.runtime_dead.length === 0) return;
|
||
|
||
let triggerCount = 1;
|
||
if (model.fac === FacSet.HERO) {
|
||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.DeadCount);
|
||
// 【局内战绩评分系统】统计死亡触发技能生效次数(用于局后防守评分结算)
|
||
smc.vmdata.scores.dead_trigger_count += model.runtime_dead.length;
|
||
}
|
||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||
|
||
for (let i = 0; i < triggerCount; i++) {
|
||
this.dispatchArray(model.runtime_dead, model, view, SkillTriggerType.Dead);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理攻击附加技能
|
||
* 判定逻辑:依据配置中的 t_num(触发所需普攻次数),对比当前的普攻累计次数取模判定。
|
||
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
|
||
*/
|
||
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
|
||
if (!model.runtime_atking || model.runtime_atking.length === 0) return;
|
||
|
||
let triggerCount = 1;
|
||
if (model.fac === FacSet.HERO) {
|
||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
|
||
}
|
||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
|
||
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
|
||
triggerCount += 1;
|
||
}
|
||
// L4 天赋:减少触发所需次数(下限 1)
|
||
const tNumReduce = model.talent_t_num_reduce;
|
||
|
||
model.runtime_atking.forEach(atkConfig => {
|
||
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
|
||
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
|
||
if (model.atk_count > 0 && model.atk_count % tNum === 0
|
||
&& this.rollTriggerChance(model, SkillTriggerType.Atking, atkConfig)) {
|
||
for (let i = 0; i < triggerCount; i++) {
|
||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 处理受击附加技能
|
||
* 判定逻辑:依据配置中的 t_num(触发所需受击次数),对比当前的受击累计次数取模判定。
|
||
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
|
||
*/
|
||
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
|
||
if (!model.runtime_atked || model.runtime_atked.length === 0) return;
|
||
|
||
let triggerCount = 1;
|
||
if (model.fac === FacSet.HERO) {
|
||
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
|
||
}
|
||
triggerCount = Math.max(1, Math.floor(triggerCount));
|
||
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
|
||
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
|
||
triggerCount += 1;
|
||
}
|
||
// L4 天赋:减少触发所需次数(下限 1)
|
||
const tNumReduce = model.talent_t_num_reduce;
|
||
|
||
model.runtime_atked.forEach(atkConfig => {
|
||
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
|
||
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
|
||
if (model.atked_count > 0 && model.atked_count % tNum === 0
|
||
&& this.rollTriggerChance(model, SkillTriggerType.Atked, atkConfig)) {
|
||
for (let i = 0; i < triggerCount; i++) {
|
||
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 触发概率判定 + 保底叠加(仅 Atking/Atked 消费)
|
||
* 次数条件达成后按 t_chance(缺省 100 必触发,兼容存量配置)掷骰;
|
||
* 未命中且配置 pity_add > 0 时,为该 s_uuid 累积下次概率增量,形成"越不触发越容易触发"的保底体验;
|
||
* 触发成功后清空叠加,最终概率封顶 100。
|
||
*
|
||
* @param model 施法者属性组件(保底缓存挂载于此)
|
||
* @param type 触发类型(决定读/写哪份保底缓存)
|
||
* @param config 当前生效的触发条目
|
||
* @returns 是否通过概率判定
|
||
*/
|
||
private static rollTriggerChance(model: HeroAttrsComp, type: SkillTriggerType, config: ResolvedTriggerEntry): boolean {
|
||
const pityMap = type === SkillTriggerType.Atking ? model.pity_atking : model.pity_atked;
|
||
const chance = Math.min(100, (config.t_chance ?? 100) + (pityMap[config.s_uuid] ?? 0));
|
||
if (chance >= 100 || Math.random() * 100 < chance) {
|
||
pityMap[config.s_uuid] = 0; // 触发成功:清空保底叠加
|
||
return true;
|
||
}
|
||
const pityAdd = config.pity_add ?? 0;
|
||
if (pityAdd > 0) {
|
||
pityMap[config.s_uuid] = (pityMap[config.s_uuid] ?? 0) + pityAdd;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 通用的数组型触发器处理(适用于 FStart 等无需额外判定的简单列表触发)
|
||
*/
|
||
private static handleArrayTrigger(configs: { s_uuid: number, t_num: number, overrides?: SkillOverrides }[] | undefined, model: HeroAttrsComp, view: HeroViewComp, type: SkillTriggerType) {
|
||
if (!configs || configs.length === 0) return;
|
||
this.dispatchArray(configs, model, view, type);
|
||
}
|
||
|
||
/**
|
||
* 批量派发技能事件
|
||
*/
|
||
private static dispatchArray(configs: { s_uuid: number, t_num?: number, overrides?: SkillOverrides }[], model: HeroAttrsComp, view: HeroViewComp, type: SkillTriggerType) {
|
||
configs.forEach(config => this.dispatchSingle(config.s_uuid, model, view, type, config.overrides));
|
||
}
|
||
|
||
/**
|
||
* 单一技能事件派发底层接口
|
||
* 事件发出后,将由 SCastSystem 的 forceCastTriggerSkill 监听拦截并执行无视CD的强制施法
|
||
*/
|
||
private static dispatchSingle(s_uuid: number, model: HeroAttrsComp, view: HeroViewComp, type: SkillTriggerType, overrides?: SkillOverrides) {
|
||
oops.message.dispatchEvent(GameEvent.TriggerSkill, {
|
||
s_uuid: s_uuid,
|
||
heroAttrs: model,
|
||
heroView: view,
|
||
triggerType: type,
|
||
overrides: overrides
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 公共单技能派发(供 HeroAtkSystem 等命中后触发场景调用)。
|
||
* 直接复用 TriggerSkill 通道(forceCastTriggerSkill),无视 CD、无施法前摇。
|
||
* @param s_uuid 被触发的技能 uuid
|
||
* @param model 施法者属性组件
|
||
* @param view 施法者视图组件
|
||
* @param type 触发类型(语义来源,如 SkillTriggerType.Atking)
|
||
* @param overrides 透传给被触发技能的覆写参数
|
||
*/
|
||
public static dispatchSinglePublic(s_uuid: number, model: HeroAttrsComp, view: HeroViewComp, type: SkillTriggerType, overrides?: SkillOverrides) {
|
||
this.dispatchSingle(s_uuid, model, view, type, overrides);
|
||
}
|
||
}
|