Files
pixelheros/assets/script/game/hero/SACastSystem.ts
panw 6df4abadd1 feat(战斗系统): 添加必杀技能类型并优化天赋触发逻辑
- 在TriType枚举中添加MAX类型用于必杀技能触发
- 重构SACastSystem中的技能处理逻辑,将普通攻击、技能和必杀技能分开处理
- 优化天赋触发条件判断,确保不同类型技能触发正确的天赋效果
- 调整技能动画播放和伤害计算逻辑,使风怒和双技能天赋能正确生效
2025-11-24 15:52:28 +08:00

353 lines
13 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, 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);
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets,ext_dmg);
}, delay);
//风怒wfuny 只针对 普通攻击起效
if (heroAttrs.useCountTal(TalEffet.WFUNY)){
let ext2_dmg = heroAttrs.useCountValTal(TalEffet.ATK_DMG);
let delay = 0.3
heroView.playSkillEffect(s_uuid);
//需要再添加 风怒动画
heroView.scheduleOnce(() => {
this.createSkill(s_uuid, heroView,targets,ext2_dmg);
},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) {
// 检查节点有效性
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>(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);
}
/**
* 选择目标位置
*/
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;
}
}