switch to ECS architecture
This commit is contained in:
@@ -8,456 +8,440 @@
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "BuildingType.h"
|
||||
#include "EcsComponents.h"
|
||||
#include "EntityAdmin.h"
|
||||
#include "EntityId.h"
|
||||
#include "MovementIntent.h"
|
||||
#include "Scrap.h"
|
||||
#include "ScrapSystem.h"
|
||||
#include "Ship.h"
|
||||
#include "ShipSystem.h"
|
||||
|
||||
static QVector2D buildingCenter(const Building& b)
|
||||
{
|
||||
return QVector2D(b.anchor.x() + b.footprint.width() / 2.0f,
|
||||
b.anchor.y() + b.footprint.height() / 2.0f);
|
||||
}
|
||||
|
||||
static bool isTargetValid(EntityId id, float range, const Ship& ship,
|
||||
const ShipSystem& ships,
|
||||
const BuildingSystem& buildings)
|
||||
{
|
||||
if (id == kInvalidEntityId) { return false; }
|
||||
const Ship* target = ships.findShip(id);
|
||||
if (target) { return (target->position - ship.position).length() <= range; }
|
||||
const Building* bld = buildings.findBuilding(id);
|
||||
if (bld) { return (buildingCenter(*bld) - ship.position).length() <= range; }
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tickHomeReturn (priority 4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void AiSystem::tickHomeReturn(ShipSystem& ships)
|
||||
void AiSystem::tickHomeReturn(EntityAdmin& admin)
|
||||
{
|
||||
ships.forEach([&](Ship& s)
|
||||
{
|
||||
if (!s.homeReturn) { return; }
|
||||
if (s.hp / s.maxHp < s.homeReturn->retreatHpFraction)
|
||||
admin.forEach<HomeReturn, Health, MovementIntent>(
|
||||
[](entt::entity /*e*/, const HomeReturn& hr, const Health& h, MovementIntent& intent)
|
||||
{
|
||||
if (4 > s.intent.priority)
|
||||
if (h.hp / h.maxHp < hr.retreatHpFraction)
|
||||
{
|
||||
s.intent = MovementIntent{4, s.homeReturn->homePos};
|
||||
if (4 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{4, hr.homePos};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tickThreatResponse (priority 3)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void AiSystem::tickThreatResponse(ShipSystem& ships, const BuildingSystem& buildings)
|
||||
void AiSystem::tickThreatResponse(EntityAdmin& admin, const BuildingSystem& buildings)
|
||||
{
|
||||
const std::vector<Building> allBuildings = buildings.allBuildings();
|
||||
const std::vector<Ship> allShips = ships.allShips();
|
||||
|
||||
ships.forEach([&](Ship& s)
|
||||
// Snapshot all combatant entities for target acquisition.
|
||||
struct CombatantInfo
|
||||
{
|
||||
if (!s.threatResponse) { return; }
|
||||
entt::entity entity;
|
||||
QVector2D position;
|
||||
bool isEnemy;
|
||||
bool isStation;
|
||||
};
|
||||
std::vector<CombatantInfo> combatants;
|
||||
|
||||
const float range = s.sensorRange;
|
||||
|
||||
if (!s.isEnemy)
|
||||
admin.forEach<Position, Faction, ShipIdentity>(
|
||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const ShipIdentity& /*si*/)
|
||||
{
|
||||
if (!isTargetValid(s.threatResponse->currentTarget.value_or(kInvalidEntityId),
|
||||
range, s, ships, buildings))
|
||||
combatants.push_back({e, pos.value, f.isEnemy, false});
|
||||
});
|
||||
|
||||
admin.forEach<Position, Faction, StationBody>(
|
||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const StationBody& /*sb*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||
});
|
||||
|
||||
admin.forEach<Position, Faction, HqProxy>(
|
||||
[&combatants](entt::entity e, const Position& pos, const Faction& f, const HqProxy& /*hq*/)
|
||||
{
|
||||
combatants.push_back({e, pos.value, f.isEnemy, true});
|
||||
});
|
||||
|
||||
admin.forEach<ThreatResponse, Position, Faction, SensorRange, MovementIntent>(
|
||||
[&](entt::entity e, ThreatResponse& threat, Position& pos, Faction& faction,
|
||||
SensorRange& sensor, MovementIntent& intent)
|
||||
{
|
||||
const float range = sensor.value;
|
||||
|
||||
// Validate current target.
|
||||
bool targetValid = false;
|
||||
if (threat.currentTarget)
|
||||
{
|
||||
s.threatResponse->currentTarget = std::nullopt;
|
||||
const entt::entity t = *threat.currentTarget;
|
||||
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
||||
{
|
||||
const float dist = (admin.get<Position>(t).value - pos.value).length();
|
||||
if (dist <= range)
|
||||
{
|
||||
targetValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetValid)
|
||||
{
|
||||
threat.currentTarget = std::nullopt;
|
||||
float bestDist = range;
|
||||
for (const Ship& candidate : allShips)
|
||||
{
|
||||
if (!candidate.isEnemy) { continue; }
|
||||
float dist = (candidate.position - s.position).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
s.threatResponse->currentTarget = candidate.id;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Building& b : allBuildings)
|
||||
for (const CombatantInfo& c : combatants)
|
||||
{
|
||||
if (b.type != BuildingType::EnemyDefenceStation) { continue; }
|
||||
float dist = (buildingCenter(b) - s.position).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
s.threatResponse->currentTarget = b.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c.entity == e) { continue; }
|
||||
|
||||
if (s.threatResponse->currentTarget)
|
||||
{
|
||||
QVector2D dest;
|
||||
const Ship* tShip = ships.findShip(*s.threatResponse->currentTarget);
|
||||
if (tShip)
|
||||
{
|
||||
dest = tShip->position;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Building* tBld = buildings.findBuilding(
|
||||
*s.threatResponse->currentTarget);
|
||||
dest = tBld ? buildingCenter(*tBld) : s.position;
|
||||
}
|
||||
if (3 > s.intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{3, dest};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (3 > s.intent.priority)
|
||||
{
|
||||
if (s.rallyBehavior)
|
||||
bool isValidTarget = false;
|
||||
if (!faction.isEnemy)
|
||||
{
|
||||
s.intent = MovementIntent{3, s.rallyBehavior->rallyPoint};
|
||||
isValidTarget = c.isEnemy;
|
||||
}
|
||||
else
|
||||
{
|
||||
s.intent = MovementIntent{3, QVector2D(s.position.x() + 1000.0f,
|
||||
s.position.y())};
|
||||
isValidTarget = !c.isEnemy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isTargetValid(s.threatResponse->currentTarget.value_or(kInvalidEntityId),
|
||||
range, s, ships, buildings))
|
||||
{
|
||||
s.threatResponse->currentTarget = std::nullopt;
|
||||
float bestDist = range;
|
||||
if (!isValidTarget) { continue; }
|
||||
|
||||
for (const Ship& candidate : allShips)
|
||||
{
|
||||
if (candidate.isEnemy) { continue; }
|
||||
float dist = (candidate.position - s.position).length();
|
||||
const float dist = (c.position - pos.value).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
s.threatResponse->currentTarget = candidate.id;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Building& b : allBuildings)
|
||||
{
|
||||
if (b.type != BuildingType::PlayerDefenceStation
|
||||
&& b.type != BuildingType::Hq)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float dist = (buildingCenter(b) - s.position).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
s.threatResponse->currentTarget = b.id;
|
||||
threat.currentTarget = c.entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.threatResponse->currentTarget)
|
||||
if (threat.currentTarget)
|
||||
{
|
||||
QVector2D dest;
|
||||
const Ship* tShip = ships.findShip(*s.threatResponse->currentTarget);
|
||||
if (tShip)
|
||||
const entt::entity t = *threat.currentTarget;
|
||||
QVector2D dest = pos.value;
|
||||
if (admin.isValid(t) && admin.hasAll<Position>(t))
|
||||
{
|
||||
dest = tShip->position;
|
||||
dest = admin.get<Position>(t).value;
|
||||
}
|
||||
else
|
||||
if (3 > intent.priority)
|
||||
{
|
||||
const Building* tBld = buildings.findBuilding(
|
||||
*s.threatResponse->currentTarget);
|
||||
dest = tBld ? buildingCenter(*tBld) : s.position;
|
||||
}
|
||||
if (3 > s.intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{3, dest};
|
||||
intent = MovementIntent{3, dest};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (3 > s.intent.priority)
|
||||
if (3 > intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{3, QVector2D(-10000.0f, s.position.y())};
|
||||
if (admin.hasAll<RallyBehavior>(e))
|
||||
{
|
||||
intent = MovementIntent{3, admin.get<RallyBehavior>(e).rallyPoint};
|
||||
}
|
||||
else if (!faction.isEnemy)
|
||||
{
|
||||
intent = MovementIntent{3, QVector2D(pos.value.x() + 1000.0f,
|
||||
pos.value.y())};
|
||||
}
|
||||
else
|
||||
{
|
||||
intent = MovementIntent{3, QVector2D(-10000.0f, pos.value.y())};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tickRepairBehavior (priority 2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void AiSystem::tickRepairBehavior(ShipSystem& ships, BuildingSystem& buildings)
|
||||
void AiSystem::tickRepairBehavior(EntityAdmin& admin, BuildingSystem& buildings)
|
||||
{
|
||||
const std::vector<Building> allBuildings = buildings.allBuildings();
|
||||
const std::vector<Ship> allShips = ships.allShips();
|
||||
|
||||
ships.forEach([&](Ship& s)
|
||||
// Snapshot all entities with health for repair targeting.
|
||||
struct RepairableInfo
|
||||
{
|
||||
if (!s.repairBehavior || !s.repairTool) { return; }
|
||||
entt::entity entity;
|
||||
QVector2D position;
|
||||
bool isEnemy;
|
||||
bool isShip;
|
||||
float hp;
|
||||
float maxHp;
|
||||
};
|
||||
std::vector<RepairableInfo> repairables;
|
||||
|
||||
const float repairRange = s.repairTool->range;
|
||||
admin.forEach<ShipIdentity, Position, Faction, Health>(
|
||||
[&repairables](entt::entity e, const ShipIdentity& /*si*/,
|
||||
const Position& pos, const Faction& f, const Health& h)
|
||||
{
|
||||
repairables.push_back({e, pos.value, f.isEnemy, true, h.hp, h.maxHp});
|
||||
});
|
||||
|
||||
bool enemyNearby = false;
|
||||
for (const Ship& candidate : allShips)
|
||||
admin.forEach<StationBody, Position, Faction, Health>(
|
||||
[&repairables](entt::entity e, const StationBody& /*sb*/,
|
||||
const Position& pos, const Faction& f, const Health& h)
|
||||
{
|
||||
if (candidate.isEnemy
|
||||
&& (candidate.position - s.position).length() <= s.sensorRange)
|
||||
{
|
||||
enemyNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (enemyNearby)
|
||||
{
|
||||
if (2 > s.intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{2, QVector2D(-10000.0f, s.position.y())};
|
||||
}
|
||||
return;
|
||||
}
|
||||
repairables.push_back({e, pos.value, f.isEnemy, false, h.hp, h.maxHp});
|
||||
});
|
||||
|
||||
EntityId currentId = s.repairBehavior->currentTarget.value_or(kInvalidEntityId);
|
||||
bool targetValid = false;
|
||||
if (currentId != kInvalidEntityId)
|
||||
// Snapshot enemy ships for threat detection.
|
||||
struct EnemyInfo
|
||||
{
|
||||
QVector2D position;
|
||||
};
|
||||
std::vector<EnemyInfo> enemies;
|
||||
admin.forEach<ShipIdentity, Position, Faction>(
|
||||
[&enemies](entt::entity /*e*/, const ShipIdentity& /*si*/,
|
||||
const Position& pos, const Faction& f)
|
||||
{
|
||||
const Ship* tShip = ships.findShip(currentId);
|
||||
if (tShip && !tShip->isEnemy && tShip->hp < tShip->maxHp)
|
||||
if (f.isEnemy)
|
||||
{
|
||||
targetValid = true;
|
||||
enemies.push_back({pos.value});
|
||||
}
|
||||
else
|
||||
});
|
||||
|
||||
admin.forEach<RepairBehavior, RepairTool, Position, Faction, SensorRange, MovementIntent>(
|
||||
[&](entt::entity e, RepairBehavior& rb, RepairTool& rt, Position& pos,
|
||||
Faction& /*faction*/, SensorRange& sensor, MovementIntent& intent)
|
||||
{
|
||||
const float repairRange = rt.range;
|
||||
|
||||
// Flee if enemy nearby.
|
||||
bool enemyNearby = false;
|
||||
for (const EnemyInfo& enemy : enemies)
|
||||
{
|
||||
const Building* tBld = buildings.findBuilding(currentId);
|
||||
if (tBld && tBld->type == BuildingType::PlayerDefenceStation
|
||||
&& tBld->hp < tBld->maxHp)
|
||||
if ((enemy.position - pos.value).length() <= sensor.value)
|
||||
{
|
||||
targetValid = true;
|
||||
enemyNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetValid)
|
||||
{
|
||||
s.repairBehavior->currentTarget = std::nullopt;
|
||||
currentId = kInvalidEntityId;
|
||||
float bestDist = s.sensorRange;
|
||||
|
||||
for (const Ship& candidate : allShips)
|
||||
if (enemyNearby)
|
||||
{
|
||||
if (candidate.isEnemy || candidate.id == s.id
|
||||
|| candidate.hp >= candidate.maxHp)
|
||||
if (2 > intent.priority)
|
||||
{
|
||||
continue;
|
||||
intent = MovementIntent{2, QVector2D(-10000.0f, pos.value.y())};
|
||||
}
|
||||
float dist = (candidate.position - s.position).length();
|
||||
if (dist < bestDist)
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate current target.
|
||||
bool targetValid = false;
|
||||
if (rb.currentTarget)
|
||||
{
|
||||
const entt::entity t = *rb.currentTarget;
|
||||
if (admin.isValid(t) && admin.hasAll<Health>(t))
|
||||
{
|
||||
bestDist = dist;
|
||||
s.repairBehavior->currentTarget = candidate.id;
|
||||
const Health& th = admin.get<Health>(t);
|
||||
if (th.hp > 0.0f && th.hp < th.maxHp)
|
||||
{
|
||||
targetValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const Building& b : allBuildings)
|
||||
if (!targetValid)
|
||||
{
|
||||
if (b.type != BuildingType::PlayerDefenceStation
|
||||
|| b.hp >= b.maxHp)
|
||||
rb.currentTarget = std::nullopt;
|
||||
float bestDist = sensor.value;
|
||||
|
||||
for (const RepairableInfo& r : repairables)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float dist = (buildingCenter(b) - s.position).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
s.repairBehavior->currentTarget = b.id;
|
||||
if (r.entity == e) { continue; }
|
||||
if (r.isEnemy) { continue; }
|
||||
if (r.hp >= r.maxHp) { continue; }
|
||||
const float dist = (r.position - pos.value).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
rb.currentTarget = r.entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentId = s.repairBehavior->currentTarget.value_or(kInvalidEntityId);
|
||||
}
|
||||
|
||||
if (currentId == kInvalidEntityId)
|
||||
{
|
||||
if (2 > s.intent.priority)
|
||||
if (!rb.currentTarget)
|
||||
{
|
||||
s.intent = MovementIntent{2, QVector2D(s.position.x() + 1000.0f,
|
||||
s.position.y())};
|
||||
if (2 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{2, QVector2D(pos.value.x() + 1000.0f,
|
||||
pos.value.y())};
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QVector2D targetPos;
|
||||
bool isShipTarget = false;
|
||||
const Ship* tShip = ships.findShip(currentId);
|
||||
if (tShip)
|
||||
{
|
||||
targetPos = tShip->position;
|
||||
isShipTarget = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Building* tBld = buildings.findBuilding(currentId);
|
||||
targetPos = tBld ? buildingCenter(*tBld) : s.position;
|
||||
}
|
||||
|
||||
float distToTarget = (targetPos - s.position).length();
|
||||
if (distToTarget <= repairRange)
|
||||
{
|
||||
if (isShipTarget)
|
||||
const entt::entity target = *rb.currentTarget;
|
||||
QVector2D targetPos = pos.value;
|
||||
bool isShipTarget = false;
|
||||
if (admin.isValid(target) && admin.hasAll<Position>(target))
|
||||
{
|
||||
ships.healShip(currentId, s.repairTool->ratePerTick);
|
||||
targetPos = admin.get<Position>(target).value;
|
||||
isShipTarget = admin.hasAll<ShipIdentity>(target);
|
||||
}
|
||||
else
|
||||
{
|
||||
buildings.healBuilding(currentId, s.repairTool->ratePerTick);
|
||||
}
|
||||
}
|
||||
|
||||
if (2 > s.intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{2, targetPos};
|
||||
}
|
||||
});
|
||||
const float distToTarget = (targetPos - pos.value).length();
|
||||
if (distToTarget <= repairRange)
|
||||
{
|
||||
if (admin.isValid(target) && admin.hasAll<Health>(target))
|
||||
{
|
||||
Health& targetHealth = admin.get<Health>(target);
|
||||
targetHealth.hp = std::min(targetHealth.hp + rt.ratePerTick,
|
||||
targetHealth.maxHp);
|
||||
}
|
||||
}
|
||||
|
||||
if (2 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{2, targetPos};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tickScrapCollector (priority 1)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void AiSystem::tickScrapCollector(ShipSystem& ships, ScrapSystem& scraps,
|
||||
void AiSystem::tickScrapCollector(EntityAdmin& admin, ScrapSystem& scraps,
|
||||
BuildingSystem& buildings)
|
||||
{
|
||||
const std::vector<Ship> allShips = ships.allShips();
|
||||
|
||||
ships.forEach([&](Ship& s)
|
||||
// Snapshot enemy ships for threat detection.
|
||||
struct EnemyShipPos
|
||||
{
|
||||
if (!s.scrapCollector || !s.cargo) { return; }
|
||||
|
||||
const float collectRange = s.cargo->collectionRange;
|
||||
|
||||
if (s.scrapCollector->deliveryBay == kInvalidEntityId)
|
||||
QVector2D position;
|
||||
};
|
||||
std::vector<EnemyShipPos> enemyShips;
|
||||
admin.forEach<ShipIdentity, Position, Faction>(
|
||||
[&enemyShips](entt::entity /*e*/, const ShipIdentity& /*si*/,
|
||||
const Position& pos, const Faction& f)
|
||||
{
|
||||
const Building* bay = buildings.findNearestBuilding(s.position,
|
||||
BuildingType::SalvageBay);
|
||||
if (bay)
|
||||
if (f.isEnemy)
|
||||
{
|
||||
s.scrapCollector->deliveryBay = bay->id;
|
||||
enemyShips.push_back({pos.value});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const EntityId bayId = s.scrapCollector->deliveryBay;
|
||||
const std::vector<ScrapInfo> allScrap = scraps.allScrapInfo();
|
||||
|
||||
QVector2D bayPos = s.position;
|
||||
if (bayId != kInvalidEntityId)
|
||||
admin.forEach<ScrapCollector, SalvageCargo, Position, SensorRange, MovementIntent>(
|
||||
[&](entt::entity /*e*/, ScrapCollector& sc, SalvageCargo& cargo,
|
||||
Position& pos, SensorRange& sensor, MovementIntent& intent)
|
||||
{
|
||||
const Building* bay = buildings.findBuilding(bayId);
|
||||
if (bay)
|
||||
{
|
||||
bayPos = buildingCenter(*bay);
|
||||
}
|
||||
}
|
||||
const float collectRange = cargo.collectionRange;
|
||||
|
||||
const bool cargoFull = (s.cargo->current >= s.cargo->capacity);
|
||||
|
||||
if (cargoFull)
|
||||
{
|
||||
if (1 > s.intent.priority)
|
||||
// Assign nearest SalvageBay if needed.
|
||||
if (sc.deliveryBay == kInvalidEntityId)
|
||||
{
|
||||
s.intent = MovementIntent{1, bayPos};
|
||||
}
|
||||
if (bayId != kInvalidEntityId
|
||||
&& (s.position - bayPos).length() <= 1.0f)
|
||||
{
|
||||
if (buildings.deliverScrapToSalvageBay(bayId))
|
||||
const Building* bay = buildings.findNearestBuilding(pos.value,
|
||||
BuildingType::SalvageBay);
|
||||
if (bay)
|
||||
{
|
||||
--s.cargo->current;
|
||||
sc.deliveryBay = bay->id;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool retreating = false;
|
||||
if (s.cargo->current == 0)
|
||||
{
|
||||
for (const Ship& candidate : allShips)
|
||||
const EntityId bayId = sc.deliveryBay;
|
||||
|
||||
QVector2D bayPos = pos.value;
|
||||
if (bayId != kInvalidEntityId)
|
||||
{
|
||||
if (candidate.isEnemy
|
||||
&& (candidate.position - s.position).length() <= collectRange)
|
||||
const Building* bay = buildings.findBuilding(bayId);
|
||||
if (bay)
|
||||
{
|
||||
if (1 > s.intent.priority)
|
||||
bayPos = QVector2D(bay->anchor.x() + bay->footprint.width() / 2.0f,
|
||||
bay->anchor.y() + bay->footprint.height() / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
const bool cargoFull = (cargo.current >= cargo.capacity);
|
||||
|
||||
if (cargoFull)
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, bayPos};
|
||||
}
|
||||
if (bayId != kInvalidEntityId
|
||||
&& (pos.value - bayPos).length() <= 1.0f)
|
||||
{
|
||||
if (buildings.deliverScrapToSalvageBay(bayId))
|
||||
{
|
||||
s.intent = MovementIntent{1, QVector2D(-10000.0f, s.position.y())};
|
||||
--cargo.current;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Retreat if enemy near and cargo empty.
|
||||
bool retreating = false;
|
||||
if (cargo.current == 0)
|
||||
{
|
||||
for (const EnemyShipPos& enemy : enemyShips)
|
||||
{
|
||||
if ((enemy.position - pos.value).length() <= collectRange)
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, QVector2D(-10000.0f, pos.value.y())};
|
||||
}
|
||||
retreating = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retreating) { return; }
|
||||
|
||||
// Collect nearby scrap.
|
||||
for (const ScrapInfo& si : allScrap)
|
||||
{
|
||||
if ((si.position - pos.value).length() <= collectRange)
|
||||
{
|
||||
if (scraps.consume(si.entity))
|
||||
{
|
||||
++cargo.current;
|
||||
sc.scrapTarget = std::nullopt;
|
||||
}
|
||||
retreating = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retreating) { return; }
|
||||
|
||||
for (const Scrap& sc : scraps.allScraps())
|
||||
{
|
||||
if ((sc.position - s.position).length() <= collectRange)
|
||||
// Move toward scrap target or find a new one.
|
||||
if (sc.scrapTarget)
|
||||
{
|
||||
if (scraps.consume(sc.id))
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
++s.cargo->current;
|
||||
s.scrapCollector->scrapTarget = std::nullopt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.scrapCollector->scrapTarget)
|
||||
{
|
||||
if (1 > s.intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{1, *s.scrapCollector->scrapTarget};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float bestDist = s.sensorRange;
|
||||
std::optional<QVector2D> bestPos;
|
||||
for (const Scrap& sc : scraps.allScraps())
|
||||
{
|
||||
float dist = (sc.position - s.position).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
bestPos = sc.position;
|
||||
}
|
||||
}
|
||||
if (bestPos)
|
||||
{
|
||||
s.scrapCollector->scrapTarget = bestPos;
|
||||
if (1 > s.intent.priority)
|
||||
{
|
||||
s.intent = MovementIntent{1, *bestPos};
|
||||
intent = MovementIntent{1, *sc.scrapTarget};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (1 > s.intent.priority)
|
||||
float bestDist = sensor.value;
|
||||
std::optional<QVector2D> bestPos;
|
||||
for (const ScrapInfo& si : allScrap)
|
||||
{
|
||||
s.intent = MovementIntent{1, QVector2D(s.position.x() + 1000.0f,
|
||||
s.position.y())};
|
||||
const float dist = (si.position - pos.value).length();
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
bestPos = si.position;
|
||||
}
|
||||
}
|
||||
if (bestPos)
|
||||
{
|
||||
sc.scrapTarget = bestPos;
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, *bestPos};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (1 > intent.priority)
|
||||
{
|
||||
intent = MovementIntent{1, QVector2D(pos.value.x() + 1000.0f,
|
||||
pos.value.y())};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user