feat(战斗): 优化自动施法目标选择逻辑
- 在 HeroAttrsComp 中新增 combat_target_eid 和 enemy_in_cast_range 字段,用于跟踪当前战斗目标 - 修改 MoveSystem 在移动时同步更新战斗目标状态,并清理无效目标 - 重构 SCastSystem 的自动施法逻辑,优先使用已锁定的战斗目标而非重新搜索 - 调整技能 6005 和 6006 的 hit_count 参数,分别改为 2 和 3 次打击 - 为友方技能施法添加事件派发机制,通知其他系统技能释放
This commit is contained in:
@@ -211,13 +211,13 @@ export const SkillSet: Record<number, SkillConfig> = {
|
||||
},
|
||||
6005: {
|
||||
uuid:6005,name:"蓝箭",sp_name:"arrow_blue",icon:"1135",TGroup:TGroup.Enemy,TType:TType.Frontline,readyAnm:"",endAnm:"",act:"atk",DTType:DTType.single,
|
||||
ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,
|
||||
ap:100,hit_count:2,hitcd:0.2,speed:720,with:0,
|
||||
ready:0,EAnm:0,DAnm:9001,RType:RType.linear,EType:EType.collision,
|
||||
buffs:[],debuffs:[],info:"对前方单个目标造成100%攻击的伤害",
|
||||
},
|
||||
6006: {
|
||||
uuid:6006,name:"绿箭",sp_name:"arrow_green",icon:"1135",TGroup:TGroup.Enemy,TType:TType.Frontline,readyAnm:"",endAnm:"",act:"atk",DTType:DTType.single,
|
||||
ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,
|
||||
ap:100,hit_count:3,hitcd:0.2,speed:720,with:0,
|
||||
ready:0,EAnm:0,DAnm:9001,RType:RType.linear,EType:EType.collision,
|
||||
buffs:[],debuffs:[],info:"对前方单个目标造成100%攻击的伤害",
|
||||
},
|
||||
|
||||
@@ -122,6 +122,8 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
skill_id:number=0; //技能攻击技能id
|
||||
can_atk=false
|
||||
can_skill=false
|
||||
combat_target_eid: number = -1;
|
||||
enemy_in_cast_range: boolean = false;
|
||||
start(){
|
||||
}
|
||||
// ==================== BUFF 系统初始化 ====================
|
||||
@@ -548,6 +550,8 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
this.skill_id = 0;
|
||||
this.can_atk=false
|
||||
this.can_skill=false
|
||||
this.combat_target_eid = -1;
|
||||
this.enemy_in_cast_range = false;
|
||||
// 重置脏标签
|
||||
this.dirty_hp = false;
|
||||
this.dirty_shield = false;
|
||||
|
||||
@@ -88,10 +88,12 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
if (model.fac !== FacSet.HERO && model.fac !== FacSet.MON) return;
|
||||
if (!move.moving) return;
|
||||
if (model.fac === FacSet.MON && smc.mission.stop_mon_action) {
|
||||
this.clearCombatTarget(model);
|
||||
view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
if (model.is_stop || model.is_dead || model.is_reviving || model.in_stun || model.in_frost) {
|
||||
this.clearCombatTarget(model);
|
||||
if (!model.is_reviving) view.status_change("idle");
|
||||
return;
|
||||
}
|
||||
@@ -103,13 +105,46 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
const nearestEnemy = this.findNearestEnemy(e);
|
||||
if (nearestEnemy) {
|
||||
this.processCombatLogic(e, move, view, model, nearestEnemy);
|
||||
this.syncCombatTarget(model, view, nearestEnemy);
|
||||
} else {
|
||||
this.clearCombatTarget(model);
|
||||
move.targetY = 0;
|
||||
this.processReturnFormation(e, move, view, model);
|
||||
model.is_atking = false;
|
||||
}
|
||||
}
|
||||
|
||||
private clearCombatTarget(model: HeroAttrsComp): void {
|
||||
model.combat_target_eid = -1;
|
||||
model.enemy_in_cast_range = false;
|
||||
}
|
||||
|
||||
private syncCombatTarget(model: HeroAttrsComp, selfView: HeroViewComp, enemyView: HeroViewComp): void {
|
||||
if (!enemyView || !enemyView.node || !enemyView.ent) {
|
||||
this.clearCombatTarget(model);
|
||||
return;
|
||||
}
|
||||
const enemyAttrs = enemyView.ent.get(HeroAttrsComp);
|
||||
if (!enemyAttrs || enemyAttrs.is_dead || enemyAttrs.is_reviving || enemyAttrs.fac === model.fac) {
|
||||
this.clearCombatTarget(model);
|
||||
return;
|
||||
}
|
||||
model.combat_target_eid = enemyView.ent.eid;
|
||||
model.enemy_in_cast_range = this.isEnemyInAttackRange(model, selfView.node.position.x, enemyView.node.position.x);
|
||||
}
|
||||
|
||||
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
|
||||
const dist = Math.abs(selfX - enemyX);
|
||||
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
||||
if (rangeType === HType.Melee) return dist <= this.meleeAttackRange;
|
||||
if (rangeType === HType.Long) {
|
||||
const [, maxRange] = this.resolveCombatRange(model, 360, 720);
|
||||
return dist <= maxRange;
|
||||
}
|
||||
const [, maxRange] = this.resolveCombatRange(model, 120, 360);
|
||||
return dist <= maxRange;
|
||||
}
|
||||
|
||||
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
||||
switch (rangeType) {
|
||||
|
||||
@@ -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