Files
pixelheros/assets/script/game/hero/SACastSystem.ts
walkpan e9abbefe9d fix(英雄技能系统): 修复怪物释放技能时消耗蓝量的问题
修改技能释放逻辑,当单位是怪物时使用无限蓝量进行检查且不扣除蓝量
2026-01-02 15:23:25 +08:00

611 lines
22 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, TGroup } 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, FacSet } from "../common/config/GameSet";
import { Attrs } from "../common/config/HeroAttrs";
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 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;
// 🔥 怪物不消耗蓝使用Infinity作为mp参数
const mpForCheck = heroAttrs.fac === FacSet.MON ? Infinity : heroAttrs.mp;
const readySkills = skills.getReadySkills(mpForCheck);
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) continue;
// 根据技能类型检查目标
if (config.SType === SType.damage) {
if (!this.hasEnemyInSkillRange(heroView, heroAttrs, skill.dis)) continue;
} else if (config.SType === SType.heal || config.SType === SType.shield) {
if (!this.hasTeamInSkillRange(heroView, heroAttrs, skill.dis)) continue;
} else if (config.SType === SType.buff) {
if (!this.hasBuffTarget(heroView, heroAttrs, skill.dis, config.TGroup)) 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) {
// 🔥 怪物不消耗蓝
if (heroAttrs.fac !== FacSet.MON) {
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);
}
/**********************天赋处理*************************************************************************/
// 根据技能类型执行不同逻辑
if (config.SType === SType.heal) {
return this.executeHealSkill(casterEntity, s_uuid, heroView, hset);
} else if (config.SType === SType.shield) {
return this.executeShieldSkill(casterEntity, s_uuid, heroView, hset);
} else if (config.SType === SType.buff) {
return this.executeBuffSkill(casterEntity, s_uuid, heroView, hset);
}
// 获取目标位置(伤害技能)
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;
}
/**
* 检查技能范围内是否有友军
*/
private hasTeamInSkillRange(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;
}
/**
* 检查Buff技能是否有目标
*/
private hasBuffTarget(heroView: HeroViewComp, heroAttrs: HeroAttrsComp, skillDistance: number, tGroup: TGroup): boolean {
if (tGroup === TGroup.Self) return true; // 自身Buff总是可以释放
// 如果是团队Buff检查范围内是否有队友
if (tGroup === TGroup.Team || tGroup === TGroup.Ally) {
return this.hasTeamInSkillRange(heroView, heroAttrs, skillDistance);
}
return false;
}
/**
* 执行Buff技能
*/
private executeBuffSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
const hAttrsCom = casterEntity.get(HeroAttrsComp);
const config = SkillSet[s_uuid];
if (!config || !config.buffs || config.buffs.length === 0) return false;
const targets = this.sBuffTargets(casterEntity, heroView, hAttrsCom, config);
if (targets.length === 0) return false;
const delay = 0.3;
heroView.scheduleOnce(() => {
for (const targetEntity of targets) {
const targetAttrs = targetEntity.get(HeroAttrsComp);
if (!targetAttrs) continue;
// 应用所有配置的Buff
for (const buffConf of config.buffs) {
// 检查概率
if (buffConf.chance >= 1 || Math.random() < buffConf.chance) {
targetAttrs.addBuff(buffConf);
console.log(`[SACastSystem] Buff生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, Buff类型=${buffConf.buff}, 值=${buffConf.value}`);
}
}
}
}, delay);
return true;
}
/**
* 选择Buff目标
*/
private sBuffTargets(casterEntity: ecs.Entity, casterView: HeroViewComp, heroAttrs: HeroAttrsComp, config: any): ecs.Entity[] {
const targets: ecs.Entity[] = [];
const tGroup = config.TGroup;
// 1. 自身
if (tGroup === TGroup.Self) {
targets.push(casterEntity);
return targets;
}
// 2. 团队/友军
if (tGroup === TGroup.Team || tGroup === TGroup.Ally) {
const maxTargets = Math.max(1, Number(config.t_num ?? 1));
const range = Number(config.dis ?? 300);
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return;
if (model.fac !== heroAttrs.fac) return; // 必须是同阵营
if (model.is_dead) return;
const distance = Math.abs(casterView.node.position.x - view.node.position.x);
if (distance <= range) {
targets.push(e);
}
});
return targets.slice(0, maxTargets);
}
return targets;
}
/**
* 执行治疗技能
*/
private executeHealSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
const hAttrsCom = casterEntity.get(HeroAttrsComp);
const config = SkillSet[s_uuid];
if (!config) return false;
const targets = this.sHealTargets(heroView, hAttrsCom, config);
if (targets.length === 0) return false;
const healAmount = config.ap * hAttrsCom.Attrs[Attrs.HP_MAX]/100;
const delay = 0.3;
heroView.scheduleOnce(() => {
for (const targetEntity of targets) {
const targetAttrs = targetEntity.get(HeroAttrsComp);
const targetView = targetEntity.get(HeroViewComp);
if (!targetAttrs || !targetView) continue;
targetAttrs.add_hp(healAmount, true);
targetView.health(healAmount);
console.log(`[SACastSystem] 治疗生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, 治疗量=${healAmount}`);
}
}, delay);
return true;
}
/**
* 执行护盾技能
*/
private executeShieldSkill(casterEntity: ecs.Entity, s_uuid: number, heroView: HeroViewComp, hset: HSSet): boolean {
const hAttrsCom = casterEntity.get(HeroAttrsComp);
const config = SkillSet[s_uuid];
if (!config) return false;
const targets = this.sShieldTargets(heroView, hAttrsCom, config);
if (targets.length === 0) return false;
const shieldAmount = config.ap * hAttrsCom.Attrs[Attrs.HP_MAX]/100;
const delay = 0.3;
heroView.scheduleOnce(() => {
for (const targetEntity of targets) {
const targetAttrs = targetEntity.get(HeroAttrsComp);
const targetView = targetEntity.get(HeroViewComp);
if (!targetAttrs || !targetView) continue;
targetAttrs.add_shield(shieldAmount, true);
targetView.add_shield(shieldAmount);
console.log(`[SACastSystem] 护盾生效: 施法者=${casterEntity.get(HeroAttrsComp)?.hero_name}, 技能=${config.name}, 目标=${targetAttrs.hero_name}, 护盾量=${shieldAmount}`);
}
}, delay);
return true;
}
/**
* 选择治疗目标
*/
private sHealTargets(caster: HeroViewComp, heroAttrs: HeroAttrsComp, config: any): ecs.Entity[] {
const targets: ecs.Entity[] = [];
const maxTargets = Math.max(1, Number(config.t_num ?? 1));
const range = Number(config.dis ?? 300);
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return;
if (model.fac !== heroAttrs.fac) return;
if (model.is_dead) return;
const distance = Math.abs(caster.node.position.x - view.node.position.x);
if (distance <= range) {
targets.push(e);
}
});
targets.sort((a, b) => {
const attrsA = a.get(HeroAttrsComp);
const attrsB = b.get(HeroAttrsComp);
if (!attrsA || !attrsB) return 0;
return attrsA.hp - attrsB.hp;
});
return targets.slice(0, maxTargets);
}
/**
* 选择护盾目标
*/
private sShieldTargets(caster: HeroViewComp, heroAttrs: HeroAttrsComp, config: any): ecs.Entity[] {
const targets: ecs.Entity[] = [];
const maxTargets = Math.max(1, Number(config.t_num ?? 1));
const range = Number(config.dis ?? 300);
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
const model = e.get(HeroAttrsComp);
const view = e.get(HeroViewComp);
if (!model || !view || !view.node) return;
if (model.fac !== heroAttrs.fac) return;
if (model.is_dead) return;
const distance = Math.abs(caster.node.position.x - view.node.position.x);
if (distance <= range) {
targets.push(e);
}
});
return targets.slice(0, maxTargets);
}
/**
* 根据位置查找实体
*/
private findEntityAtPosition(pos: Vec3): ecs.Entity | null {
let foundEntity: ecs.Entity | null = null;
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
const view = e.get(HeroViewComp);
if (!view || !view.node) return false;
const distance = Vec3.distance(pos, view.node.position);
if (distance < 50) {
foundEntity = e;
return true;
}
return false;
});
return foundEntity;
}
}