import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { Vec3, v3 } from "cc"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { HeroViewComp } from "./HeroViewComp"; import { HSSet, SkillSet, SType } from "../common/config/SkillSet"; import { HeroSkillsComp, SkillSlot } from "./HeroSkills"; import { Skill } from "../skill/Skill"; import { smc } from "../common/SingletonModuleComp"; import { TalComp } from "./TalComp"; import { TalEffet, TriType } from "../common/config/TalSet"; import { BoxSet } from "../common/config/GameSet"; /** * ==================== 自动施法系统 ==================== * * 职责: * 1. 检测可施放的技能 * 2. 根据策略自动施法(AI) * 3. 选择目标 * 4. 添加施法请求标记 * * 设计理念: * - 负责"何时施法"的决策 * - 通过添加 CSRequestComp 触发施法 * - 可被玩家输入系统或AI系统复用 * - 支持多种AI策略 */ @ecs.register('SACastSystem') export class SACastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { filter(): ecs.IMatcher { return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp); } update(e: ecs.Entity): void { if(!smc.mission.play || smc.mission.pause) return; const skills = e.get(HeroSkillsComp); const heroAttrs = e.get(HeroAttrsComp); const heroView = e.get(HeroViewComp); if (!skills || !heroAttrs || !heroView) return; // 检查基本条件 if (heroAttrs.is_dead || heroAttrs.isStun() || heroAttrs.isFrost()) return; // 检查是否正在攻击(只有攻击时才释放技能) if (!heroAttrs.is_atking) return; // 获取所有可施放的技能 const readySkills = skills.getReadySkills(heroAttrs.mp); if (readySkills.length === 0) return; // 选择第一个可施放的伤害技能 for (const s_uuid of readySkills) { const skill = skills.getSkill(s_uuid); if (!skill) continue; if (skill.hset === HSSet.max && !skills.max_auto) continue; const config = SkillSet[skill.s_uuid]; if (!config || config.SType !== SType.damage) continue; // 检查是否有敌人在技能攻击范围内 if (!this.hasEnemyInSkillRange(heroView, heroAttrs, skill.dis)) continue; // ✅ 开始执行施法 this.startCast(e,skill,skill.hset); // 一次只施放一个技能 break; } } private startCast(e: ecs.Entity,skill:SkillSlot,hset:HSSet): boolean { if (!skill||!e) return false const skills = e.get(HeroSkillsComp); const heroAttrs = e.get(HeroAttrsComp); const heroView = e.get(HeroViewComp); // 3. 检查施法条件 if (!this.checkCastConditions(skills, heroAttrs, skill.s_uuid)) return false // 4. 执行施法 const castSucess = this.executeCast(e, skill.s_uuid, heroView,hset); // 5. 扣除资源和重置CD if (castSucess) { heroAttrs.mp -= skill.cost; skills.resetCD(skill.s_uuid); } return castSucess; } public manualCast(e: ecs.Entity, s_uuid: number): boolean { if (!e) return false const skills = e.get(HeroSkillsComp) const heroAttrs = e.get(HeroAttrsComp) const heroView = e.get(HeroViewComp) if (!skills || !heroAttrs || !heroView) return false const slot = skills.getSkill(s_uuid) if (!slot) return false return this.startCast(e, slot, slot.hset) } public manualCastMax(e: ecs.Entity): boolean { const skills = e.get(HeroSkillsComp) if (!skills) return false for (const key in skills.skills) { const s_uuid = Number(key) const slot = skills.getSkill(s_uuid) if (slot && slot.hset === HSSet.max) { return this.manualCast(e, s_uuid) } } return false } /** * 检查施法条件 */ private checkCastConditions(skills: HeroSkillsComp, heroAttrs: HeroAttrsComp, s_uuid: number): boolean { // 检查角色状态 if (heroAttrs.is_dead) { return false; } // 检查控制状态(眩晕、冰冻) if (heroAttrs.isStun() || heroAttrs.isFrost()) { return false; } // 检查CD和MP if (!skills.canCast(s_uuid, heroAttrs.mp)) { return false; } return true; } /** * 执行施法 */ private executeCast(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp,hset:HSSet): boolean { const heroAttrs=casterEntity.get(HeroAttrsComp) const config = SkillSet[s_uuid]; if (!config) { console.error("[SACastSystem] 技能配置不存在:", s_uuid); return false; } // 1. 播放施法动画 heroView.playSkillEffect(s_uuid); /**********************天赋处理*************************************************************************/ // 2. 更新攻击类型的天赋触发值,技能和必杀级 if(casterEntity.has(TalComp)){ const talComp = casterEntity.get(TalComp); if (hset === HSSet.atk) talComp.updateCur(TriType.ATK); if (hset === HSSet.skill) talComp.updateCur(TriType.SKILL); if (hset === HSSet.max) talComp.updateCur(TriType.MAX); } /**********************天赋处理*************************************************************************/ // 获取目标位置 let targets = this.sTargets(heroView, s_uuid); if (targets.length === 0) { console.warn("[SACastSystem] 没有找到有效目标"); return false; } // 2.1 普通攻击逻辑 if (hset === HSSet.atk){ let delay = 0.3 let ext_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG); let splash = heroAttrs.useCountValTal(TalEffet.SPLASH); heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,targets,ext_dmg,splash); }, delay); //风怒wfuny 只针对 普通攻击起效 if (heroAttrs.useCountTal(TalEffet.WFUNY)){ let ext2_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG); let splash2 = heroAttrs.useCountValTal(TalEffet.SPLASH); let delay = 0.3 heroView.playSkillEffect(s_uuid); //需要再添加 风怒动画 heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,targets,ext2_dmg,splash2); },delay); } } // 2.2 技能攻击逻辑 if(hset === HSSet.skill){ let delay = 0.3 let ext_dmg = heroAttrs.useCountValTal(TalEffet.SKILL_DMG); heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,targets,ext_dmg); }, delay); // 双技能 只针对 技能起效 if(heroAttrs.useCountTal(TalEffet.D_SKILL)){ let ext2_dmg = heroAttrs.useCountValTal(TalEffet.SKILL_DMG); let delay = 0.3 heroView.playSkillEffect(s_uuid); //需要再添加 双技能动画 heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,targets,ext2_dmg); },delay); } } // 2.3 必杀技能逻辑 if(hset === HSSet.max){ let delay = 0.3 heroView.playSkillEffect(s_uuid); //需要再添加 最大伤害动画 heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,targets); },delay); } return true; } /** * 创建技能实体 */ private createSkill(s_uuid: number, caster: HeroViewComp,targets:Vec3[]=[],ext_dmg:number=0,splash:number=0) { // 检查节点有效性 if (!caster.node || !caster.node.isValid) { console.warn("[SACastSystem] 施法者节点无效"); return; } // 获取场景节点 const parent = caster.node.parent; if (!parent) { console.warn("[SACastSystem] 场景节点无效"); return; } // 创建技能实体 const skill = ecs.getEntity(Skill); // 获取施法者位置作为起始位置 const startPos = caster.node.position.clone(); const targetPos = targets[0]; // 使用第一个目标位置 // console.log(`[SACastSystem]: ${s_uuid}, 起始位置: ${startPos}, 目标位置: ${targetPos}`); // 加载技能实体(包括预制体、组件初始化等) skill.load(startPos, parent, s_uuid, targetPos, caster,ext_dmg,splash); } /** * 选择目标位置 */ private sTargets(caster: HeroViewComp, s_uuid: number): Vec3[] { const heroAttrs = caster.ent.get(HeroAttrsComp); if (!heroAttrs) return []; const config = SkillSet[s_uuid]; if (!config) return this.sDefaultTargets(caster, heroAttrs.fac); const maxTargets = Math.max(1, Number((config as any).t_num ?? 1)); const targets = this.sDamageTargets(caster, config, maxTargets); if (targets.length === 0) { targets.push(...this.sDefaultTargets(caster, heroAttrs.fac)); } return targets; } /** * 选择伤害技能目标 */ private sDamageTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] { const targets: Vec3[] = []; const heroAttrs = caster.ent.get(HeroAttrsComp); if (!heroAttrs) return targets; const range = Number((config as any).range ?? config.dis ?? 300); const enemyPositions = this.findNearbyEnemies(caster, heroAttrs.fac, range); // 选择最多maxTargets个目标 for (let i = 0; i < Math.min(maxTargets, enemyPositions.length); i++) { targets.push(enemyPositions[i]); } // 如果没有找到敌人,使用默认位置 if (targets.length === 0) { targets.push(...this.sDefaultTargets(caster, heroAttrs.fac)); } return targets; } /** * 选择默认目标 */ private sDefaultTargets(caster: HeroViewComp, fac: number): Vec3[] { const targets: Vec3[] = []; const defaultX = fac === 0 ? 400 : -400; targets.push(v3(defaultX, BoxSet.GAME_LINE, 1)); return targets; } /** * 查找附近的敌人 */ private findNearbyEnemies(caster: HeroViewComp, fac: number, range: number): Vec3[] { const enemies: Vec3[] = []; if (!caster || !caster.node) return enemies; const currentPos = caster.node.position; const results: { pos: Vec3; dist: number; laneBias: number }[] = []; ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { const model = e.get(HeroAttrsComp); const view = e.get(HeroViewComp); if (!model || !view || !view.node) return false; if (model.is_dead) return false; if (model.fac === fac) return false; const pos = view.node.position; const dist = Math.abs(currentPos.x - pos.x); if (dist <= range) { const laneBias = Math.abs(currentPos.y - pos.y); results.push({ pos: pos.clone(), dist, laneBias }); } return false; }); results.sort((a, b) => { if (a.laneBias !== b.laneBias) return a.laneBias - b.laneBias; return a.dist - b.dist; }); for (const r of results) enemies.push(r.pos); return enemies; } /** * 检查技能攻击范围内是否有敌人 */ private hasEnemyInSkillRange(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number): boolean { if (!heroView || !heroView.node) return false; const currentPos = heroView.node.position; const team = heroAttrs.fac; let found = false; ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => { const model = e.get(HeroAttrsComp); const view = e.get(HeroViewComp); if (!view || !view.node) return false; const distance = Math.abs(currentPos.x - view.node.position.x); if (model.fac !== team && !model.is_dead) { if (distance <= skillDistance) { found = true; return true; } } }); return found; } }