Files
pixelheros/assets/script/game/hero/SCastSystem.ts
panw d5e03d7856 refactor(skill): 移除技能配置中未使用的cd和t_num字段
清理技能配置接口和实现代码,删除已不再使用的冷却时间(cd)和目标数量(t_num)字段。
在SCastSystem中,将目标数量计算简化为固定使用最小值1,因为t_num字段已废弃。
同时移除SkillView中已注释掉的旧逻辑代码。
2026-03-12 16:27:01 +08:00

158 lines
6.7 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 } from "cc";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp";
import { BuffsList, SkillConfig, SkillSet, SType, TGroup } from "../common/config/SkillSet";
import { Skill } from "../skill/Skill";
import { smc } from "../common/SingletonModuleComp";
import { GameConst } from "../common/config/GameConst";
import { mLogger } from "../common/Logger";
/**
* ==================== 自动施法系统 ====================
*
* 职责:
* 1. 检测可施放的技能
* 2. 根据策略自动施法AI
* 3. 选择目标
* 4. 添加施法请求标记
*
* 设计理念:
* - 负责"何时施法"的决策
* - 通过添加 CSRequestComp 触发施法
* - 可被玩家输入系统或AI系统复用
* - 支持多种AI策略
*/
@ecs.register('SCastSystem')
export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
debugMode: boolean = false; // 是否启用调试模式
filter(): ecs.IMatcher {
return ecs.allOf(HeroAttrsComp, HeroViewComp);
}
update(e: ecs.Entity): void {
if(!smc.mission.play ) return;
if(smc.mission.pause) return
const heroAttrs = e.get(HeroAttrsComp);
const heroView = e.get(HeroViewComp);
if (!heroAttrs || !heroView || !heroView.node) return;
if (heroAttrs.is_dead || heroAttrs.is_reviving || heroAttrs.isStun() || heroAttrs.isFrost()) return;
heroAttrs.updateCD(this.dt);
if (!heroAttrs.is_atking) return;
const castSkillId = this.pickCastSkill(heroAttrs, heroView);
if (castSkillId === 0) return;
this.castSkill(e, castSkillId, heroAttrs, heroView);
}
private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): number {
const skillCandidates = [heroAttrs.skill_id, heroAttrs.atk_id];
for (const s_uuid of skillCandidates) {
if (!s_uuid) continue;
const config = SkillSet[s_uuid];
if (!config) continue;
const isMainSkill = s_uuid === heroAttrs.skill_id;
if (isMainSkill && !heroAttrs.can_skill) continue;
if (!isMainSkill && !heroAttrs.can_atk) continue;
const targets = this.findTargets(heroView, heroAttrs, config);
if (targets.length === 0) continue;
return s_uuid;
}
return 0;
}
private castSkill(entity: ecs.Entity, s_uuid: number, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) {
const config = SkillSet[s_uuid];
if (!config) return;
const targets = this.findTargets(heroView, heroAttrs, config);
if (targets.length === 0) return;
heroView.playSkillEffect(s_uuid);
const isMainSkill = s_uuid === heroAttrs.skill_id;
const delay = GameConst.Battle.SKILL_CAST_DELAY;
heroView.scheduleOnce(() => {
if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return;
if (config.SType === SType.damage) {
this.createSkillEntity(s_uuid, heroView, targets[0].node.position);
} else {
this.applySupportSkill(entity, config, targets);
}
}, delay);
if (isMainSkill) {
heroAttrs.triggerSkillCD();
} else {
heroAttrs.triggerAtkCD();
}
}
private createSkillEntity(s_uuid: number, caster: HeroViewComp, targetPos: Vec3) {
if (!caster.node || !caster.node.isValid) return;
const parent = caster.node.parent;
if (!parent) return;
const skill = ecs.getEntity<Skill>(Skill);
skill.load(caster.node.position.clone(), parent, s_uuid, targetPos.clone(), caster, 0);
}
private applySupportSkill(casterEntity: ecs.Entity, config: SkillConfig, targets: HeroViewComp[]) {
const casterAttrs = casterEntity.get(HeroAttrsComp);
if (!casterAttrs) return;
const ratio = Number(config.ap ?? 0) / 100;
for (const target of targets) {
if (!target.ent) continue;
const model = target.ent.get(HeroAttrsComp);
if (!model || model.is_dead) continue;
if (config.SType === SType.heal) {
const amount = model.hp_max * ratio;
model.add_hp(amount, true);
target.health(amount);
continue;
}
if (config.SType === SType.shield) {
const amount = model.hp_max * ratio;
model.shield_max = Math.max(model.shield_max, amount);
model.add_shield(amount, true);
continue;
}
if (config.SType === SType.buff) {
for (const buffId of config.buffs) {
const buffConf = BuffsList[buffId];
if (buffConf) {
model.addBuff(buffConf);
}
}
}
}
mLogger.log(this.debugMode, "SCastSystem", `[SCastSystem] ${casterAttrs.hero_name} 施放 ${config.name}`);
}
private findTargets(caster: HeroViewComp, casterAttrs: HeroAttrsComp, config: SkillConfig): HeroViewComp[] {
const range = casterAttrs.getCachedMaxSkillDistance() || GameConst.Battle.DEFAULT_SEARCH_RANGE;
const isEnemy = config.TGroup === TGroup.Enemy;
const isSelf = config.TGroup === TGroup.Self;
const isTeam = config.TGroup === TGroup.Team || config.TGroup === TGroup.Ally;
const isAll = config.TGroup === TGroup.All;
if (isSelf) return [caster];
const currentPos = caster.node.position;
const list: { view: HeroViewComp; dis: number; lane: number }[] = [];
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(ent => {
const targetAttrs = ent.get(HeroAttrsComp);
const targetView = ent.get(HeroViewComp);
if (!targetAttrs || !targetView || !targetView.node || targetAttrs.is_dead) return;
if (targetView === caster) return;
const isSameFac = targetAttrs.fac === casterAttrs.fac;
if (isEnemy && isSameFac) return;
if (isTeam && !isSameFac) return;
if (!isEnemy && !isTeam && !isAll) return;
const dis = Math.abs(currentPos.x - targetView.node.position.x);
if (dis > range) return;
const lane = Math.abs(currentPos.y - targetView.node.position.y);
list.push({ view: targetView, dis, lane });
});
list.sort((a, b) => {
if (a.lane !== b.lane) return a.lane - b.lane;
return a.dis - b.dis;
});
const maxTargets = Math.max(GameConst.Skill.MIN_TARGET_COUNT, 1);
return list.slice(0, maxTargets).map(item => item.view);
}
}