feat(战斗): 优化自动施法目标选择逻辑
- 在 HeroAttrsComp 中新增 combat_target_eid 和 enemy_in_cast_range 字段,用于跟踪当前战斗目标 - 修改 MoveSystem 在移动时同步更新战斗目标状态,并清理无效目标 - 重构 SCastSystem 的自动施法逻辑,优先使用已锁定的战斗目标而非重新搜索 - 调整技能 6005 和 6006 的 hit_count 参数,分别改为 2 和 3 次打击 - 为友方技能施法添加事件派发机制,通知其他系统技能释放
This commit is contained in:
@@ -2,10 +2,12 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
|
||||
import { Vec3 } from "cc";
|
||||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||||
import { HeroViewComp } from "./HeroViewComp";
|
||||
import { BuffsList, SkillConfig, SkillKind, SkillSet, TGroup, TType } from "../common/config/SkillSet";
|
||||
import { BuffsList, SkillConfig, SkillKind, SkillSet, TGroup } from "../common/config/SkillSet";
|
||||
import { Skill } from "../skill/Skill";
|
||||
import { smc } from "../common/SingletonModuleComp";
|
||||
import { GameConst } from "../common/config/GameConst";
|
||||
import { oops } from "db://oops-framework/core/Oops";
|
||||
import { GameEvent } from "../common/config/GameEvent";
|
||||
|
||||
/**
|
||||
* ==================== 自动施法系统 ====================
|
||||
@@ -25,8 +27,7 @@ import { GameConst } from "../common/config/GameConst";
|
||||
@ecs.register('SCastSystem')
|
||||
export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||
debugMode: boolean = false; // 是否启用调试模式
|
||||
private readonly emptyCastPlan = { skillId: 0, targets: [] as HeroViewComp[] };
|
||||
private readonly emptyCandidates: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[] = [];
|
||||
private readonly emptyCastPlan = { skillId: 0, targets: [] as HeroViewComp[], isFriendly: false };
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(HeroAttrsComp, HeroViewComp);
|
||||
@@ -46,9 +47,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
this.castSkill(e, castPlan, heroAttrs, heroView);
|
||||
}
|
||||
|
||||
private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): { skillId: number; targets: HeroViewComp[] } {
|
||||
const range = heroAttrs.getCachedMaxSkillDistance() || GameConst.Battle.DEFAULT_SEARCH_RANGE;
|
||||
const candidates = this.collectCandidates(heroView, heroAttrs, range);
|
||||
private pickCastSkill(heroAttrs: HeroAttrsComp, heroView: HeroViewComp): { skillId: number; targets: HeroViewComp[]; isFriendly: boolean } {
|
||||
const skillCandidates = [heroAttrs.skill_id, heroAttrs.atk_id];
|
||||
for (const s_uuid of skillCandidates) {
|
||||
if (!s_uuid) continue;
|
||||
@@ -57,14 +56,18 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
const isMainSkill = s_uuid === heroAttrs.skill_id;
|
||||
if (isMainSkill && !heroAttrs.can_skill) continue;
|
||||
if (!isMainSkill && !heroAttrs.can_atk) continue;
|
||||
const targets = this.findTargetsByCandidates(heroView, config, candidates);
|
||||
if (targets.length === 0) continue;
|
||||
return { skillId: s_uuid, targets };
|
||||
if (this.isFriendlySkill(config.TGroup)) {
|
||||
return { skillId: s_uuid, targets: [heroView], isFriendly: true };
|
||||
}
|
||||
if (!heroAttrs.enemy_in_cast_range) continue;
|
||||
const target = this.resolveCombatTarget(heroAttrs);
|
||||
if (!target) continue;
|
||||
return { skillId: s_uuid, targets: [target], isFriendly: false };
|
||||
}
|
||||
return this.emptyCastPlan;
|
||||
}
|
||||
|
||||
private castSkill(entity: ecs.Entity, castPlan: { skillId: number; targets: HeroViewComp[] }, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) {
|
||||
private castSkill(entity: ecs.Entity, castPlan: { skillId: number; targets: HeroViewComp[]; isFriendly: boolean }, heroAttrs: HeroAttrsComp, heroView: HeroViewComp) {
|
||||
const s_uuid = castPlan.skillId;
|
||||
const config = SkillSet[s_uuid];
|
||||
if (!config) return;
|
||||
@@ -78,6 +81,15 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
|
||||
heroView.scheduleOnce(() => {
|
||||
if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return;
|
||||
if (castPlan.isFriendly) {
|
||||
oops.message.dispatchEvent(GameEvent.CastHeroSkill, {
|
||||
casterEid: entity.eid,
|
||||
s_uuid,
|
||||
fac: heroAttrs.fac,
|
||||
targetEids: castPlan.targets.map(target => target.ent?.eid).filter((eid): eid is number => typeof eid === "number")
|
||||
});
|
||||
return;
|
||||
}
|
||||
const validTargets = this.filterValidTargets(castPlan.targets);
|
||||
if (validTargets.length === 0) return;
|
||||
this.applyPrimaryEffect(entity, s_uuid, config, heroView, validTargets);
|
||||
@@ -153,59 +165,19 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
});
|
||||
}
|
||||
|
||||
private collectCandidates(caster: HeroViewComp, casterAttrs: HeroAttrsComp, range: number): { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[] {
|
||||
if (!caster || !caster.node || !caster.node.isValid) return this.emptyCandidates;
|
||||
const currentPos = caster.node.position;
|
||||
const list: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[] = [];
|
||||
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 dis = Math.abs(currentPos.x - targetView.node.position.x);
|
||||
if (dis > range) return;
|
||||
const lane = Math.abs(currentPos.y - targetView.node.position.y);
|
||||
const isSameFac = targetAttrs.fac === casterAttrs.fac;
|
||||
list.push({ view: targetView, attrs: targetAttrs, dis, lane, isSameFac });
|
||||
});
|
||||
return list;
|
||||
private isFriendlySkill(group: TGroup): boolean {
|
||||
return group === TGroup.Self || group === TGroup.Team || group === TGroup.Ally;
|
||||
}
|
||||
|
||||
private findTargetsByCandidates(caster: HeroViewComp, config: SkillConfig, candidates: { view: HeroViewComp; attrs: HeroAttrsComp; dis: number; lane: number; isSameFac: boolean }[]): HeroViewComp[] {
|
||||
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];
|
||||
if (!isEnemy && !isTeam && !isAll) return this.emptyCastPlan.targets;
|
||||
const list = candidates.filter(item => {
|
||||
if (isEnemy && item.isSameFac) return false;
|
||||
if (isTeam && !item.isSameFac) return false;
|
||||
return true;
|
||||
});
|
||||
list.sort((a, b) => {
|
||||
const type = config.TType ?? TType.Frontline;
|
||||
switch (type) {
|
||||
case TType.Backline:
|
||||
if (a.lane !== b.lane) return a.lane - b.lane;
|
||||
return b.dis - a.dis;
|
||||
case TType.LowestHP:
|
||||
if (a.attrs.hp !== b.attrs.hp) return a.attrs.hp - b.attrs.hp;
|
||||
return a.dis - b.dis;
|
||||
case TType.HighestHP:
|
||||
if (a.attrs.hp !== b.attrs.hp) return b.attrs.hp - a.attrs.hp;
|
||||
return a.dis - b.dis;
|
||||
case TType.HighestAP:
|
||||
if (a.attrs.ap !== b.attrs.ap) return b.attrs.ap - a.attrs.ap;
|
||||
return a.dis - b.dis;
|
||||
case TType.Frontline:
|
||||
default:
|
||||
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);
|
||||
private resolveCombatTarget(heroAttrs: HeroAttrsComp): HeroViewComp | null {
|
||||
if (heroAttrs.combat_target_eid <= 0) return null;
|
||||
const targetEntity = ecs.getEntityByEid(heroAttrs.combat_target_eid);
|
||||
if (!targetEntity) return null;
|
||||
const targetAttrs = targetEntity.get(HeroAttrsComp);
|
||||
const targetView = targetEntity.get(HeroViewComp);
|
||||
if (!targetAttrs || !targetView || !targetView.node || !targetView.node.isValid) return null;
|
||||
if (targetAttrs.is_dead || targetAttrs.is_reviving) return null;
|
||||
if (targetAttrs.fac === heroAttrs.fac) return null;
|
||||
return targetView;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user