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"; /** * ==================== 自动施法系统 ==================== * * 职责: * 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 ok = this.executeCast(e, skill.s_uuid, heroView,hset); // 5. 扣除资源和重置CD if (ok) { heroAttrs.mp -= skill.cost; skills.resetCD(skill.s_uuid); } return ok; } 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 config = SkillSet[s_uuid]; if (!config) { console.error("[SACastSystem] 技能配置不存在:", s_uuid); return false; } // 1. 播放施法动画 heroView.playSkillEffect(s_uuid); let isDSill=false let isWFuny=false // 2. 更新攻击类型的天赋触发值 if(casterEntity.has(TalComp)){ const talComp = casterEntity.get(TalComp); if (hset === HSSet.atk) talComp.updateCur(TriType.ATK); if (hset != HSSet.atk) talComp.updateCur(TriType.SKILL); isDSill=talComp.checkIsTrigger().isDSill isWFuny=talComp.checkIsTrigger().isWFuny } // 2. 延迟创建技能实体(等待动画) const delay = 0.3 heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,isWFuny); isWFuny=false }, delay); if(isDSill){ heroView.playSkillEffect(s_uuid); heroView.scheduleOnce(() => { this.createSkill(s_uuid, heroView,isWFuny); isWFuny=false }, delay); } const heroAttrs = casterEntity.get(HeroAttrsComp); // console.log(`[SACastSystem] ${heroAttrs?.hero_name ?? '未知'} 施放技能: ${config.name}`); return true; } /** * 创建技能实体 */ private createSkill(s_uuid: number, caster: HeroViewComp,isWFuny:boolean) { // 检查节点有效性 if (!caster.node || !caster.node.isValid) { console.warn("[SACastSystem] 施法者节点无效"); return; } // 获取场景节点 const parent = caster.node.parent; if (!parent) { console.warn("[SACastSystem] 场景节点无效"); return; } // 获取目标位置 const targets = this.sTargets(caster); if (targets.length === 0) { 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); } /** * 选择目标位置 */ private sTargets(caster: HeroViewComp): Vec3[] { // 简化版:选择最前方的敌人 const targets: Vec3[] = []; // 这里可以调用 SkillConComp 的目标选择逻辑 // 暂时返回默认位置 if (caster == null) return targets; if (caster.ent == null) return targets; const heroAttrs = caster.ent.get(HeroAttrsComp); const fac = heroAttrs?.fac ?? 0; const defaultX = fac === 0 ? 400 : -400; targets.push(v3(defaultX, 0, 0)); 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 enemyPositions = this.findNearbyEnemies(caster, heroAttrs.fac, config.range || 300); // 选择最多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 sHealTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] { const targets: Vec3[] = []; const heroAttrs = caster.ent.get(HeroAttrsComp); if (!heroAttrs) return targets; // 寻找血量最低的友军 const allyPositions = this.findLowHealthAllies(caster, heroAttrs.fac, config.range || 200); for (let i = 0; i < Math.min(maxTargets, allyPositions.length); i++) { targets.push(allyPositions[i]); } // 如果没有找到友军,治疗自己 if (targets.length === 0 && caster.node) { targets.push(caster.node.position.clone()); } return targets; } /** * 选择BUFF技能目标 */ private sBuffTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] { // BUFF技能通常施放在自己或友军身上 return this.sHealTargets(caster, config, maxTargets); } /** * 选择DEBUFF技能目标 */ private sDebuffTargets(caster: HeroViewComp, config: any, maxTargets: number): Vec3[] { // DEBUFF技能通常施放在敌人身上 return this.sDamageTargets(caster, config, maxTargets); } /** * 选择默认目标 */ private sDefaultTargets(caster: HeroViewComp, faction: number): Vec3[] { const targets: Vec3[] = []; const defaultX = faction === 0 ? 400 : -400; targets.push(v3(defaultX, 0, 0)); return targets; } /** * 查找附近的敌人 */ private findNearbyEnemies(caster: HeroViewComp, faction: number, range: number): Vec3[] { // 简化实现,实际应该查询ECS中的敌方实体 const enemies: Vec3[] = []; // 模拟敌人位置 const enemyX = faction === 0 ? 300 : -300; enemies.push(v3(enemyX, 0, 0)); enemies.push(v3(enemyX + 50, 20, 0)); return enemies; } /** * 查找血量低的友军 */ private findLowHealthAllies(caster: HeroViewComp, faction: number, range: number): Vec3[] { // 简化实现,实际应该查询ECS中的友方实体并按血量排序 const allies: Vec3[] = []; // 如果自己血量低,优先治疗自己 return allies; } /** * 检查技能攻击范围内是否有敌人 */ 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; } }