import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { Vec3, v3 } from "cc"; import { HeroSkillsComp } from "./HeroSkills"; import { HeroAttrsComp } from "./HeroAttrsComp"; import { HeroViewComp } from "./HeroViewComp"; import { SkillSet, SType } from "../common/config/SkillSet"; import { SkillEnt } from "../skill/SkillEnt"; import { smc } from "../common/SingletonModuleComp"; /** * ==================== 施法请求标记组件 ==================== * * 用途: * - 标记角色想要施放某个技能 * - 由外部(如AI系统、玩家输入)添加 * - 施法系统处理后自动移除 */ @ecs.register('CastSkillRequest') export class CastSkillRequestComp extends ecs.Comp { /** 技能索引(在 HeroSkillsComp.skills 中的位置) */ skillIndex: number = 0; /** 目标位置数组(由请求者提供) */ targetPositions: Vec3[] = []; reset() { this.skillIndex = 0; this.targetPositions = []; } } /** * ==================== 技能施法系统 ==================== * * 职责: * 1. 监听 CastSkillRequestComp 标记组件 * 2. 检查施法条件(CD、MP、状态) * 3. 扣除资源(MP) * 4. 创建技能实体 * 5. 触发施法动画 * 6. 移除请求标记 * * 设计理念: * - 使用标记组件驱动,符合 ECS 理念 * - 施法检查与执行分离 * - 自动处理资源消耗和CD重置 */ export class SkillCastSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { /** * 过滤器:拥有技能数据 + 施法请求的实体 */ filter(): ecs.IMatcher { return ecs.allOf(HeroSkillsComp, HeroAttrsComp, CastSkillRequestComp); } /** * 实体进入时触发(即请求施法时) */ entityEnter(e: ecs.Entity): void { const skillsData = e.get(HeroSkillsComp); const heroModel = e.get(HeroAttrsComp); const request = e.get(CastSkillRequestComp); const heroView = e.get(HeroViewComp); // 1. 验证数据完整性 if (!skillsData || !heroModel || !request || !heroView) { console.warn("[SkillCastSystem] 数据不完整,取消施法"); e.remove(CastSkillRequestComp); return; } // 2. 获取技能数据 const skill = skillsData.getSkill(request.skillIndex); if (!skill) { console.warn(`[SkillCastSystem] 技能索引无效: ${request.skillIndex}`); e.remove(CastSkillRequestComp); return; } // 3. 检查施法条件 if (!this.checkCastConditions(skillsData, heroModel, request.skillIndex)) { e.remove(CastSkillRequestComp); return; } // 4. 执行施法 this.executeCast(e, skill, request.targetPositions, heroView); // 5. 扣除资源和重置CD heroModel.mp -= skill.cost; skillsData.resetCD(request.skillIndex); // 6. 移除请求标记 e.remove(CastSkillRequestComp); } /** * 检查施法条件 */ private checkCastConditions(skillsData: HeroSkillsComp, heroModel: HeroAttrsComp, skillIndex: number): boolean { // 检查角色状态 if (heroModel.is_dead) { return false; } // 检查控制状态(眩晕、冰冻) if (heroModel.isStun() || heroModel.isFrost()) { return false; } // 检查CD和MP if (!skillsData.canCast(skillIndex, heroModel.mp)) { return false; } return true; } /** * 执行施法 */ private executeCast(casterEntity: ecs.Entity, skill: any, targetPositions: Vec3[], heroView: HeroViewComp) { const config = SkillSet[skill.uuid]; if (!config) { console.error("[SkillCastSystem] 技能配置不存在:", skill.uuid); return; } // 1. 播放施法动画 heroView.playSkillEffect(skill.uuid); // 2. 延迟创建技能实体(等待动画) const delay = config.with ?? 0.3; // 施法前摇时间 heroView.scheduleOnce(() => { this.createSkillEntity(skill.uuid, heroView, targetPositions); }, delay); const heroModel = casterEntity.get(HeroAttrsComp); console.log(`[SkillCastSystem] ${heroModel?.hero_name ?? '未知'} 施放技能: ${config.name}`); } /** * 创建技能实体 */ private createSkillEntity(skillId: number, caster: HeroViewComp, targetPositions: Vec3[]) { // 检查节点有效性 if (!caster.node || !caster.node.isValid) { console.warn("[SkillCastSystem] 施法者节点无效"); return; } // 获取场景节点 const parent = caster.node.parent; if (!parent) { console.warn("[SkillCastSystem] 场景节点无效"); return; } // ✅ 使用现有的 SkillEnt 创建技能 const skillEnt = ecs.getEntity(SkillEnt); skillEnt.load( caster.node.position, // 起始位置 parent, // 父节点 skillId, // 技能ID targetPositions, // 目标位置数组 caster, // 施法者 0 // 额外伤害(暂时为0) ); } } /** * ==================== 技能CD更新系统 ==================== * * 职责: * 1. 每帧更新所有角色的技能CD * 2. 自动递减CD时间 * * 设计理念: * - 独立的CD管理系统 * - 只负责时间递减,不处理施法逻辑 */ export class SkillCDSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { filter(): ecs.IMatcher { return ecs.allOf(HeroSkillsComp); } update(e: ecs.Entity): void { const skillsData = e.get(HeroSkillsComp); if (!skillsData) return; // 更新所有技能CD skillsData.updateCDs(this.dt); } } /** * ==================== 自动施法系统 ==================== * * 职责: * 1. 检测可施放的技能 * 2. 根据策略自动施法(AI) * 3. 选择目标 * 4. 添加施法请求标记 * * 设计理念: * - 负责"何时施法"的决策 * - 通过添加 CastSkillRequestComp 触发施法 * - 可被玩家输入系统或AI系统复用 */ export class SkillAutocastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate { filter(): ecs.IMatcher { return ecs.allOf(HeroSkillsComp, HeroAttrsComp, HeroViewComp); } update(e: ecs.Entity): void { const skillsData = e.get(HeroSkillsComp); const heroModel = e.get(HeroAttrsComp); const heroView = e.get(HeroViewComp); if (!skillsData || !heroModel || !heroView) return; // 检查基本条件 if (heroModel.is_dead || heroModel.isStun() || heroModel.isFrost()) return; // 检查是否正在攻击(只有攻击时才释放技能) if (!heroView.is_atking) return; // 获取所有可施放的技能 const readySkills = skillsData.getReadySkills(heroModel.mp); if (readySkills.length === 0) return; // 选择第一个可施放的伤害技能 for (const skillIndex of readySkills) { const skill = skillsData.getSkill(skillIndex); if (!skill) continue; const config = SkillSet[skill.uuid]; if (!config || config.SType !== SType.damage) continue; // ✅ 添加施法请求标记组件 const request = e.add(CastSkillRequestComp) as CastSkillRequestComp; request.skillIndex = skillIndex; request.targetPositions = this.selectTargets(heroView); // 一次只施放一个技能 break; } } /** * 选择目标位置 */ private selectTargets(caster: HeroViewComp): Vec3[] { // 简化版:选择最前方的敌人 const targets: Vec3[] = []; // 这里可以调用 SkillConComp 的目标选择逻辑 // 暂时返回默认位置 const heroModel = caster.ent.get(HeroAttrsComp); const fac = heroModel?.fac ?? 0; const defaultX = fac === 0 ? 400 : -400; targets.push(v3(defaultX, 0, 0)); return targets; } }