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, SType, TGroup } 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"; /** * ==================== 自动施法系统 ==================== * * 职责: * 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; if (config.SType === SType.damage) { this.createSkillEntity(s_uuid, heroView, targets[0].node.position); } else { 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.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; const ratio = Number(config.ap ?? 0) / 100; for (const target of targets) { if (!target.ent) continue; const model = target.ent.get(HeroAttrsComp); if (!model || model.is_dead) continue; if (config.SType === SType.heal) { const amount = model.hp_max * ratio; model.add_hp(amount, true); target.health(amount); continue; } if (config.SType === SType.shield) { const amount = model.hp_max * ratio; model.shield_max = Math.max(model.shield_max, amount); model.add_shield(amount, true); continue; } if (config.SType === SType.buff) { for (const buffId of config.buffs) { 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; 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, dis, lane }); }); list.sort((a, b) => { 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); } }