dedupe AttackExecutor and RepairExecutor via executeOrbitAndAssign
The two executors were line-for-line duplicates: orbit the behavior target, then hand it to the owner's in-range modules. Both now call a templated executeOrbitAndAssign<Behavior, ModuleComponent>; the view types, iteration order, and sequence of component writes are unchanged. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GH8ZMRY3vhxxXcaUxBqxkk
This commit is contained in:
@@ -8,6 +8,7 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/ai/Centroid.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/Centroid.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ai/DeliverScrapEvaluator.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/DeliverScrapEvaluator.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ai/DeliverScrapExecutor.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/DeliverScrapExecutor.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/OrbitAndAssignExecutor.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ai/RallyEvaluator.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/RallyEvaluator.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ai/RallyExecutor.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/RallyExecutor.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ai/RepairEvaluator.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ai/RepairEvaluator.h
|
||||||
|
|||||||
@@ -2,12 +2,8 @@
|
|||||||
|
|
||||||
#include "AttackBehavior.h"
|
#include "AttackBehavior.h"
|
||||||
#include "BehaviorKind.h"
|
#include "BehaviorKind.h"
|
||||||
#include "DynamicBodyComponent.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "ModuleOwnerComponent.h"
|
#include "OrbitAndAssignExecutor.h"
|
||||||
#include "MovementIntentComponent.h"
|
|
||||||
#include "PositionComponent.h"
|
|
||||||
#include "SelectedBehaviorComponent.h"
|
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
#include "WeaponComponent.h"
|
#include "WeaponComponent.h"
|
||||||
|
|
||||||
@@ -15,55 +11,7 @@ void AttackExecutor::execute(EntityAdmin& admin)
|
|||||||
{
|
{
|
||||||
TRACE();
|
TRACE();
|
||||||
|
|
||||||
// Ships: move toward the behavior target.
|
// Orbit the attack target and hand it to every weapon that can reach it
|
||||||
admin.forEach<AttackBehavior, SelectedBehaviorComponent, PositionComponent,
|
// (REQ-SHP-ORBIT).
|
||||||
MovementIntentComponent>(
|
executeOrbitAndAssign<AttackBehavior, WeaponComponent>(admin, BehaviorKind::Attack);
|
||||||
[&](entt::entity /*e*/, const AttackBehavior& attack,
|
|
||||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
|
||||||
MovementIntentComponent& intent)
|
|
||||||
{
|
|
||||||
if (selected.winner != BehaviorKind::Attack) { return; }
|
|
||||||
if (!attack.currentTarget) { return; }
|
|
||||||
|
|
||||||
const entt::entity t = *attack.currentTarget;
|
|
||||||
QVector2D center = pos.value;
|
|
||||||
float radius = 0.0f;
|
|
||||||
QVector2D centerVelocity;
|
|
||||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
|
||||||
{
|
|
||||||
center = admin.get<PositionComponent>(t).value;
|
|
||||||
radius = attack.orbitRadius_tiles;
|
|
||||||
if (admin.hasAll<DynamicBodyComponent>(t))
|
|
||||||
{
|
|
||||||
centerVelocity = admin.get<DynamicBodyComponent>(t).velocity_tpt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
intent = MovementIntentComponent{true, center, radius, centerVelocity};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Weapons: assign the behavior target only if it is within this weapon's range.
|
|
||||||
admin.forEach<WeaponComponent, ModuleOwnerComponent>(
|
|
||||||
[&](entt::entity /*we*/, WeaponComponent& weapon, const ModuleOwnerComponent& owner)
|
|
||||||
{
|
|
||||||
if (!admin.hasAll<AttackBehavior, SelectedBehaviorComponent>(owner.owner))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const SelectedBehaviorComponent& selected =
|
|
||||||
admin.get<SelectedBehaviorComponent>(owner.owner);
|
|
||||||
if (selected.winner != BehaviorKind::Attack) { return; }
|
|
||||||
|
|
||||||
const AttackBehavior& attack = admin.get<AttackBehavior>(owner.owner);
|
|
||||||
if (!attack.currentTarget) { return; }
|
|
||||||
|
|
||||||
const entt::entity t = *attack.currentTarget;
|
|
||||||
if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t)) { return; }
|
|
||||||
|
|
||||||
const QVector2D ownerPos = admin.get<PositionComponent>(owner.owner).value;
|
|
||||||
const float dist = (admin.get<PositionComponent>(t).value - ownerPos).length();
|
|
||||||
if (dist <= weapon.range_tiles)
|
|
||||||
{
|
|
||||||
weapon.currentTarget = t;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
89
src/lib/ecs/system/ai/OrbitAndAssignExecutor.h
Normal file
89
src/lib/ecs/system/ai/OrbitAndAssignExecutor.h
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QVector2D>
|
||||||
|
|
||||||
|
#include "entt/entity/entity.hpp"
|
||||||
|
|
||||||
|
#include "BehaviorKind.h"
|
||||||
|
#include "DynamicBodyComponent.h"
|
||||||
|
#include "EntityAdmin.h"
|
||||||
|
#include "ModuleOwnerComponent.h"
|
||||||
|
#include "MovementIntentComponent.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "SelectedBehaviorComponent.h"
|
||||||
|
|
||||||
|
// Shared executor body for the behaviors that orbit a single target entity and then
|
||||||
|
// hand that target to the ship's in-range modules (REQ-SHP-ORBIT): Attack (with
|
||||||
|
// WeaponComponent) and Repair (with RepairToolComponent).
|
||||||
|
//
|
||||||
|
// Two passes, in this order — the order and the exact sequence of component writes
|
||||||
|
// are load-bearing for determinism (see the Tick Order section of
|
||||||
|
// docs/architecture.md):
|
||||||
|
// 1. Ships that have `Behavior` and won with `kind` write their MovementIntent to
|
||||||
|
// orbit the behavior's target at the behavior's orbit radius. A target that is
|
||||||
|
// gone (or has no position) degenerates to "hold position": the ship's own
|
||||||
|
// position with a zero radius.
|
||||||
|
// 2. Modules of type `ModuleComponent` whose owner won with `kind` adopt the
|
||||||
|
// behavior's target, but only when it lies within that module's own range.
|
||||||
|
// Out-of-range modules keep whatever target they already had, which
|
||||||
|
// CombatSystem/RepairSystem re-validate.
|
||||||
|
//
|
||||||
|
// `Behavior` must expose `std::optional<entt::entity> currentTarget` and
|
||||||
|
// `float orbitRadius_tiles`; `ModuleComponent` must expose `float range_tiles` and
|
||||||
|
// `std::optional<entt::entity> currentTarget`.
|
||||||
|
template <typename Behavior, typename ModuleComponent>
|
||||||
|
void executeOrbitAndAssign(EntityAdmin& admin, BehaviorKind kind)
|
||||||
|
{
|
||||||
|
// Ships: move toward the behavior target.
|
||||||
|
admin.forEach<Behavior, SelectedBehaviorComponent, PositionComponent,
|
||||||
|
MovementIntentComponent>(
|
||||||
|
[&](entt::entity /*e*/, const Behavior& behavior,
|
||||||
|
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
||||||
|
MovementIntentComponent& intent)
|
||||||
|
{
|
||||||
|
if (selected.winner != kind) { return; }
|
||||||
|
if (!behavior.currentTarget) { return; }
|
||||||
|
|
||||||
|
const entt::entity t = *behavior.currentTarget;
|
||||||
|
QVector2D center = pos.value;
|
||||||
|
float radius = 0.0f;
|
||||||
|
QVector2D centerVelocity;
|
||||||
|
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
||||||
|
{
|
||||||
|
center = admin.get<PositionComponent>(t).value;
|
||||||
|
radius = behavior.orbitRadius_tiles;
|
||||||
|
if (admin.hasAll<DynamicBodyComponent>(t))
|
||||||
|
{
|
||||||
|
centerVelocity = admin.get<DynamicBodyComponent>(t).velocity_tpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intent = MovementIntentComponent{true, center, radius, centerVelocity};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modules: assign the behavior target only if it is within this module's range.
|
||||||
|
admin.forEach<ModuleComponent, ModuleOwnerComponent>(
|
||||||
|
[&](entt::entity /*me*/, ModuleComponent& module,
|
||||||
|
const ModuleOwnerComponent& owner)
|
||||||
|
{
|
||||||
|
if (!admin.hasAll<Behavior, SelectedBehaviorComponent>(owner.owner))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const SelectedBehaviorComponent& selected =
|
||||||
|
admin.get<SelectedBehaviorComponent>(owner.owner);
|
||||||
|
if (selected.winner != kind) { return; }
|
||||||
|
|
||||||
|
const Behavior& behavior = admin.get<Behavior>(owner.owner);
|
||||||
|
if (!behavior.currentTarget) { return; }
|
||||||
|
|
||||||
|
const entt::entity t = *behavior.currentTarget;
|
||||||
|
if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t)) { return; }
|
||||||
|
|
||||||
|
const QVector2D ownerPos = admin.get<PositionComponent>(owner.owner).value;
|
||||||
|
const float dist = (admin.get<PositionComponent>(t).value - ownerPos).length();
|
||||||
|
if (dist <= module.range_tiles)
|
||||||
|
{
|
||||||
|
module.currentTarget = t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,69 +1,17 @@
|
|||||||
#include "RepairExecutor.h"
|
#include "RepairExecutor.h"
|
||||||
|
|
||||||
#include "BehaviorKind.h"
|
#include "BehaviorKind.h"
|
||||||
#include "DynamicBodyComponent.h"
|
|
||||||
#include "EntityAdmin.h"
|
#include "EntityAdmin.h"
|
||||||
#include "ModuleOwnerComponent.h"
|
#include "OrbitAndAssignExecutor.h"
|
||||||
#include "MovementIntentComponent.h"
|
|
||||||
#include "PositionComponent.h"
|
|
||||||
#include "RepairBehavior.h"
|
#include "RepairBehavior.h"
|
||||||
#include "RepairToolComponent.h"
|
#include "RepairToolComponent.h"
|
||||||
#include "SelectedBehaviorComponent.h"
|
|
||||||
#include "tracing.h"
|
#include "tracing.h"
|
||||||
|
|
||||||
void RepairExecutor::execute(EntityAdmin& admin)
|
void RepairExecutor::execute(EntityAdmin& admin)
|
||||||
{
|
{
|
||||||
TRACE();
|
TRACE();
|
||||||
|
|
||||||
// Ships: move toward the repair target.
|
// Orbit the repair target and hand it to every repair tool that can reach it
|
||||||
admin.forEach<RepairBehavior, SelectedBehaviorComponent, PositionComponent,
|
// (REQ-SHP-ORBIT).
|
||||||
MovementIntentComponent>(
|
executeOrbitAndAssign<RepairBehavior, RepairToolComponent>(admin, BehaviorKind::Repair);
|
||||||
[&](entt::entity /*e*/, const RepairBehavior& repair,
|
|
||||||
const SelectedBehaviorComponent& selected, const PositionComponent& pos,
|
|
||||||
MovementIntentComponent& intent)
|
|
||||||
{
|
|
||||||
if (selected.winner != BehaviorKind::Repair) { return; }
|
|
||||||
if (!repair.currentTarget) { return; }
|
|
||||||
|
|
||||||
const entt::entity t = *repair.currentTarget;
|
|
||||||
QVector2D center = pos.value;
|
|
||||||
float radius = 0.0f;
|
|
||||||
QVector2D centerVelocity;
|
|
||||||
if (admin.isValid(t) && admin.hasAll<PositionComponent>(t))
|
|
||||||
{
|
|
||||||
center = admin.get<PositionComponent>(t).value;
|
|
||||||
radius = repair.orbitRadius_tiles;
|
|
||||||
if (admin.hasAll<DynamicBodyComponent>(t))
|
|
||||||
{
|
|
||||||
centerVelocity = admin.get<DynamicBodyComponent>(t).velocity_tpt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
intent = MovementIntentComponent{true, center, radius, centerVelocity};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Repair tools: prefer the behavior target if it is within tool range.
|
|
||||||
admin.forEach<RepairToolComponent, ModuleOwnerComponent>(
|
|
||||||
[&](entt::entity /*re*/, RepairToolComponent& tool, const ModuleOwnerComponent& owner)
|
|
||||||
{
|
|
||||||
if (!admin.hasAll<RepairBehavior, SelectedBehaviorComponent>(owner.owner))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const SelectedBehaviorComponent& selected =
|
|
||||||
admin.get<SelectedBehaviorComponent>(owner.owner);
|
|
||||||
if (selected.winner != BehaviorKind::Repair) { return; }
|
|
||||||
|
|
||||||
const RepairBehavior& repair = admin.get<RepairBehavior>(owner.owner);
|
|
||||||
if (!repair.currentTarget) { return; }
|
|
||||||
|
|
||||||
const entt::entity t = *repair.currentTarget;
|
|
||||||
if (!admin.isValid(t) || !admin.hasAll<PositionComponent>(t)) { return; }
|
|
||||||
|
|
||||||
const QVector2D ownerPos = admin.get<PositionComponent>(owner.owner).value;
|
|
||||||
const float dist = (admin.get<PositionComponent>(t).value - ownerPos).length();
|
|
||||||
if (dist <= tool.range_tiles)
|
|
||||||
{
|
|
||||||
tool.currentTarget = t;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user