Files
pixelheros/assets/script/game/hero/SCastSystem.ts
panw 5d83bd1516 refactor(skill): 将buff/debuff应用逻辑从SkillView移到SCastSystem
重构技能效果应用逻辑,将buff/debuff处理从SkillView的碰撞检测中移除,统一在SCastSystem中根据技能配置决定是否创建技能实体或直接应用支持效果。这样可以更清晰地分离伤害技能和支持技能的处理逻辑,避免在碰撞时重复应用效果。
2026-03-12 22:02:42 +08:00

191 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { Vec3 } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { BuffsList, SkillConfig, SkillSet, TGroup, TType } from "../common/config/SkillSet";
import { Skill } from "../skill/Skill";
import { smc } from "../common/SingletonModuleComp";
import { GameConst } from "../common/config/GameConst";
import { mLogger } from "../common/Logger";
import { Attrs } from "../common/config/HeroAttrs";
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 1. 检测可施放的技能
* 2. 根据策略自动施法AI
* 3. 选择目标
* 4. 添加施法请求标记
*
* 设计理念:
* - 负责"何时施法"的决策
* - 通过添加 CSRequestComp 触发施法
* - 可被玩家输入系统或AI系统复用
* - 支持多种AI策略
*/
@ecs.register('SCastSystem')
export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
debugMode: boolean = false; // 是否启用调试模式
filter(): ecs.IMatcher {
return ecs.allOf(HeroAttrsComp, HeroViewComp);
}
update(e: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return
const heroAttrs = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
if (!heroAttrs || !heroView || !heroView.node) return;
if (heroAttrs.is_dead || heroAttrs.is_reviving || heroAttrs.isStun() || heroAttrs.isFrost()) return;
heroAttrs.updateCD(this.dt);
if (!heroAttrs.is_atking) return;
const castSkillId = this.pickCastSkill(heroAttrs, heroView);
if (castSkillId === 0) return;
this.castSkill(e, castSkillId, heroAttrs, heroView);
}
private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): number {
const skillCandidates = [heroAttrs.skill_id, heroAttrs.atk_id];
for (const s_uuid of skillCandidates) {
if (!s_uuid) continue;
const config = SkillSet[s_uuid];
if (!config) continue;
const isMainSkill = s_uuid === heroAttrs.skill_id;
if (isMainSkill && !heroAttrs.can_skill) continue;
if (!isMainSkill && !heroAttrs.can_atk) continue;
const targets = this.findTargets(heroView, heroAttrs, config);
if (targets.length === 0) continue;
return s_uuid;
}
return 0;
}
private castSkill(entity: ecs.Entity, s_uuid: number, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) {
const config = SkillSet[s_uuid];
if (!config) return;
const targets = this.findTargets(heroView, heroAttrs, config);
if (targets.length === 0) return;
heroView.playSkillEffect(s_uuid);
const isMainSkill = s_uuid === heroAttrs.skill_id;
const delay = GameConst.Battle.SKILL_CAST_DELAY;
heroView.scheduleOnce(() => {
if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return;
// 优先判断是否有ap伤害物理/魔法伤害)
if (config.ap > 0) {
this.createSkillEntity(s_uuid, heroView, targets[0].node.position);
}
// 接着应用buff效果包括治疗、护盾等已转换为buff的逻辑
if ((config.buffs && config.buffs.length > 0) || (config.debuffs && config.debuffs.length > 0)) {
this.applySupportSkill(entity, config, targets);
}
}, delay);
if (isMainSkill) {
heroAttrs.triggerSkillCD();
} else {
heroAttrs.triggerAtkCD();
}
}
private createSkillEntity(s_uuid: number, caster: HeroViewComp, targetPos: Vec3) {
if (!caster.node || !caster.node.isValid) return;
const parent = caster.node.parent;
if (!parent) return;
const skill = ecs.getEntity<Skill>(Skill);
skill.load(caster.node.position.clone(), parent, s_uuid, targetPos.clone(), caster, 0);
}
private applySupportSkill(casterEntity: ecs.Entity, config: SkillConfig, targets: HeroViewComp[]) {
const casterAttrs = casterEntity.get(HeroAttrsComp);
if (!casterAttrs) return;
for (const target of targets) {
if (!target.ent) continue;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) continue;
// 统一通过 Buff 系统应用效果
if (config.buffs) {
for (const buffId of config.buffs) {
const buffConf = BuffsList[buffId];
if (buffConf) {
model.addBuff(buffConf);
}
}
}
// 应用 Debuff 效果
if (config.debuffs) {
for (const buffId of config.debuffs) {
const buffConf = BuffsList[buffId];
if (buffConf) {
model.addBuff(buffConf);
}
}
}
}
mLogger.log(this.debugMode, "SCastSystem", `[SCastSystem] ${casterAttrs.hero_name} 施放 ${config.name}`);
}
private findTargets(caster: HeroViewComp, casterAttrs: HeroAttrsComp, config: SkillConfig): HeroViewComp[] {
const range = casterAttrs.getCachedMaxSkillDistance() || GameConst.Battle.DEFAULT_SEARCH_RANGE;
const isEnemy = config.TGroup === TGroup.Enemy;
const isSelf = config.TGroup === TGroup.Self;
const isTeam = config.TGroup === TGroup.Team || config.TGroup === TGroup.Ally;
const isAll = config.TGroup === TGroup.All;
if (isSelf) return [caster];
const currentPos = caster.node.position;
const list: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number }[] = [];
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(ent => {
const targetAttrs = ent.get(HeroAttrsComp);
const targetView = ent.get(HeroViewComp);
if (!targetAttrs || !targetView || !targetView.node || targetAttrs.is_dead) return;
if (targetView === caster) return;
const isSameFac = targetAttrs.fac === casterAttrs.fac;
if (isEnemy && isSameFac) return;
if (isTeam && !isSameFac) return;
if (!isEnemy && !isTeam && !isAll) return;
const dis = Math.abs(currentPos.x - targetView.node.position.x);
if (dis > range) return;
const lane = Math.abs(currentPos.y - targetView.node.position.y);
list.push({ view: targetView, attrs: targetAttrs, dis, lane });
});
list.sort((a, b) => {
// 优先检查是否在同一行 (除了特殊目标类型)
// 如果是寻找特殊目标(如最低血量),通常忽略行优先,但在范围内全搜索
// 但如果设计要求"最近的优先",则通常还是先看行。
// 这里假设 TType 优先级高于 Lane 优先级,或者在 TType 相同情况下比较 Lane
const type = config.TType ?? TType.Frontline;
switch (type) {
case TType.Backline:
// 后排:距离最远优先
if (a.lane !== b.lane) return a.lane - b.lane; // 先同行
return b.dis - a.dis;
case TType.LowestHP:
// 最低血量
if (a.attrs.hp !== b.attrs.hp) return a.attrs.hp - b.attrs.hp;
return a.dis - b.dis; // 血量相同选最近
case TType.HighestHP:
// 最高血量
if (a.attrs.hp !== b.attrs.hp) return b.attrs.hp - a.attrs.hp;
return a.dis - b.dis;
case TType.HighestAP:
// 最高攻击
if (a.attrs.ap !== b.attrs.ap) return b.attrs.ap - a.attrs.ap;
return a.dis - b.dis;
case TType.Frontline:
default:
// 前排:距离最近优先 (默认)
if (a.lane !== b.lane) return a.lane - b.lane;
return a.dis - b.dis;
}
});
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, 1);
return list.slice(0, maxTargets).map(item => item.view);
}
}