#pragma once #include #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 currentTarget` and // `float orbitRadius_tiles`; `ModuleComponent` must expose `float range_tiles` and // `std::optional currentTarget`. template void executeOrbitAndAssign(EntityAdmin& admin, BehaviorKind kind) { // Ships: move toward the behavior target. admin.forEach( [&](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(t)) { center = admin.get(t).value; radius = behavior.orbitRadius_tiles; if (admin.hasAll(t)) { centerVelocity = admin.get(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( [&](entt::entity /*me*/, ModuleComponent& module, const ModuleOwnerComponent& owner) { if (!admin.hasAll(owner.owner)) { return; } const SelectedBehaviorComponent& selected = admin.get(owner.owner); if (selected.winner != kind) { return; } const Behavior& behavior = admin.get(owner.owner); if (!behavior.currentTarget) { return; } const entt::entity t = *behavior.currentTarget; if (!admin.isValid(t) || !admin.hasAll(t)) { return; } const QVector2D ownerPos = admin.get(owner.owner).value; const float dist = (admin.get(t).value - ownerPos).length(); if (dist <= module.range_tiles) { module.currentTarget = t; } }); }