From b3acf9aa06a7f2133e2c3c6e61baefd1e6225db5 Mon Sep 17 00:00:00 2001 From: Saurtron Date: Fri, 10 Jan 2025 18:27:35 +0100 Subject: [PATCH] Make FactoryCAI capable of guard, reclaim and repair. --- rts/Sim/CMakeLists.txt | 1 + rts/Sim/Features/FeatureHandler.cpp | 3 +- rts/Sim/Units/CommandAI/BuilderCAI.cpp | 205 +----- rts/Sim/Units/CommandAI/BuilderCAI.h | 26 - rts/Sim/Units/CommandAI/BuilderCaches.cpp | 147 ++++ rts/Sim/Units/CommandAI/BuilderCaches.h | 50 ++ rts/Sim/Units/CommandAI/FactoryCAI.cpp | 801 +++++++++++++++++++++- rts/Sim/Units/CommandAI/FactoryCAI.h | 52 ++ rts/Sim/Units/Unit.cpp | 3 +- rts/Sim/Units/UnitDefHandler.cpp | 4 +- rts/Sim/Units/UnitHandler.cpp | 9 +- rts/Sim/Units/UnitLoader.cpp | 6 +- rts/Sim/Units/UnitMemPool.h | 3 +- rts/Sim/Units/UnitTypes/Builder.cpp | 4 +- rts/Sim/Units/UnitTypes/Factory.cpp | 233 ++++++- rts/Sim/Units/UnitTypes/Factory.h | 42 +- 16 files changed, 1338 insertions(+), 251 deletions(-) create mode 100644 rts/Sim/Units/CommandAI/BuilderCaches.cpp create mode 100644 rts/Sim/Units/CommandAI/BuilderCaches.h diff --git a/rts/Sim/CMakeLists.txt b/rts/Sim/CMakeLists.txt index 2a847d1189..d1b571dbc8 100644 --- a/rts/Sim/CMakeLists.txt +++ b/rts/Sim/CMakeLists.txt @@ -110,6 +110,7 @@ add_library(engineSim STATIC "${CMAKE_CURRENT_SOURCE_DIR}/Units/CommandAI/CommandDescription.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Units/CommandAI/FactoryCAI.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Units/CommandAI/MobileCAI.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/Units/CommandAI/BuilderCaches.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Units/Scripts/CobEngine.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Units/Scripts/CobFile.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Units/Scripts/CobFileHandler.cpp" diff --git a/rts/Sim/Features/FeatureHandler.cpp b/rts/Sim/Features/FeatureHandler.cpp index b76dcf8bba..4b2d887fcf 100644 --- a/rts/Sim/Features/FeatureHandler.cpp +++ b/rts/Sim/Features/FeatureHandler.cpp @@ -10,6 +10,7 @@ #include "Sim/Ecs/Registry.h" #include "Sim/Misc/QuadField.h" #include "Sim/Units/CommandAI/BuilderCAI.h" +#include "Sim/Units/CommandAI/BuilderCaches.h" #include "System/creg/STL_Set.h" #include "System/EventHandler.h" #include "System/TimeProfiler.h" @@ -209,7 +210,7 @@ void CFeatureHandler::Update() bool CFeatureHandler::TryFreeFeatureID(int id) { RECOIL_DETAILED_TRACY_ZONE; - if (CBuilderCAI::IsFeatureBeingReclaimed(id)) { + if (CBuilderCaches::IsFeatureBeingReclaimed(id)) { // postpone putting this ID back into the free pool // (this gives area-reclaimers time to choose a new // target with a different ID) diff --git a/rts/Sim/Units/CommandAI/BuilderCAI.cpp b/rts/Sim/Units/CommandAI/BuilderCAI.cpp index adb207da56..aaf935116c 100644 --- a/rts/Sim/Units/CommandAI/BuilderCAI.cpp +++ b/rts/Sim/Units/CommandAI/BuilderCAI.cpp @@ -23,6 +23,7 @@ #include "Sim/Units/UnitTypes/Builder.h" #include "Sim/Units/UnitTypes/Building.h" #include "Sim/Units/UnitTypes/Factory.h" +#include "Sim/Units/CommandAI/BuilderCaches.h" #include "System/SpringMath.h" #include "System/StringUtil.h" #include "System/EventHandler.h" @@ -55,14 +56,6 @@ CR_REG_METADATA(CBuilderCAI , ( CR_PREALLOC(GetPreallocContainer) )) -// not adding to members, should repopulate itself -spring::unordered_set CBuilderCAI::reclaimers; -spring::unordered_set CBuilderCAI::featureReclaimers; -spring::unordered_set CBuilderCAI::resurrecters; - -std::vector CBuilderCAI::removees; - - static std::string GetUnitDefBuildOptionToolTip(const UnitDef* ud, bool disabled) { RECOIL_DETAILED_TRACY_ZONE; std::string tooltip; @@ -222,20 +215,12 @@ CBuilderCAI::CBuilderCAI(CUnit* owner): CBuilderCAI::~CBuilderCAI() { RECOIL_DETAILED_TRACY_ZONE; - RemoveUnitFromReclaimers(owner); - RemoveUnitFromFeatureReclaimers(owner); - RemoveUnitFromResurrecters(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromResurrecters(owner); unitHandler.RemoveBuilderCAI(this); } -void CBuilderCAI::InitStatic() -{ - RECOIL_DETAILED_TRACY_ZONE; - spring::clear_unordered_set(reclaimers); - spring::clear_unordered_set(featureReclaimers); - spring::clear_unordered_set(resurrecters); -} - void CBuilderCAI::PostLoad() { RECOIL_DETAILED_TRACY_ZONE; @@ -779,7 +764,7 @@ void CBuilderCAI::ExecuteRepair(Command& c) canRepairUnit &= ((unit->beingBuilt) || (unit->unitDef->repairable && (unit->health < unit->maxHealth))); canRepairUnit &= ((unit != owner) || owner->unitDef->canSelfRepair); canRepairUnit &= (!unit->soloBuilder || (unit->soloBuilder == owner)); - canRepairUnit &= (!c.IsInternalOrder() || (c.GetOpts() & CONTROL_KEY) || !IsUnitBeingReclaimed(unit, owner)); + canRepairUnit &= (!c.IsInternalOrder() || (c.GetOpts() & CONTROL_KEY) || !CBuilderCaches::IsUnitBeingReclaimed(unit, owner)); canRepairUnit &= (UpdateTargetLostTimer(unit->id) != 0); if (canRepairUnit) { @@ -950,7 +935,7 @@ void CBuilderCAI::ExecuteGuard(Command& c) } } - if (!(c.GetOpts() & CONTROL_KEY) && IsUnitBeingReclaimed(guardee, owner)) + if (!(c.GetOpts() & CONTROL_KEY) && CBuilderCaches::IsUnitBeingReclaimed(guardee, owner)) return; const float3 pos = guardee->pos; @@ -1025,8 +1010,8 @@ void CBuilderCAI::ExecuteReclaim(Command& c) const int rid = FindReclaimTarget(pos, radius, c.GetOpts(), recopt, curdist); if ((rid > 0) && (rid != uid)) { StopMoveAndFinishCommand(); - RemoveUnitFromReclaimers(owner); - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); return; } } @@ -1036,21 +1021,21 @@ void CBuilderCAI::ExecuteReclaim(Command& c) CFeature* feature = featureHandler.GetFeature(uid - unitHandler.MaxUnits()); if (feature != nullptr) { - bool featureBeingResurrected = IsFeatureBeingResurrected(feature->id, owner); + bool featureBeingResurrected = CBuilderCaches::IsFeatureBeingResurrected(feature->id, owner); featureBeingResurrected &= c.IsInternalOrder(); if (featureBeingResurrected || !ReclaimObject(feature)) { StopMoveAndFinishCommand(); - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); } else { - AddUnitToFeatureReclaimers(owner); + CBuilderCaches::AddUnitToFeatureReclaimers(owner); } } else { StopMoveAndFinishCommand(); - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); } - RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); } else { // reclaim unit CUnit* unit = unitHandler.GetUnit(uid); @@ -1068,8 +1053,8 @@ void CBuilderCAI::ExecuteReclaim(Command& c) if (outOfReclaimRange || busyAlliedBuilder) { StopMoveAndFinishCommand(); - RemoveUnitFromReclaimers(owner); - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); return; } } @@ -1078,14 +1063,14 @@ void CBuilderCAI::ExecuteReclaim(Command& c) if (!ReclaimObject(unit)) { StopMoveAndFinishCommand(); } else { - AddUnitToReclaimers(owner); + CBuilderCaches::AddUnitToReclaimers(owner); } } else { - RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); StopMoveAndFinishCommand(); } - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); } } else if (c.GetNumParams() == 4) { // area reclaim @@ -1095,8 +1080,8 @@ void CBuilderCAI::ExecuteReclaim(Command& c) const bool recEnemyOnly = (c.GetOpts() & META_KEY) && (c.GetOpts() & CONTROL_KEY); const bool recSpecial = !!(c.GetOpts() & CONTROL_KEY); - RemoveUnitFromReclaimers(owner); - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); ownerBuilder->StopBuild(); ReclaimOption recopt = REC_NORESCHECK; @@ -1115,8 +1100,8 @@ void CBuilderCAI::ExecuteReclaim(Command& c) } else { // wrong number of parameters - RemoveUnitFromReclaimers(owner); - RemoveUnitFromFeatureReclaimers(owner); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); StopMoveAndFinishCommand(); } } @@ -1147,16 +1132,16 @@ void CBuilderCAI::ExecuteResurrect(Command& c) CFeature* feature = featureHandler.GetFeature(id - unitHandler.MaxUnits()); if (feature && feature->udef != nullptr) { - if ((c.IsInternalOrder() && !(c.GetOpts() & CONTROL_KEY) && IsFeatureBeingReclaimed(feature->id, owner)) || + if ((c.IsInternalOrder() && !(c.GetOpts() & CONTROL_KEY) && CBuilderCaches::IsFeatureBeingReclaimed(feature->id, owner)) || !ResurrectObject(feature)) { - RemoveUnitFromResurrecters(owner); + CBuilderCaches::RemoveUnitFromResurrecters(owner); StopMoveAndFinishCommand(); } else { - AddUnitToResurrecters(owner); + CBuilderCaches::AddUnitToResurrecters(owner); } } else { - RemoveUnitFromResurrecters(owner); + CBuilderCaches::RemoveUnitFromResurrecters(owner); if (ownerBuilder->lastResurrected && unitHandler.GetUnitUnsafe(ownerBuilder->lastResurrected) != nullptr && owner->unitDef->canRepair) { // resurrection finished, start repair (by overwriting the current order) @@ -1171,7 +1156,7 @@ void CBuilderCAI::ExecuteResurrect(Command& c) StopMoveAndFinishCommand(); } } else { // resurrect unit - RemoveUnitFromResurrecters(owner); + CBuilderCaches::RemoveUnitFromResurrecters(owner); StopMoveAndFinishCommand(); } } else if (c.GetNumParams() == 4) { @@ -1190,7 +1175,7 @@ void CBuilderCAI::ExecuteResurrect(Command& c) } else { // wrong number of parameters - RemoveUnitFromResurrecters(owner); + CBuilderCaches::RemoveUnitFromResurrecters(owner); StopMoveAndFinishCommand(); } } @@ -1386,134 +1371,6 @@ int CBuilderCAI::GetDefaultCmd(const CUnit* pointed, const CFeature* feature) } -void CBuilderCAI::AddUnitToReclaimers(CUnit* unit) { reclaimers.insert(unit->id); } -void CBuilderCAI::RemoveUnitFromReclaimers(CUnit* unit) { reclaimers.erase(unit->id); } - -void CBuilderCAI::AddUnitToFeatureReclaimers(CUnit* unit) { featureReclaimers.insert(unit->id); } -void CBuilderCAI::RemoveUnitFromFeatureReclaimers(CUnit* unit) { featureReclaimers.erase(unit->id); } - -void CBuilderCAI::AddUnitToResurrecters(CUnit* unit) { resurrecters.insert(unit->id); } -void CBuilderCAI::RemoveUnitFromResurrecters(CUnit* unit) { resurrecters.erase(unit->id); } - - -/** - * Checks if a unit is being reclaimed by a friendly con. - * - * We assume that there will not be a lot of reclaimers, because performance - * would suck if there were. Ideally, reclaimers should be assigned on a - * per-unit basis, but this requires tracking of deaths, which albeit - * already done, is not exactly simple to follow. - * - * TODO easy: store reclaiming units per allyteam - * TODO harder: update reclaimers as they start/finish reclaims and/or die - */ -bool CBuilderCAI::IsUnitBeingReclaimed(const CUnit* unit, const CUnit* friendUnit) -{ - bool retval = false; - - removees.clear(); - removees.reserve(reclaimers.size()); - - for (auto it = reclaimers.begin(); it != reclaimers.end(); ++it) { - const CUnit* u = unitHandler.GetUnit(*it); - const CCommandAI* cai = u->commandAI; - const CCommandQueue& cq = cai->commandQue; - - if (cq.empty()) { - removees.push_back(u->id); - continue; - } - const Command& c = cq.front(); - if (c.GetID() != CMD_RECLAIM || (c.GetNumParams() != 1 && c.GetNumParams() != 5)) { - removees.push_back(u->id); - continue; - } - const int cmdUnitId = (int)c.GetParam(0); - if (cmdUnitId == unit->id && (friendUnit == nullptr || teamHandler.Ally(friendUnit->allyteam, u->allyteam))) { - retval = true; - break; - } - } - - for (auto it = removees.begin(); it != removees.end(); ++it) - RemoveUnitFromReclaimers(unitHandler.GetUnit(*it)); - - return retval; -} - - -bool CBuilderCAI::IsFeatureBeingReclaimed(int featureId, const CUnit* friendUnit) -{ - RECOIL_DETAILED_TRACY_ZONE; - bool retval = false; - - removees.clear(); - removees.reserve(featureReclaimers.size()); - - for (auto it = featureReclaimers.begin(); it != featureReclaimers.end(); ++it) { - const CUnit* u = unitHandler.GetUnit(*it); - const CCommandAI* cai = u->commandAI; - const CCommandQueue& cq = cai->commandQue; - - if (cq.empty()) { - removees.push_back(u->id); - continue; - } - const Command& c = cq.front(); - if (c.GetID() != CMD_RECLAIM || (c.GetNumParams() != 1 && c.GetNumParams() != 5)) { - removees.push_back(u->id); - continue; - } - const int cmdFeatureId = (int)c.GetParam(0); - if ((cmdFeatureId - unitHandler.MaxUnits()) == featureId && (friendUnit == nullptr || teamHandler.Ally(friendUnit->allyteam, u->allyteam))) { - retval = true; - break; - } - } - - for (auto it = removees.begin(); it != removees.end(); ++it) - RemoveUnitFromFeatureReclaimers(unitHandler.GetUnit(*it)); - - return retval; -} - - -bool CBuilderCAI::IsFeatureBeingResurrected(int featureId, const CUnit* friendUnit) -{ - RECOIL_DETAILED_TRACY_ZONE; - bool retval = false; - - removees.clear(); - removees.reserve(resurrecters.size()); - - for (auto it = resurrecters.begin(); it != resurrecters.end(); ++it) { - const CUnit* u = unitHandler.GetUnit(*it); - const CCommandAI* cai = u->commandAI; - const CCommandQueue& cq = cai->commandQue; - - if (cq.empty()) { - removees.push_back(u->id); - continue; - } - const Command& c = cq.front(); - if (c.GetID() != CMD_RESURRECT || c.GetNumParams() != 1) { - removees.push_back(u->id); - continue; - } - const int cmdFeatureId = (int)c.GetParam(0); - if ((cmdFeatureId - unitHandler.MaxUnits()) == featureId && (friendUnit == nullptr || teamHandler.Ally(friendUnit->allyteam, u->allyteam))) { - retval = true; - break; - } - } - - for (auto it = removees.begin(); it != removees.end(); ++it) - RemoveUnitFromResurrecters(unitHandler.GetUnit(*it)); - - return retval; -} - - bool CBuilderCAI::ReclaimObject(CSolidObject* object) { RECOIL_DETAILED_TRACY_ZONE; if (MoveInBuildRange(object)) { @@ -1610,7 +1467,7 @@ int CBuilderCAI::FindReclaimTarget(const float3& pos, float radius, unsigned cha if (!owner->unitDef->canmove && !IsInBuildRange(f)) continue; - if (IsFeatureBeingResurrected(f->id, owner)) + if (CBuilderCaches::IsFeatureBeingResurrected(f->id, owner)) continue; metal |= (recSpecial && !metal && f->defResources.metal > 0.0f); @@ -1681,7 +1538,7 @@ bool CBuilderCAI::FindResurrectableFeatureAndResurrect( if (owner->immobile && !IsInBuildRange(f)) continue; - if (!(options & CONTROL_KEY) && IsFeatureBeingReclaimed(f->id, owner)) + if (!(options & CONTROL_KEY) && CBuilderCaches::IsFeatureBeingReclaimed(f->id, owner)) continue; bestDist = dist; @@ -1812,7 +1669,7 @@ bool CBuilderCAI::FindRepairTargetAndRepair( continue; // don't repair stuff that's being reclaimed - if (!(options & CONTROL_KEY) && IsUnitBeingReclaimed(unit, owner)) + if (!(options & CONTROL_KEY) && CBuilderCaches::IsUnitBeingReclaimed(unit, owner)) continue; stationary |= (!stationary && !unit->IsMoving()); diff --git a/rts/Sim/Units/CommandAI/BuilderCAI.h b/rts/Sim/Units/CommandAI/BuilderCAI.h index a1c9881731..f838fbcdef 100644 --- a/rts/Sim/Units/CommandAI/BuilderCAI.h +++ b/rts/Sim/Units/CommandAI/BuilderCAI.h @@ -27,7 +27,6 @@ class CBuilderCAI : public CMobileCAI CBuilderCAI(); ~CBuilderCAI(); - static void InitStatic(); void PostLoad(); int GetDefaultCmd(const CUnit* unit, const CFeature* feature); @@ -52,13 +51,6 @@ class CBuilderCAI : public CMobileCAI bool ReclaimObject(CSolidObject* o); bool ResurrectObject(CFeature* feature); - /** - * Checks if a unit is being reclaimed by a friendly con. - */ - static bool IsUnitBeingReclaimed(const CUnit* unit, const CUnit* friendUnit = nullptr); - static bool IsFeatureBeingReclaimed(int featureId, const CUnit* friendUnit = nullptr); - static bool IsFeatureBeingResurrected(int featureId, const CUnit* friendUnit = nullptr); - bool IsInBuildRange(const CWorldObject* obj) const; bool IsInBuildRange(const float3& pos, const float radius) const; float GetBuildRange(const float targetRadius) const; @@ -66,12 +58,6 @@ class CBuilderCAI : public CMobileCAI public: spring::unordered_set buildOptions; - static spring::unordered_set reclaimers; - static spring::unordered_set featureReclaimers; - static spring::unordered_set resurrecters; - - static std::vector removees; - private: enum ReclaimOptions { REC_NORESCHECK = 1<<0, @@ -124,18 +110,6 @@ class CBuilderCAI : public CMobileCAI /// add a command to reclaim a feature that is blocking our build-site void ReclaimFeature(CFeature* f); - /// fix for patrolling cons repairing/resurrecting stuff that's being reclaimed - static void AddUnitToReclaimers(CUnit*); - static void RemoveUnitFromReclaimers(CUnit*); - - /// fix for cons wandering away from their target circle - static void AddUnitToFeatureReclaimers(CUnit*); - static void RemoveUnitFromFeatureReclaimers(CUnit*); - - /// fix for patrolling cons reclaiming stuff that is being resurrected - static void AddUnitToResurrecters(CUnit*); - static void RemoveUnitFromResurrecters(CUnit*); - inline float f3Dist(const float3& a, const float3& b) const { return range3D ? a.distance(b) : a.distance2D(b); } diff --git a/rts/Sim/Units/CommandAI/BuilderCaches.cpp b/rts/Sim/Units/CommandAI/BuilderCaches.cpp new file mode 100644 index 0000000000..0be513ccf8 --- /dev/null +++ b/rts/Sim/Units/CommandAI/BuilderCaches.cpp @@ -0,0 +1,147 @@ +#include "Sim/Units/CommandAI/BuilderCaches.h" +#include "Sim/Misc/TeamHandler.h" +#include "Sim/Units/UnitHandler.h" +#include "Sim/Units/Unit.h" + +// not adding to members, should repopulate itself +spring::unordered_set CBuilderCaches::reclaimers; +spring::unordered_set CBuilderCaches::featureReclaimers; +spring::unordered_set CBuilderCaches::resurrecters; + +std::vector CBuilderCaches::removees; + +void CBuilderCaches::InitStatic() +{ + spring::clear_unordered_set(reclaimers); + spring::clear_unordered_set(featureReclaimers); + spring::clear_unordered_set(resurrecters); +} + +void CBuilderCaches::AddUnitToReclaimers(CUnit* unit) { reclaimers.insert(unit->id); } +void CBuilderCaches::RemoveUnitFromReclaimers(CUnit* unit) { reclaimers.erase(unit->id); } + +void CBuilderCaches::AddUnitToFeatureReclaimers(CUnit* unit) { featureReclaimers.insert(unit->id); } +void CBuilderCaches::RemoveUnitFromFeatureReclaimers(CUnit* unit) { featureReclaimers.erase(unit->id); } + +void CBuilderCaches::AddUnitToResurrecters(CUnit* unit) { resurrecters.insert(unit->id); } +void CBuilderCaches::RemoveUnitFromResurrecters(CUnit* unit) { resurrecters.erase(unit->id); } + + +/** + * Checks if a unit is being reclaimed by a friendly con. + * + * We assume that there will not be a lot of reclaimers, because performance + * would suck if there were. Ideally, reclaimers should be assigned on a + * per-unit basis, but this requires tracking of deaths, which albeit + * already done, is not exactly simple to follow. + * + * TODO easy: store reclaiming units per allyteam + * TODO harder: update reclaimers as they start/finish reclaims and/or die + */ +bool CBuilderCaches::IsUnitBeingReclaimed(const CUnit* unit, const CUnit* friendUnit) +{ + bool retval = false; + + removees.clear(); + removees.reserve(reclaimers.size()); + + for (auto it = reclaimers.begin(); it != reclaimers.end(); ++it) { + const CUnit* u = unitHandler.GetUnit(*it); + const CCommandAI* cai = u->commandAI; + const CCommandQueue& cq = cai->commandQue; + + if (cq.empty()) { + removees.push_back(u->id); + continue; + } + const Command& c = cq.front(); + if (c.GetID() != CMD_RECLAIM || (c.GetNumParams() != 1 && c.GetNumParams() != 5)) { + removees.push_back(u->id); + continue; + } + const int cmdUnitId = (int)c.GetParam(0); + if (cmdUnitId == unit->id && (friendUnit == nullptr || teamHandler.Ally(friendUnit->allyteam, u->allyteam))) { + retval = true; + break; + } + } + + for (auto it = removees.begin(); it != removees.end(); ++it) + RemoveUnitFromReclaimers(unitHandler.GetUnit(*it)); + + return retval; +} + + + +bool CBuilderCaches::IsFeatureBeingReclaimed(int featureId, const CUnit* friendUnit) +{ + bool retval = false; + + removees.clear(); + removees.reserve(featureReclaimers.size()); + + for (auto it = featureReclaimers.begin(); it != featureReclaimers.end(); ++it) { + const CUnit* u = unitHandler.GetUnit(*it); + const CCommandAI* cai = u->commandAI; + const CCommandQueue& cq = cai->commandQue; + + if (cq.empty()) { + removees.push_back(u->id); + continue; + } + const Command& c = cq.front(); + if (c.GetID() != CMD_RECLAIM || (c.GetNumParams() != 1 && c.GetNumParams() != 5)) { + removees.push_back(u->id); + continue; + } + const int cmdFeatureId = (int)c.GetParam(0); + if ((cmdFeatureId - unitHandler.MaxUnits()) == featureId && (friendUnit == nullptr || teamHandler.Ally(friendUnit->allyteam, u->allyteam))) { + retval = true; + break; + } + } + + for (auto it = removees.begin(); it != removees.end(); ++it) + RemoveUnitFromFeatureReclaimers(unitHandler.GetUnit(*it)); + + return retval; +} + + +bool CBuilderCaches::IsFeatureBeingResurrected(int featureId, const CUnit* friendUnit) +{ + bool retval = false; + + removees.clear(); + removees.reserve(resurrecters.size()); + + for (auto it = resurrecters.begin(); it != resurrecters.end(); ++it) { + const CUnit* u = unitHandler.GetUnit(*it); + const CCommandAI* cai = u->commandAI; + const CCommandQueue& cq = cai->commandQue; + + if (cq.empty()) { + removees.push_back(u->id); + continue; + } + const Command& c = cq.front(); + if (c.GetID() != CMD_RESURRECT || c.GetNumParams() != 1) { + removees.push_back(u->id); + continue; + } + const int cmdFeatureId = (int)c.GetParam(0); + if ((cmdFeatureId - unitHandler.MaxUnits()) == featureId && (friendUnit == nullptr || teamHandler.Ally(friendUnit->allyteam, u->allyteam))) { + retval = true; + break; + } + } + + for (auto it = removees.begin(); it != removees.end(); ++it) + RemoveUnitFromResurrecters(unitHandler.GetUnit(*it)); + + return retval; +} + + + diff --git a/rts/Sim/Units/CommandAI/BuilderCaches.h b/rts/Sim/Units/CommandAI/BuilderCaches.h new file mode 100644 index 0000000000..4225955659 --- /dev/null +++ b/rts/Sim/Units/CommandAI/BuilderCaches.h @@ -0,0 +1,50 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ + +#ifndef _BUILDER_CACHES_H_ +#define _BUILDER_CACHES_H_ + +#include "MobileCAI.h" +#include "Sim/Units/BuildInfo.h" +#include "System/Misc/BitwiseEnum.h" +#include "System/UnorderedSet.hpp" + +#include + +class CUnit; +class CBuilder; +class CFeature; +class CSolidObject; +class CWorldObject; +struct Command; +struct UnitDef; + +class CBuilderCaches +{ +public: + /** + * Checks if a unit is being reclaimed by a friendly con. + */ + static void InitStatic(); + static bool IsUnitBeingReclaimed(const CUnit* unit, const CUnit* friendUnit = nullptr); + static bool IsFeatureBeingReclaimed(int featureId, const CUnit* friendUnit = nullptr); + static bool IsFeatureBeingResurrected(int featureId, const CUnit* friendUnit = nullptr); + static spring::unordered_set reclaimers; + static spring::unordered_set featureReclaimers; + static spring::unordered_set resurrecters; + + static std::vector removees; + + /// fix for patrolling cons repairing/resurrecting stuff that's being reclaimed + static void AddUnitToReclaimers(CUnit*); + static void RemoveUnitFromReclaimers(CUnit*); + + /// fix for cons wandering away from their target circle + static void AddUnitToFeatureReclaimers(CUnit*); + static void RemoveUnitFromFeatureReclaimers(CUnit*); + + /// fix for patrolling cons reclaiming stuff that is being resurrected + static void AddUnitToResurrecters(CUnit*); + static void RemoveUnitFromResurrecters(CUnit*); +}; + +#endif // _BUILDER_CACHES_H_ diff --git a/rts/Sim/Units/CommandAI/FactoryCAI.cpp b/rts/Sim/Units/CommandAI/FactoryCAI.cpp index b74a2f3b61..f9065fbf9a 100644 --- a/rts/Sim/Units/CommandAI/FactoryCAI.cpp +++ b/rts/Sim/Units/CommandAI/FactoryCAI.cpp @@ -19,14 +19,28 @@ #include "System/StringUtil.h" #include "System/EventHandler.h" #include "System/Exceptions.h" +#include "Sim/Misc/QuadField.h" +#include "Sim/Units/UnitTypes/Builder.h" +#include "Sim/Features/Feature.h" +#include "Sim/Features/FeatureDef.h" +#include "Sim/Features/FeatureHandler.h" +#include "Sim/Units/CommandAI/BuilderCaches.h" + +#include "Sim/Misc/ModInfo.h" #include "System/Misc/TracyDefs.h" CR_BIND_DERIVED(CFactoryCAI ,CCommandAI , ) CR_REG_METADATA(CFactoryCAI , ( + CR_MEMBER(range3D), + CR_MEMBER(ownerFactory), + CR_MEMBER(randomCounter), CR_MEMBER(newUnitCommands), CR_MEMBER(buildOptions), + CR_MEMBER(lastPC1), + CR_MEMBER(lastPC2), + CR_MEMBER(lastPC3), CR_PREALLOC(GetPreallocContainer) )) @@ -50,12 +64,26 @@ static std::string GetUnitDefBuildOptionToolTip(const UnitDef* ud, bool disabled -CFactoryCAI::CFactoryCAI(): CCommandAI() +CFactoryCAI::CFactoryCAI(): + ownerFactory(nullptr), + lastPC1(-1), + lastPC2(-1), + lastPC3(-1), + range3D(true), + randomCounter(0), + CCommandAI() {} -CFactoryCAI::CFactoryCAI(CUnit* owner): CCommandAI(owner) +CFactoryCAI::CFactoryCAI(CUnit* owner): + CCommandAI(owner), + randomCounter(0), + lastPC1(-1), + lastPC2(-1), + lastPC3(-1), + range3D(owner->unitDef->buildRange3D) { + ownerFactory = static_cast(owner); commandQue.SetQueueType(CCommandQueue::BuildQueueType); newUnitCommands.SetQueueType(CCommandQueue::NewUnitQueueType); @@ -111,6 +139,45 @@ CFactoryCAI::CFactoryCAI(CUnit* owner): CCommandAI(owner) possibleCommands.push_back(commandDescriptionCache.GetPtr(std::move(c))); } + if (owner->unitDef->canRepair) { + SCommandDescription c; + + c.id = CMD_REPAIR; + c.type = CMDTYPE_ICON_UNIT_OR_AREA; + + c.action = "repair"; + c.name = "Repair"; + c.tooltip = c.name + ": Repairs another unit"; + c.mouseicon = c.name; + possibleCommands.push_back(commandDescriptionCache.GetPtr(std::move(c))); + } + else if (owner->unitDef->canAssist) { + SCommandDescription c; + + c.id = CMD_REPAIR; + c.type = CMDTYPE_ICON_UNIT_OR_AREA; + + c.action = "assist"; + c.name = "Assist"; + c.tooltip = c.name + ": Help build something"; + c.mouseicon = c.name; + possibleCommands.push_back(commandDescriptionCache.GetPtr(std::move(c))); + } + + if (owner->unitDef->canReclaim) { + SCommandDescription c; + + c.id = CMD_RECLAIM; + c.type = CMDTYPE_ICON_UNIT_FEATURE_OR_AREA; + + c.action = "reclaim"; + c.name = "Reclaim"; + c.tooltip = c.name + ": Sucks in the metal/energy content of a unit/feature\nand adds it to your storage"; + c.mouseicon = c.name; + possibleCommands.push_back(commandDescriptionCache.GetPtr(std::move(c))); + } + + CFactory* fac = static_cast(owner); for (const auto& bi: fac->unitDef->buildOptions) { @@ -152,6 +219,335 @@ static constexpr int GetCountMultiplierFromOptions(int opts) return ret; } + +void CFactoryCAI::ExecuteGuard(Command& c) +{ + RECOIL_DETAILED_TRACY_ZONE; + if (!owner->unitDef->canGuard) + return; + + CUnit* guardee = unitHandler.GetUnit(c.GetParam(0)); + + if (guardee == nullptr) { + FinishCommand(); + return; + } + + if (guardee == owner) { + FinishCommand(); + return; + } + if (UpdateTargetLostTimer(guardee->id) == 0) { + FinishCommand(); + return; + } + if (guardee->outOfMapTime > (GAME_SPEED * 5)) { + FinishCommand(); + return; + } + + if (CBuilder* b = dynamic_cast(guardee)) { + if (b->terraforming) { + if (IsInBuildRange(b->terraformCenter, b->terraformRadius * 0.7f)) { + //ownerFactory->HelpTerraform(b); + } + return; + } else if (b->curReclaim && owner->unitDef->canReclaim) { + if (!ReclaimObject(b->curReclaim)) { + //StopMove(); + } + return; + } else if (b->curResurrect && owner->unitDef->canResurrect) { + /*if (!ResurrectObject(b->curResurrect)) { + //StopMove(); + }*/ + return; + } else { + ownerFactory->StopBuild(); + } + + const bool pushRepairCommand = + ( b->curBuild != nullptr) && + ( b->curBuild->soloBuilder == nullptr || b->curBuild->soloBuilder == owner) && + (( b->curBuild->beingBuilt && owner->unitDef->canAssist) || + ( !b->curBuild->beingBuilt && owner->unitDef->canRepair)); + + if (pushRepairCommand) { + Command nc(CMD_REPAIR, c.GetOpts(), b->curBuild->id); + + commandQue.push_front(nc); + inCommand = false; + SlowUpdate(); + return; + } + } + + if (CFactory* fac = dynamic_cast(guardee)) { + const bool pushRepairCommand = + ( fac->curBuild != nullptr) && + ( fac->curBuild->soloBuilder == nullptr || fac->curBuild->soloBuilder == owner) && + (( fac->curBuild->beingBuilt && owner->unitDef->canAssist) || + (!fac->curBuild->beingBuilt && owner->unitDef->canRepair)); + + if (pushRepairCommand) { + commandQue.push_front(Command(CMD_REPAIR, c.GetOpts(), fac->curBuild->id)); + inCommand = false; + // SlowUpdate(); + return; + } + } + + if (!(c.GetOpts() & CONTROL_KEY) && CBuilderCaches::IsUnitBeingReclaimed(guardee, owner)) + return; + + const float3 pos = guardee->pos; + const float radius = (guardee->immobile) ? guardee->buildeeRadius : guardee->buildeeRadius * 0.8f; // in case of mobile units reduce radius a bit + + if (IsInBuildRange(pos, radius)) { + const bool pushRepairCommand = + ( guardee->health < guardee->maxHealth) && + ( guardee->soloBuilder == nullptr || guardee->soloBuilder == owner) && + (( guardee->beingBuilt && owner->unitDef->canAssist) || + (!guardee->beingBuilt && owner->unitDef->canRepair)); + + if (pushRepairCommand) { + commandQue.push_front(Command(CMD_REPAIR, c.GetOpts(), guardee->id)); + inCommand = false; + return; + } + + //NonMoving(); + } +} + + +void CFactoryCAI::ExecuteRepair(Command& c) +{ + RECOIL_DETAILED_TRACY_ZONE; + // not all builders are repair-capable by default + if (!owner->unitDef->canRepair) + return; + + if (c.GetNumParams() == 1 || c.GetNumParams() == 5) { + // repair unit + CUnit* unit = unitHandler.GetUnit(c.GetParam(0)); + + if (unit == nullptr) { + FinishCommand(); + return; + } + + if (tempOrder && owner->moveState <= MOVESTATE_MANEUVER) { + // limit how far away we go when not roaming + if (LinePointDist(commandPos1, commandPos2, unit->pos) > std::max(500.0f, GetBuildRange(unit->buildeeRadius))) { + FinishCommand(); + return; + } + } + + if (c.GetNumParams() == 5) { + if (!IsInBuildRange(unit)) { + FinishCommand(); + return; + } + /*const float3& pos = c.GetPos(1); + const float radius = c.GetParam(4) + 100.0f; // do not walk too far outside repair area + + if ((pos - unit->pos).SqLength2D() > radius * radius || + (unit->IsMoving() && ((c.IsInternalOrder() && !TargetInterceptable(unit, unit->speed.Length2D())) || ownerFactory->curBuild == unit) + && !IsInBuildRange(unit))) { + FinishCommand(); + return; + }*/ + } + + // do not consider units under construction irreparable + // even if they can be repaired + bool canRepairUnit = true; + canRepairUnit &= ((unit->beingBuilt) || (unit->unitDef->repairable && (unit->health < unit->maxHealth))); + canRepairUnit &= ((unit != owner) || owner->unitDef->canSelfRepair); + canRepairUnit &= (!unit->soloBuilder || (unit->soloBuilder == owner)); + canRepairUnit &= (!c.IsInternalOrder() || (c.GetOpts() & CONTROL_KEY) || !CBuilderCaches::IsUnitBeingReclaimed(unit, owner)); + canRepairUnit &= (UpdateTargetLostTimer(unit->id) != 0); + + if (canRepairUnit) { + if (IsInBuildRange(unit)) + ownerFactory->SetRepairTarget(unit); + } else { + FinishCommand(); + } + } else if (c.GetNumParams() == 4) { + // area repair + const float3 pos = c.GetPos(0); + const float radius = c.GetParam(3); + + ownerFactory->StopBuild(); + if (FindRepairTargetAndRepair(pos, radius, c.GetOpts(), false, (c.GetOpts() & META_KEY))) { + inCommand = false; + SlowUpdate(); + return; + } + + if (!(c.GetOpts() & ALT_KEY)) + FinishCommand(); + + } else { + FinishCommand(); + } + +} + +void CFactoryCAI::ExecuteFight(Command& c) +{ + RECOIL_DETAILED_TRACY_ZONE; + assert(c.IsInternalOrder() || owner->unitDef->canFight); + + if (tempOrder) { + tempOrder = false; + inCommand = true; + } + if (c.GetNumParams() < 3) { + LOG_L(L_ERROR, "[BuilderCAI::%s][f=%d][id=%d][#c.params=%d min=3]", __func__, gs->frameNum, owner->id, c.GetNumParams()); + return; + } + + if (c.GetNumParams() >= 6) { + if (!inCommand) + commandPos1 = c.GetPos(3); + + } else { + // Some hackery to make sure the line (commandPos1,commandPos2) is NOT + // rotated (only shortened) if we reach this because the previous return + // fight command finished by the 'if((curPos-pos).SqLength2D()<(64*64)){' + // condition, but is actually updated correctly if you click somewhere + // outside the area close to the line (for a new command). + if (f3SqDist(owner->pos, commandPos1 = ClosestPointOnLine(commandPos1, commandPos2, owner->pos)) > Square(96.0f)) + commandPos1 = owner->pos; + } + + float3 pos = c.GetPos(0); + if (!inCommand) { + inCommand = true; + commandPos2 = pos; + } + + float3 curPosOnLine = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); + + if (c.GetNumParams() >= 6) + pos = curPosOnLine; + + /*if (pos != owner->moveType->goalPos) + SetGoal(pos, owner->pos);*/ + + const UnitDef* ownerDef = owner->unitDef; + + const bool resurrectMode = !!(c.GetOpts() & ALT_KEY); + const bool reclaimEnemyMode = !!(c.GetOpts() & META_KEY); + const bool reclaimEnemyOnlyMode = (c.GetOpts() & CONTROL_KEY) && (c.GetOpts() & META_KEY); + + ReclaimOption recopt; + if (resurrectMode ) recopt |= REC_NONREZ; + if (reclaimEnemyMode ) recopt |= REC_ENEMY; + if (reclaimEnemyOnlyMode) recopt |= REC_ENEMYONLY; + + const float searchRadius = (owner->immobile ? 0.0f : (300.0f * owner->moveState)) + ownerFactory->buildDistance; + + // Priority 1: Repair + if (!reclaimEnemyOnlyMode && (ownerDef->canRepair || ownerDef->canAssist) && FindRepairTargetAndRepair(curPosOnLine, searchRadius, c.GetOpts(), true, resurrectMode)){ + tempOrder = true; + inCommand = false; + + if (lastPC1 != gs->frameNum) { //avoid infinite loops + lastPC1 = gs->frameNum; + SlowUpdate(); + } + + return; + } + + // Priority 2: Resurrect (optional) + /*if (!reclaimEnemyOnlyMode && resurrectMode && ownerDef->canResurrect && FindResurrectableFeatureAndResurrect(curPosOnLine, searchRadius, c.GetOpts(), false)) { + tempOrder = true; + inCommand = false; + + if (lastPC2 != gs->frameNum) { //avoid infinite loops + lastPC2 = gs->frameNum; + SlowUpdate(); + } + + return; + }*/ + + // Priority 3: Reclaim / reclaim non resurrectable (optional) / reclaim enemy units (optional) + if (ownerDef->canReclaim && FindReclaimTargetAndReclaim(curPosOnLine, searchRadius, c.GetOpts(), recopt)) { + tempOrder = true; + inCommand = false; + + if (lastPC3 != gs->frameNum) { //avoid infinite loops + lastPC3 = gs->frameNum; + SlowUpdate(); + } + + return; + } + + if (f3SqDist(owner->pos, pos) < Square(64.0f)) { + FinishCommand(); + return; + } + + /*if (owner->HaveTarget() && owner->moveType->progressState != AMoveType::Done) { + StopMove(); + } else { + SetGoal(owner->moveType->goalPos, owner->pos); + }*/ +} + + +bool CFactoryCAI::ReclaimObject(CSolidObject* object) { + RECOIL_DETAILED_TRACY_ZONE; + if (IsInBuildRange(object)) { + ownerFactory->SetReclaimTarget(object); + return true; + } + + return false; +} + +/* +bool CFactoryCAI::ResurrectObject(CFeature *feature) { + RECOIL_DETAILED_TRACY_ZONE; + if (IsInBuildRange(feature)) { + ownerFactory->SetResurrectTarget(feature); + return true; + } + + return false; +}*/ + + +float CFactoryCAI::GetBuildRange(const float targetRadius) const +{ + RECOIL_DETAILED_TRACY_ZONE; + return (ownerFactory->buildDistance); +} + + +bool CFactoryCAI::IsInBuildRange(const CWorldObject* obj) const +{ + RECOIL_DETAILED_TRACY_ZONE; + return IsInBuildRange(obj->pos, obj->buildeeRadius); +} + +bool CFactoryCAI::IsInBuildRange(const float3& objPos, const float objRadius) const +{ + RECOIL_DETAILED_TRACY_ZONE; + const float immDistSqr = f3SqDist(owner->pos, objPos); + const float buildDist = GetBuildRange(objRadius); + + return (immDistSqr <= (buildDist * buildDist)); +} + void CFactoryCAI::GiveCommandReal(const Command& c, bool fromSynced) { RECOIL_DETAILED_TRACY_ZONE; @@ -161,6 +557,10 @@ void CFactoryCAI::GiveCommandReal(const Command& c, bool fromSynced) if ((cmdID != CMD_MOVE) && !AllowedCommand(c, fromSynced)) return; + CCommandQueue* queue = &commandQue; + if (buildOptions.size() > 0) + queue = &newUnitCommands; + auto boi = buildOptions.find(cmdID); // not a build order (or a build order we do not support, eg. if multiple @@ -185,30 +585,30 @@ void CFactoryCAI::GiveCommandReal(const Command& c, bool fromSynced) } if (!(c.GetOpts() & SHIFT_KEY)) { - waitCommandsAI.ClearUnitQueue(owner, newUnitCommands); + waitCommandsAI.ClearUnitQueue(owner, *queue); CCommandAI::ClearCommandDependencies(); - newUnitCommands.clear(); + queue->clear(); } CCommandAI::AddCommandDependency(c); if (cmdID != CMD_STOP) { if ((cmdID == CMD_WAIT) || (cmdID == CMD_SELFD)) { - if (!newUnitCommands.empty() && (newUnitCommands.back().GetID() == cmdID)) { + if (!queue->empty() && (queue->back().GetID() == cmdID)) { if (cmdID == CMD_WAIT) { waitCommandsAI.RemoveWaitCommand(owner, c); } - newUnitCommands.pop_back(); + queue->pop_back(); } else { - newUnitCommands.push_back(c); + queue->push_back(c); } } else { bool dummy; - if (CancelCommands(c, newUnitCommands, dummy) > 0) { + if (CancelCommands(c, *queue, dummy) > 0) { return; } else { - if (GetOverlapQueued(c, newUnitCommands).empty()) { - newUnitCommands.push_back(c); + if (GetOverlapQueued(c, *queue).empty()) { + queue->push_back(c); } else { return; } @@ -217,7 +617,7 @@ void CFactoryCAI::GiveCommandReal(const Command& c, bool fromSynced) } // the first new-unit build order can not be WAIT or SELFD - while (!newUnitCommands.empty()) { + while (!queue->empty() && queue == &newUnitCommands) { const Command& newUnitCommand = newUnitCommands.front(); const int id = newUnitCommand.GetID(); @@ -396,6 +796,10 @@ void CFactoryCAI::SlowUpdate() } else { // regular order (move/wait/etc) switch (c.GetID()) { + case CMD_FIGHT: { ExecuteFight(c); } break; + case CMD_REPAIR: { ExecuteRepair(c); } break; + case CMD_RECLAIM: { ExecuteReclaim(c); } break; + case CMD_GUARD: { ExecuteGuard(c); } break; case CMD_STOP: { ExecuteStop(c); } break; @@ -421,6 +825,267 @@ void CFactoryCAI::ExecuteStop(Command& c) commandQue.pop_front(); } +void CFactoryCAI::ExecuteReclaim(Command& c) +{ + RECOIL_DETAILED_TRACY_ZONE; + // not all builders are reclaim-capable by default + if (!owner->unitDef->canReclaim) + return; + + if (c.GetNumParams() == 1 || c.GetNumParams() == 5) { + const int signedId = (int) c.GetParam(0); + + if (signedId < 0) { + LOG_L(L_WARNING, "Trying to reclaim unit or feature with id < 0 (%i), aborting.", signedId); + return; + } + + const unsigned int uid = signedId; + + const bool checkForBetterTarget = ((++randomCounter % 5) == 0); + if (checkForBetterTarget && c.IsInternalOrder() && (c.GetNumParams() >= 5)) { + // regular check if there is a closer reclaim target + CSolidObject* obj; + + if (uid >= unitHandler.MaxUnits()) { + obj = featureHandler.GetFeature(uid - unitHandler.MaxUnits()); + } else { + obj = unitHandler.GetUnit(uid); + } + + if (obj) { + const float3& pos = c.GetPos(1); + const float radius = c.GetParam(4); + const float curdist = pos.SqDistance2D(obj->pos); + + const bool recUnits = !!(c.GetOpts() & META_KEY); + const bool recEnemyOnly = (c.GetOpts() & META_KEY) && (c.GetOpts() & CONTROL_KEY); + const bool recSpecial = !!(c.GetOpts() & CONTROL_KEY); + + ReclaimOption recopt = REC_NORESCHECK; + if (recUnits) recopt |= REC_UNITS; + if (recEnemyOnly) recopt |= REC_ENEMYONLY; + if (recSpecial) recopt |= REC_SPECIAL; + + const int rid = FindReclaimTarget(pos, radius, c.GetOpts(), recopt, curdist); + if ((rid > 0) && (rid != uid)) { + FinishCommand(); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + return; + } + } + } + + if (uid >= unitHandler.MaxUnits()) { // reclaim feature + CFeature* feature = featureHandler.GetFeature(uid - unitHandler.MaxUnits()); + + if (feature != nullptr) { + bool featureBeingResurrected = CBuilderCaches::IsFeatureBeingResurrected(feature->id, owner); + featureBeingResurrected &= c.IsInternalOrder(); + + if (featureBeingResurrected || !ReclaimObject(feature)) { + FinishCommand(); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + } else { + CBuilderCaches::AddUnitToFeatureReclaimers(owner); + } + } else { + FinishCommand(); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + } + + CBuilderCaches::RemoveUnitFromReclaimers(owner); + } else { // reclaim unit + CUnit* unit = unitHandler.GetUnit(uid); + + if (unit != nullptr && c.GetNumParams() == 5) { + const float3& pos = c.GetPos(1); + const float radius = c.GetParam(4) + 100.0f; // do not walk too far outside reclaim area + + const bool outOfReclaimRange = + (pos.SqDistance2D(unit->pos) > radius * radius) || + (ownerFactory->curReclaim == unit && unit->IsMoving() && !IsInBuildRange(unit)); + const bool busyAlliedBuilder = + unit->unitDef->builder && + !unit->commandAI->commandQue.empty() && + teamHandler.Ally(owner->allyteam, unit->allyteam); + + if (outOfReclaimRange || busyAlliedBuilder) { + FinishCommand(); + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + return; + } + } + + if (unit != nullptr && unit != owner && unit->unitDef->reclaimable && UpdateTargetLostTimer(unit->id) && unit->AllowedReclaim(owner)) { + if (!ReclaimObject(unit)) { + FinishCommand(); + } else { + CBuilderCaches::AddUnitToReclaimers(owner); + } + } else { + CBuilderCaches::RemoveUnitFromReclaimers(owner); + FinishCommand(); + } + + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + } + } else if (c.GetNumParams() == 4) { + // area reclaim + const float3 pos = c.GetPos(0); + const float radius = c.GetParam(3); + const bool recUnits = !!(c.GetOpts() & META_KEY); + const bool recEnemyOnly = (c.GetOpts() & META_KEY) && (c.GetOpts() & CONTROL_KEY); + const bool recSpecial = !!(c.GetOpts() & CONTROL_KEY); + + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + ownerFactory->StopBuild(); + + ReclaimOption recopt = REC_NORESCHECK; + if (recUnits) recopt |= REC_UNITS; + if (recEnemyOnly) recopt |= REC_ENEMYONLY; + if (recSpecial) recopt |= REC_SPECIAL; + + if (FindReclaimTargetAndReclaim(pos, radius, c.GetOpts(), recopt)) { + inCommand = false; + SlowUpdate(); + return; + } + + if (!(c.GetOpts() & ALT_KEY)) + FinishCommand(); + + } else { + // wrong number of parameters + CBuilderCaches::RemoveUnitFromReclaimers(owner); + CBuilderCaches::RemoveUnitFromFeatureReclaimers(owner); + FinishCommand(); + } +} + +bool CFactoryCAI::FindReclaimTargetAndReclaim(const float3& pos, float radius, unsigned char cmdopt, ReclaimOption recoptions) +{ + RECOIL_DETAILED_TRACY_ZONE; + const int rid = FindReclaimTarget(pos, radius, cmdopt, recoptions); + + if (rid < 0) + return false; + + // FIGHT commands always resource check + if (!(recoptions & REC_NORESCHECK)) + PushOrUpdateReturnFight(); + + Command c(CMD_RECLAIM, cmdopt | INTERNAL_ORDER, rid, pos); + c.PushParam(radius); + commandQue.push_front(c); + return true; +} + + +int CFactoryCAI::FindReclaimTarget(const float3& pos, float radius, unsigned char cmdopt, ReclaimOption recoptions, float bestStartDist) const +{ + RECOIL_DETAILED_TRACY_ZONE; + const bool noResCheck = recoptions & REC_NORESCHECK; + const bool recUnits = recoptions & REC_UNITS; + const bool recNonRez = recoptions & REC_NONREZ; + const bool recEnemy = recoptions & REC_ENEMY; + const bool recEnemyOnly = recoptions & REC_ENEMYONLY; + const bool recSpecial = recoptions & REC_SPECIAL; + + const CSolidObject* best = nullptr; + float bestDist = bestStartDist; + bool stationary = false; + int rid = -1; + + if (recUnits || recEnemy || recEnemyOnly) { + QuadFieldQuery qfQuery; + quadField.GetUnitsExact(qfQuery, pos, radius, false); + + for (const CUnit* u: *qfQuery.units) { + if (u == owner) + continue; + if (!u->unitDef->reclaimable) + continue; + if (!((!recEnemy && !recEnemyOnly) || !teamHandler.Ally(owner->allyteam, u->allyteam))) + continue; + if (!(u->losStatus[owner->allyteam] & (LOS_INRADAR|LOS_INLOS))) + continue; + + // reclaim stationary targets first + if (u->IsMoving() && stationary) + continue; + + // do not reclaim friendly builders that are busy + if (u->unitDef->builder && teamHandler.Ally(owner->allyteam, u->allyteam) && !u->commandAI->commandQue.empty()) + continue; + + const float dist = f3SqDist(u->pos, owner->pos); + if (dist < bestDist || (!stationary && !u->IsMoving())) { + if (owner->immobile && !IsInBuildRange(u)) + continue; + + if (!stationary && !u->IsMoving()) + stationary = true; + + bestDist = dist; + best = u; + } + } + if (best != nullptr) + rid = best->id; + } + + if ((!best || !stationary) && !recEnemyOnly) { + best = nullptr; + const CTeam* team = teamHandler.Team(owner->team); + QuadFieldQuery qfQuery; + quadField.GetFeaturesExact(qfQuery, pos, radius, false); + bool metal = false; + + for (const CFeature* f: *qfQuery.features) { + if (!f->def->reclaimable) + continue; + if (!recSpecial && !f->def->autoreclaim) + continue; + + if (recNonRez && f->udef != nullptr) + continue; + + if (recSpecial && metal && f->defResources.metal <= 0.0) + continue; + + const float dist = f3SqDist(f->pos, owner->pos); + + if ((dist < bestDist || (recSpecial && !metal && f->defResources.metal > 0.0)) && + (noResCheck || + ((f->defResources.metal > 0.0f) && (team->res.metal < team->resStorage.metal)) || + ((f->defResources.energy > 0.0f) && (team->res.energy < team->resStorage.energy))) + ) { + if (!f->IsInLosForAllyTeam(owner->allyteam)) + continue; + + if (!owner->unitDef->canmove && !IsInBuildRange(f)) + continue; + + /*if (CBuilderCaches::IsFeatureBeingResurrected(f->id, owner)) + continue;*/ + + metal |= (recSpecial && !metal && f->defResources.metal > 0.0f); + + bestDist = dist; + best = f; + } + } + + if (best != nullptr) + rid = unitHandler.MaxUnits() + best->id; + } + + return rid; +} int CFactoryCAI::GetDefaultCmd(const CUnit* pointed, const CFeature* feature) { @@ -461,3 +1126,117 @@ void CFactoryCAI::UpdateIconName(int cmdID, const int& numQueued) selectedUnitsHandler.PossibleCommandChange(owner); } + + +bool CFactoryCAI::FindRepairTargetAndRepair( + const float3& pos, + float radius, + unsigned char options, + bool attackEnemy, + bool builtOnly +) { + RECOIL_DETAILED_TRACY_ZONE; + QuadFieldQuery qfQuery; + quadField.GetUnitsExact(qfQuery, pos, radius, false); + const CUnit* bestUnit = nullptr; + + const float maxSpeed = owner->moveType->GetMaxSpeed(); + float unitSpeed = 0.0f; + float bestDist = 1.0e30f; + + bool haveEnemy = false; + bool trySelfRepair = false; + bool stationary = false; + + for (const CUnit* unit: *qfQuery.units) { + if (teamHandler.Ally(owner->allyteam, unit->allyteam)) { + if (!haveEnemy && (unit->health < unit->maxHealth)) { + // don't help allies build unless set on roam + if (unit->beingBuilt && owner->team != unit->team && (owner->moveState != MOVESTATE_ROAM)) + continue; + + // don't help factories produce units when set on hold pos + if (unit->beingBuilt && unit->moveDef != nullptr && (owner->moveState == MOVESTATE_HOLDPOS)) + continue; + + // don't assist or repair if can't assist or repair + if (!ownerFactory->CanAssistUnit(unit) && !ownerFactory->CanRepairUnit(unit)) + continue; + + if (unit == owner) { + trySelfRepair = true; + continue; + } + // repair stationary targets first + if (unit->IsMoving() && stationary) + continue; + + if (builtOnly && unit->beingBuilt) + continue; + + float dist = f3SqDist(unit->pos, owner->pos); + + // avoid targets that are faster than our max speed + if (unit->IsMoving()) { + unitSpeed = unit->speed.Length2D(); + dist *= (1.0f + std::max(unitSpeed - maxSpeed, 0.0f)); + } + if (dist < bestDist || (!stationary && !unit->IsMoving())) { + // dont lock-on to units outside of our reach (for immobile builders) + //if ((owner->immobile || (unit->IsMoving() && !TargetInterceptable(unit, unitSpeed))) && !IsInBuildRange(unit)) + continue; + + // don't repair stuff that's being reclaimed + /*if (!(options & CONTROL_KEY) && CBuilderCaches::IsUnitBeingReclaimed(unit, owner)) + continue; + + stationary |= (!stationary && !unit->IsMoving()); + + bestDist = dist; + bestUnit = unit;*/ + } + } + } else { + if (unit->IsNeutral()) + continue; + + if (!attackEnemy || !owner->unitDef->canAttack || (owner->maxRange <= 0) ) + continue; + + if (!(unit->losStatus[owner->allyteam] & (LOS_INRADAR | LOS_INLOS))) + continue; + + const float dist = f3SqDist(unit->pos, owner->pos); + + if ((dist < bestDist) || !haveEnemy) { + if (owner->immobile && ((dist - unit->buildeeRadius) > owner->maxRange)) + continue; + + bestUnit = unit; + bestDist = dist; + haveEnemy = true; + } + } + } + + if (bestUnit == nullptr) { + if (!trySelfRepair || !owner->unitDef->canSelfRepair || (owner->health >= owner->maxHealth)) + return false; + + bestUnit = owner; + } + + if (!haveEnemy) { + if (attackEnemy) + PushOrUpdateReturnFight(); + + Command c(CMD_REPAIR, options | INTERNAL_ORDER, bestUnit->id, pos); + c.PushParam(radius); + commandQue.push_front(c); + } else { + PushOrUpdateReturnFight(); // attackEnemy must be true + commandQue.push_front(Command(CMD_ATTACK, options | INTERNAL_ORDER, bestUnit->id)); + } + + return true; +} diff --git a/rts/Sim/Units/CommandAI/FactoryCAI.h b/rts/Sim/Units/CommandAI/FactoryCAI.h index ff916be728..b1b8d9a0bf 100644 --- a/rts/Sim/Units/CommandAI/FactoryCAI.h +++ b/rts/Sim/Units/CommandAI/FactoryCAI.h @@ -7,10 +7,14 @@ #include "CommandQueue.h" #include +#include "System/Misc/BitwiseEnum.h" #include "System/UnorderedMap.hpp" class CUnit; +class CFactory; class CFeature; +class CWorldObject; +class CSolidObject; struct Command; class CFactoryCAI : public CCommandAI @@ -33,12 +37,60 @@ class CFactoryCAI : public CCommandAI void FactoryFinishBuild(const Command& command); void ExecuteStop(Command& c); + virtual void ExecuteGuard(Command& c); + virtual void ExecuteRepair(Command& c); + virtual void ExecuteReclaim(Command& c); + virtual void ExecuteFight(Command& c); + bool ReclaimObject(CSolidObject* o); + //bool ResurrectObject(CFeature* feature); + CCommandQueue newUnitCommands; spring::unordered_map buildOptions; + bool tempOrder; + float3 commandPos1; + float3 commandPos2; + + int lastPC1; ///< helps avoid infinite loops + int lastPC2; + int lastPC3; + + void PushOrUpdateReturnFight() { + CCommandAI::PushOrUpdateReturnFight(commandPos1, commandPos2); + } + private: + int randomCounter; ///< used to balance intervals of time intensive ai optimizations + enum ReclaimOptions { + REC_NORESCHECK = 1<<0, + REC_UNITS = 1<<1, + REC_NONREZ = 1<<2, + REC_ENEMY = 1<<3, + REC_ENEMYONLY = 1<<4, + REC_SPECIAL = 1<<5 + }; + typedef Bitwise::BitwiseEnum ReclaimOption; + int FindReclaimTarget(const float3& pos, float radius, unsigned char cmdopt, ReclaimOption recoptions, float bestStartDist = 1.0e30f) const; + bool FindReclaimTargetAndReclaim(const float3& pos, float radius, unsigned char cmdopt, ReclaimOption recoptions); + +private: + CFactory* ownerFactory; + bool range3D; + bool IsInBuildRange(const CWorldObject* obj) const; + bool IsInBuildRange(const float3& pos, const float radius) const; + float GetBuildRange(const float targetRadius) const; void UpdateIconName(int id, const int& numQueued); + + inline float f3Dist(const float3& a, const float3& b) const { + return range3D ? a.distance(b) : a.distance2D(b); + } + + inline float f3SqDist(const float3& a, const float3& b) const { + return range3D ? a.SqDistance(b) : a.SqDistance2D(b); + } + + bool FindRepairTargetAndRepair(const float3& pos, float radius, unsigned char options, bool attackEnemy, bool builtOnly); }; #endif // _FACTORY_AI_H_ diff --git a/rts/Sim/Units/Unit.cpp b/rts/Sim/Units/Unit.cpp index 9f1ec62fbd..917e3d832e 100644 --- a/rts/Sim/Units/Unit.cpp +++ b/rts/Sim/Units/Unit.cpp @@ -20,6 +20,7 @@ #include "CommandAI/CommandAI.h" #include "CommandAI/FactoryCAI.h" #include "CommandAI/MobileCAI.h" +#include "CommandAI/BuilderCaches.h" #include "ExternalAI/EngineOutHandler.h" #include "Game/GameHelper.h" @@ -164,7 +165,7 @@ void CUnit::InitStatic() SetExpReloadScale(modInfo.unitExpReloadScale); SetExpGrade(0.0f); - CBuilderCAI::InitStatic(); + CBuilderCaches::InitStatic(); unitToolTipMap.Clear(); } diff --git a/rts/Sim/Units/UnitDefHandler.cpp b/rts/Sim/Units/UnitDefHandler.cpp index 4037c7a8f8..60dd95915c 100644 --- a/rts/Sim/Units/UnitDefHandler.cpp +++ b/rts/Sim/Units/UnitDefHandler.cpp @@ -236,8 +236,8 @@ void CUnitDefHandler::SanitizeUnitDefs() { for (auto &ud : unitDefsVector) { // Factories cannot assist another builder - if (ud.IsFactoryUnit()) - ud.canAssist = false; + //if (ud.IsFactoryUnit()) + // ud.canAssist = false; // Make sure the wreck name refers to an existent feature if (ud.wreckName != "") { diff --git a/rts/Sim/Units/UnitHandler.cpp b/rts/Sim/Units/UnitHandler.cpp index 8bf40e1bce..8aa06bc809 100644 --- a/rts/Sim/Units/UnitHandler.cpp +++ b/rts/Sim/Units/UnitHandler.cpp @@ -100,10 +100,11 @@ void CUnitHandler::Init() { GeneralMoveSystem::Init(); UnitTrapCheckSystem::Init(); - static_assert(sizeof(CBuilder) >= sizeof(CUnit ), ""); - static_assert(sizeof(CBuilder) >= sizeof(CBuilding ), ""); - static_assert(sizeof(CBuilder) >= sizeof(CExtractorBuilding), ""); - static_assert(sizeof(CBuilder) >= sizeof(CFactory ), ""); + static_assert(sizeof(CFactory) >= sizeof(CUnit ), ""); + static_assert(sizeof(CFactory) >= sizeof(CBuilding ), ""); + static_assert(sizeof(CFactory) >= sizeof(CExtractorBuilding), ""); + static_assert(sizeof(CFactory) >= sizeof(CFactory ), ""); + static_assert(sizeof(CFactory) >= sizeof(CBuilder ), ""); { // set the global (runtime-constant) unit-limit as the sum diff --git a/rts/Sim/Units/UnitLoader.cpp b/rts/Sim/Units/UnitLoader.cpp index fdf69188bb..ea82fba1ee 100644 --- a/rts/Sim/Units/UnitLoader.cpp +++ b/rts/Sim/Units/UnitLoader.cpp @@ -49,8 +49,9 @@ CCommandAI* CUnitLoader::NewCommandAI(CUnit* u, const UnitDef* ud) static_assert(sizeof( CMobileCAI) <= sizeof(u->caiMemBuffer), ""); static_assert(sizeof( CCommandAI) <= sizeof(u->caiMemBuffer), ""); - if (ud->IsFactoryUnit()) + if (ud->IsFactoryUnit()) { return (new (u->caiMemBuffer) CFactoryCAI(u)); + } if (ud->IsMobileBuilderUnit() || ud->IsStaticBuilderUnit()) return (new (u->caiMemBuffer) CBuilderCAI(u)); @@ -62,8 +63,9 @@ CCommandAI* CUnitLoader::NewCommandAI(CUnit* u, const UnitDef* ud) if (ud->IsAirUnit()) return (new (u->caiMemBuffer) CMobileCAI(u)); - if (ud->IsGroundUnit() || ud->IsTransportUnit()) + if (ud->IsGroundUnit() || ud->IsTransportUnit()) { return (new (u->caiMemBuffer) CMobileCAI(u)); + } return (new (u->caiMemBuffer) CCommandAI(u)); } diff --git a/rts/Sim/Units/UnitMemPool.h b/rts/Sim/Units/UnitMemPool.h index b1d613406f..be694d1782 100644 --- a/rts/Sim/Units/UnitMemPool.h +++ b/rts/Sim/Units/UnitMemPool.h @@ -4,12 +4,13 @@ #define UNIT_MEMPOOL_H #include "UnitTypes/Builder.h" +#include "UnitTypes/Factory.h" #include "Sim/Misc/GlobalConstants.h" #include "System/MemPoolTypes.h" #if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64)) // CBuilder is (currently) the largest derived unit-type -typedef StaticMemPoolT UnitMemPool; +typedef StaticMemPoolT UnitMemPool; #else typedef FixedDynMemPoolT UnitMemPool; #endif diff --git a/rts/Sim/Units/UnitTypes/Builder.cpp b/rts/Sim/Units/UnitTypes/Builder.cpp index ce7596ea00..6e5d9d3c50 100644 --- a/rts/Sim/Units/UnitTypes/Builder.cpp +++ b/rts/Sim/Units/UnitTypes/Builder.cpp @@ -28,6 +28,7 @@ #include "System/EventHandler.h" #include "System/Log/ILog.h" #include "System/Sound/ISoundChannels.h" +#include "Sim/Units/CommandAI/BuilderCaches.h" #include "System/Misc/TracyDefs.h" @@ -388,7 +389,6 @@ bool CBuilder::UpdateReclaim(const Command& fCommand) bool CBuilder::UpdateResurrect(const Command& fCommand) { RECOIL_DETAILED_TRACY_ZONE; - CBuilderCAI* cai = static_cast(commandAI); CFeature* curResurrectee = curResurrect; if (curResurrectee == nullptr || f3SqDist(curResurrectee->pos, pos) >= Square(buildDistance + curResurrectee->buildeeRadius) || !inBuildStance) @@ -440,7 +440,7 @@ bool CBuilder::UpdateResurrect(const Command& fCommand) resurrectee->SetSoloBuilder(this, resurrecteeDef); resurrectee->SetHeading(curResurrectee->heading, !resurrectee->upright && resurrectee->IsOnGround(), false, 0.0f); - for (const int resurrecterID: cai->resurrecters) { + for (const int resurrecterID: CBuilderCaches::resurrecters) { CBuilder* resurrecter = static_cast(unitHandler.GetUnit(resurrecterID)); CCommandAI* resurrecterCAI = resurrecter->commandAI; diff --git a/rts/Sim/Units/UnitTypes/Factory.cpp b/rts/Sim/Units/UnitTypes/Factory.cpp index f5012fc967..650855e3ea 100644 --- a/rts/Sim/Units/UnitTypes/Factory.cpp +++ b/rts/Sim/Units/UnitTypes/Factory.cpp @@ -9,6 +9,8 @@ #include "Sim/Misc/GroundBlockingObjectMap.h" #include "Sim/Misc/ModInfo.h" #include "Sim/Misc/QuadField.h" +#include "Sim/Features/Feature.h" +#include "Sim/Features/FeatureHandler.h" #include "Sim/Misc/TeamHandler.h" #include "Sim/MoveTypes/MoveType.h" #include "Sim/MoveTypes/MoveDefHandler.h" @@ -25,6 +27,7 @@ #include "System/SpringMath.h" #include "System/creg/DefTypes.h" #include "System/Sound/ISoundChannels.h" +#include "Sim/Units/Scripts/CobInstance.h" #include "Game/GlobalUnsynced.h" @@ -33,6 +36,9 @@ CR_BIND_DERIVED(CFactory, CBuilding, ) CR_REG_METADATA(CFactory, ( CR_MEMBER(buildSpeed), + CR_MEMBER(buildDistance), + CR_MEMBER(reclaimSpeed), + CR_MEMBER(range3D), CR_MEMBER(boOffset), CR_MEMBER(boRadius), @@ -55,6 +61,9 @@ CR_REG_METADATA(CFactory, ( CFactory::CFactory() : CBuilding() , buildSpeed(100.0f) + , reclaimSpeed(100) + , range3D(true) + , buildDistance(16) , boOffset(0.0f) //can't set here , boRadius(0.0f) //can't set here , boRelHeading(0) @@ -81,7 +90,10 @@ void CFactory::PreInit(const UnitLoadParams& params) { RECOIL_DETAILED_TRACY_ZONE; unitDef = params.unitDef; + range3D = unitDef->buildRange3D; buildSpeed = unitDef->buildSpeed / GAME_SPEED; + buildDistance = (params.unitDef)->buildDistance; + reclaimSpeed = INV_GAME_SPEED * unitDef->reclaimSpeed; CBuilding::PreInit(params); @@ -116,7 +128,7 @@ void CFactory::Update() // construction (by assisting builders) or has to be killed --> the // latter is easier if (curBuild != nullptr) - StopBuild(); + StopBuild(true); return; } @@ -154,6 +166,19 @@ void CFactory::Update() if (curBuild != nullptr) { UpdateBuild(curBuild); FinishBuild(curBuild); + } else if (!IsStunned()) { + const CFactoryCAI* cai = static_cast(commandAI); + const CCommandQueue& cQueue = cai->commandQue; + const Command& fCommand = (!cQueue.empty())? cQueue.front(): Command(CMD_STOP); + + bool updated = false; + + //updated = updated || UpdateTerraform(fCommand); + //updated = updated || AssistTerraform(fCommand); + //updated = updated || UpdateBuild(fCommand); + updated = updated || UpdateReclaim(fCommand); + //updated = updated || UpdateResurrect(fCommand); + //updated = updated || UpdateCapture(fCommand); } const bool wantClose = (!IsStunned() && yardOpen && (gs->frameNum >= (lastBuildUpdateFrame + GAME_SPEED * (UNIT_SLOWUPDATE_RATE >> 1)))); @@ -168,7 +193,29 @@ void CFactory::Update() CBuilding::Update(); } +bool CFactory::UpdateReclaim(const Command& fCommand) +{ + RECOIL_DETAILED_TRACY_ZONE; + // AddBuildPower can invoke StopBuild indirectly even if returns true + // and reset curReclaim to null (which would crash CreateNanoParticle) + CSolidObject* curReclaimee = curReclaim; + + if (curReclaimee == nullptr || f3SqDist(curReclaimee->pos, pos) >= Square(buildDistance + curReclaimee->buildeeRadius) || !inBuildStance) + return false; + + if (fCommand.GetID() == CMD_WAIT) { + StopBuild(); + return true; + } + + ScriptDecloak(curReclaimee, nullptr); + if (!curReclaimee->AddBuildPower(this, -reclaimSpeed)) + return true; + + CreateNanoParticle(curReclaimee->midPos, curReclaimee->radius * 0.7f, true, (reclaimingUnit && curReclaimee->team != team)); + return true; +} void CFactory::StartBuild(const UnitDef* buildeeDef) { RECOIL_DETAILED_TRACY_ZONE; @@ -226,17 +273,19 @@ void CFactory::UpdateBuild(CUnit* buildee) { const int buildPieceHeading = GetHeadingFromVector(buildPieceMat[8], buildPieceMat[10]); const int buildFaceHeading = GetHeadingFromFacing(buildFacing); - float3 buildeePos = buildPos; + const CCommandQueue& queue = commandAI->commandQue; - // note: basically StaticMoveType::SlowUpdate() - if (buildee->FloatOnWater() && buildee->IsInWater()) - buildeePos.y = -buildee->moveType->GetWaterline(); + if (!queue.empty() && (queue.front().GetID() < 0)) { + float3 buildeePos = buildPos; - // rotate unit nanoframe with platform - buildee->Move(buildeePos, false); - buildee->SetHeading((-buildPieceHeading + buildFaceHeading) & (SPRING_CIRCLE_DIVS - 1), false, false, 0.0f); + // note: basically StaticMoveType::SlowUpdate() + if (buildee->FloatOnWater() && buildee->IsInWater()) + buildeePos.y = -buildee->moveType->GetWaterline(); - const CCommandQueue& queue = commandAI->commandQue; + // rotate unit nanoframe with platform + buildee->Move(buildeePos, false); + buildee->SetHeading((-buildPieceHeading + buildFaceHeading) & (SPRING_CIRCLE_DIVS - 1), false, false, 0.0f); + } if (!queue.empty() && (queue.front().GetID() == CMD_WAIT)) { buildee->AddBuildPower(this, 0.0f); @@ -246,7 +295,7 @@ void CFactory::UpdateBuild(CUnit* buildee) { if (!buildee->AddBuildPower(this, buildSpeed)) return; - CreateNanoParticle(); + CreateNanoParticle(buildee->midPos, buildee->radius * 0.5f, false); } void CFactory::FinishBuild(CUnit* buildee) { @@ -256,26 +305,34 @@ void CFactory::FinishBuild(CUnit* buildee) { if (unitDef->fullHealthFactory && buildee->health < buildee->maxHealth) return; - // assign buildee to same group as us - if (GetGroup() != nullptr && buildee->GetGroup() != nullptr) - buildee->SetGroup(GetGroup(), true); + bool isOurs = false; + const CCommandQueue& queue = commandAI->commandQue; + if (!queue.empty() && (queue.front().GetID() < 0)) { + // assign buildee to same group as us + if (GetGroup() != nullptr && buildee->GetGroup() != nullptr) + buildee->SetGroup(GetGroup(), true); + isOurs = true; + + } const CCommandAI* bcai = buildee->commandAI; // if not idle, the buildee already has user orders const bool buildeeIdle = (bcai->commandQue.empty()); const bool buildeeMobile = (dynamic_cast(bcai) != nullptr); - if (buildeeIdle || buildeeMobile) { + if (isOurs && (buildeeIdle || buildeeMobile)) { AssignBuildeeOrders(buildee); waitCommandsAI.AddLocalUnit(buildee, this); } - // inform our commandAI - CFactoryCAI* factoryCAI = static_cast(commandAI); - factoryCAI->FactoryFinishBuild(finishedBuildCommand); + if (isOurs) { + // inform our commandAI + CFactoryCAI* factoryCAI = static_cast(commandAI); + factoryCAI->FactoryFinishBuild(finishedBuildCommand); - eventHandler.UnitFromFactory(buildee, this, !buildeeIdle); - StopBuild(); + eventHandler.UnitFromFactory(buildee, this, !buildeeIdle); + } + StopBuild(true); } @@ -304,13 +361,16 @@ unsigned int CFactory::QueueBuild(const UnitDef* buildeeDef, const Command& buil return FACTORY_NEXT_BUILD_ORDER; } -void CFactory::StopBuild() +void CFactory::StopBuild(bool callScript) { RECOIL_DETAILED_TRACY_ZONE; - // cancel a build-in-progress - script->StopBuilding(); + /*if (curBuild != nullptr) + DeleteDeathDependence(curBuild, DEPENDENCE_BUILD);*/ + if (callScript) + script->StopBuilding(); if (curBuild) { + // cancel a build-in-progress if (curBuild->beingBuilt) { AddMetal(curBuild->cost.metal * curBuild->buildProgress, false); curBuild->KillUnit(nullptr, false, true, -CSolidObject::DAMAGE_FACTORY_CANCEL); @@ -318,16 +378,40 @@ void CFactory::StopBuild() DeleteDeathDependence(curBuild, DEPENDENCE_BUILD); } + + if (curReclaim != nullptr) + DeleteDeathDependence(curReclaim, DEPENDENCE_RECLAIM); + /*if (helpTerraform != nullptr) + DeleteDeathDependence(helpTerraform, DEPENDENCE_TERRAFORM); + if (curResurrect != nullptr) + DeleteDeathDependence(curResurrect, DEPENDENCE_RESURRECT);*/ + if (curCapture != nullptr) + DeleteDeathDependence(curCapture, DEPENDENCE_CAPTURE); + curBuild = nullptr; + curReclaim = nullptr; + /*helpTerraform = nullptr;*/ + curResurrect = nullptr; + curCapture = nullptr; curBuildDef = nullptr; + + /*if (terraforming) { + constexpr int tsr = TERRA_SMOOTHING_RADIUS; + mapDamage->RecalcArea(tx1 - tsr, tx2 + tsr, tz1 - tsr, tz2 + tsr); + } + + terraforming = false; + + SetHoldFire(false);*/ } + void CFactory::DependentDied(CObject* o) { RECOIL_DETAILED_TRACY_ZONE; if (o == curBuild) { curBuild = nullptr; - StopBuild(); + StopBuild(true); } CUnit::DependentDied(o); @@ -517,7 +601,7 @@ bool CFactory::ChangeTeam(int newTeam, ChangeType type) } -void CFactory::CreateNanoParticle(bool highPriority) +void CFactory::CreateNanoParticle(const float3& goal, float radius, bool inverse, bool highPriority) { RECOIL_DETAILED_TRACY_ZONE; const int modelNanoPiece = nanoPieceCache.GetNanoPiece(script); @@ -529,5 +613,104 @@ void CFactory::CreateNanoParticle(bool highPriority) const float3 nanoPos = this->GetObjectSpacePos(relNanoFirePos); // unsynced - projectileHandler.AddNanoParticle(nanoPos, curBuild->midPos, unitDef, team, highPriority); + projectileHandler.AddNanoParticle(nanoPos, goal, unitDef, team, radius, inverse, highPriority); +} + +void CFactory::SetRepairTarget(CUnit* target) +{ + RECOIL_DETAILED_TRACY_ZONE; + if (target == curBuild) + return; + + StopBuild(false); + TempHoldFire(CMD_REPAIR); + + curBuild = target; + AddDeathDependence(curBuild, DEPENDENCE_BUILD); + + if (!target->groundLevelled) { + // resume levelling the ground + tx1 = (int)std::max(0.0f, (target->pos.x - (target->xsize * 0.5f * SQUARE_SIZE)) / SQUARE_SIZE); + tz1 = (int)std::max(0.0f, (target->pos.z - (target->zsize * 0.5f * SQUARE_SIZE)) / SQUARE_SIZE); + tx2 = std::min(mapDims.mapx, tx1 + target->xsize); + tz2 = std::min(mapDims.mapy, tz1 + target->zsize); + + terraformCenter = target->pos; + terraformRadius = (tx1 - tx2) * SQUARE_SIZE; + terraformType = Terraform_Building; + terraforming = true; + } + + ScriptStartBuilding(target->pos, false); +} + + +void CFactory::SetReclaimTarget(CSolidObject* target) +{ + RECOIL_DETAILED_TRACY_ZONE; + if (dynamic_cast(target) != nullptr && !static_cast(target)->def->reclaimable) + return; + + CUnit* recUnit = dynamic_cast(target); + + if (recUnit != nullptr && !recUnit->unitDef->reclaimable) + return; + + if (curReclaim == target || this == target) + return; + + StopBuild(false); + TempHoldFire(CMD_RECLAIM); + + reclaimingUnit = (recUnit != nullptr); + curReclaim = target; + + AddDeathDependence(curReclaim, DEPENDENCE_RECLAIM); + + ScriptStartBuilding(target->pos, false); +} + +bool CFactory::ScriptStartBuilding(float3 pos, bool silent) +{ + RECOIL_DETAILED_TRACY_ZONE; + if (script->HasStartBuilding()) { + const float3 wantedDir = (pos - midPos).Normalize(); + const float h = GetHeadingFromVectorF(wantedDir.x, wantedDir.z); + const float p = math::asin(wantedDir.dot(updir)); + const float pitch = math::asin(frontdir.dot(updir)); + + // clamping p - pitch not needed, range of asin is -PI/2..PI/2, + // so max difference between two asin calls is PI. + // FIXME: convert CSolidObject::heading to radians too. + script->StartBuilding(ClampRad(h - heading * TAANG2RAD), p - pitch); + } + + if ((!silent || inBuildStance) && IsInLosForAllyTeam(gu->myAllyTeam)) + Channels::General->PlayRandomSample(unitDef->sounds.build, pos); + + return inBuildStance; +} + + +bool CFactory::CanAssistUnit(const CUnit* u, const UnitDef* def) const +{ + RECOIL_DETAILED_TRACY_ZONE; + if (!unitDef->canAssist) + return false; + + return ((def == nullptr || u->unitDef == def) && u->beingBuilt && (u->buildProgress < 1.0f) && (u->soloBuilder == nullptr || u->soloBuilder == this)); +} + + +bool CFactory::CanRepairUnit(const CUnit* u) const +{ + RECOIL_DETAILED_TRACY_ZONE; + if (!unitDef->canRepair) + return false; + if (u->beingBuilt) + return false; + if (u->health >= u->maxHealth) + return false; + + return (u->unitDef->repairable); } diff --git a/rts/Sim/Units/UnitTypes/Factory.h b/rts/Sim/Units/UnitTypes/Factory.h index 4b38e3491f..08b654d6f8 100644 --- a/rts/Sim/Units/UnitTypes/Factory.h +++ b/rts/Sim/Units/UnitTypes/Factory.h @@ -11,6 +11,7 @@ struct UnitDef; struct Command; class CFactory; +class CFeature; class CFactory : public CBuilding { @@ -22,14 +23,17 @@ class CFactory : public CBuilding void StartBuild(const UnitDef* buildeeDef); void UpdateBuild(CUnit* buildee); void FinishBuild(CUnit* buildee); - void StopBuild(); + void StopBuild(bool callScript = false); + bool UpdateReclaim(const Command& fCommand); + //bool UpdateResurrect(const Command& fCommand); /// @return whether the to-be-built unit is enqueued unsigned int QueueBuild(const UnitDef* buildeeDef, const Command& buildCmd); void Update(); void DependentDied(CObject* o); - void CreateNanoParticle(bool highPriority = false); + //void CreateNanoParticle(bool highPriority = false); + void CreateNanoParticle(const float3& goal, float radius, bool inverse, bool highPriority = false); /// supply the build piece to speed up float3 CalcBuildPos(int buildPiece = -1); @@ -41,12 +45,24 @@ class CFactory : public CBuilding const NanoPieceCache& GetNanoPieceCache() const { return nanoPieceCache; } NanoPieceCache& GetNanoPieceCache() { return nanoPieceCache; } + void SetRepairTarget(CUnit* target); + void SetReclaimTarget(CSolidObject* object); + //void SetResurrectTarget(CFeature* feature); + bool ScriptStartBuilding(float3 pos, bool silent); + bool CanAssistUnit(const CUnit* u, const UnitDef* def = nullptr) const; + bool CanRepairUnit(const CUnit* u) const; + inline float f3SqLen(const float3& a) const { return (range3D ? a.SqLength() : a.SqLength2D()); } + inline float f3SqDist(const float3& a, const float3& b) const { return (f3SqLen(a - b)); } + inline float f3Dist(const float3& a, const float3& b) const { return (f3Len(a - b)); } + inline float f3Len(const float3& a) const { return (range3D ? a.Length() : a.Length2D()); } private: void SendToEmptySpot(CUnit* unit); void AssignBuildeeOrders(CUnit* unit); + bool StartBuild(BuildInfo& buildInfo, CFeature*& feature, bool& inWaitStance, bool& limitReached); public: float buildSpeed; + float reclaimSpeed; //BuggerOff fine tuning float boOffset; @@ -64,6 +80,10 @@ class CFactory : public CBuilding FACTORY_NEXT_BUILD_ORDER = 2, }; + bool range3D; ///< spheres instead of infinite cylinders for range tests + float buildDistance; + + CSolidObject* curReclaim; private: const UnitDef* curBuildDef; int lastBuildUpdateFrame; @@ -71,6 +91,24 @@ class CFactory : public CBuilding Command finishedBuildCommand; NanoPieceCache nanoPieceCache; + + + CFeature* curResurrect; + int lastResurrected; + CUnit* curCapture; + bool reclaimingUnit; + + bool terraforming; + /*float terraformHelp; + float myTerraformLeft;*/ + enum TerraformType { + Terraform_Building, + Terraform_Restore + } terraformType; + int tx1,tx2,tz1,tz2; + float3 terraformCenter; + float terraformRadius; + }; #endif // _FACTORY_H