From 5b2b604ae15538b24333b7411d69afe6931e684c Mon Sep 17 00:00:00 2001 From: Michael Holtan Date: Mon, 1 Mar 2021 23:48:09 -0600 Subject: [PATCH] Fix Issue #35 "Improve mineral saturation" This will send new/idle workers to a non-saturated townhall. Townhalls will build an extra worker over Ideal and it will go to a new non-saturated townhall upon creation, so each townhall will continually build workers if there are any townhalls that are not yet saturated (until 80 total). After constructing a building, workers will go to an unsaturated townhall instead of the closest/starting one. --- src/Dispatcher.cpp | 10 +++ src/Dispatcher.h | 2 + src/Hub.cpp | 119 ++++++++++++++++++++------- src/Hub.h | 16 +++- src/blueprints/TownHall.cpp | 12 ++- src/core/Map.cpp | 21 ++++- src/core/Map.h | 15 +++- src/plugins/Miner.cpp | 107 +++++++++++++++++------- src/plugins/Plugin.h | 3 + src/strategies/terran/MarinePush.cpp | 16 ++-- 10 files changed, 241 insertions(+), 80 deletions(-) diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 5aa3b45..8bfc258 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -145,6 +145,16 @@ void Dispatcher::OnUpgradeCompleted(sc2::UpgradeID id_) { i->OnUpgradeCompleted(id_); } +void Dispatcher::OnUnitEnterVision(const sc2::Unit* unit_) { + gHistory.info() << sc2::UnitTypeToName(unit_->unit_type) << + "(" << unit_->tag << ") entered vision" << std::endl; + + gHub->OnUnitEnterVision(*unit_); + + for (const auto& i : m_plugins) + i->OnUnitEnterVision(unit_, m_builder.get()); +} + void Dispatcher::OnError(const std::vector& client_errors, const std::vector& protocol_errors) { for (const auto i : client_errors) { diff --git a/src/Dispatcher.h b/src/Dispatcher.h index d10db81..dcfec26 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -32,6 +32,8 @@ struct Dispatcher: sc2::Agent { void OnUpgradeCompleted(sc2::UpgradeID id_) final; + void OnUnitEnterVision(const sc2::Unit* unit_) final; + void OnError(const std::vector& client_errors, const std::vector& protocol_errors = {}) final; diff --git a/src/Hub.cpp b/src/Hub.cpp index f8e5d1d..d9931a2 100644 --- a/src/Hub.cpp +++ b/src/Hub.cpp @@ -98,17 +98,9 @@ void Hub::OnUnitCreated(const sc2::Unit& unit_) { case sc2::UNIT_TYPEID::PROTOSS_NEXUS: case sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER: case sc2::UNIT_TYPEID::ZERG_HATCHERY: - for (auto& i : m_expansions) { - if (std::floor(i.town_hall_location.x) != std::floor(unit_.pos.x) || - std::floor(i.town_hall_location.y) != std::floor(unit_.pos.y)) - continue; - - i.owner = Owner::SELF; - gHistory.info() << "Capture region: (" << - unit_.pos.x << ", " << unit_.pos.y << - ")" << std::endl; - return; - } + CaptureExpansion(unit_); + gHistory.info() << "Capture region: (" << unit_.pos.x << + ", " << unit_.pos.y << ")" << std::endl; return; default: @@ -121,12 +113,29 @@ void Hub::OnUnitDestroyed(const sc2::Unit& unit_) { case sc2::UNIT_TYPEID::PROTOSS_PROBE: case sc2::UNIT_TYPEID::TERRAN_SCV: case sc2::UNIT_TYPEID::ZERG_DRONE: { - if (m_busy_workers.Remove(Worker(unit_))) { - gHistory.info() << "Our busy worker was destroyed" << std::endl; + if (m_free_workers.Remove(Worker(unit_))) { + return; + } + + m_busy_workers.Remove(Worker(unit_)); + gHistory.info() << "Our busy worker was destroyed" << std::endl; + + auto it = std::find_if(m_expansions.begin(), m_expansions.end(), + [unit_](const Expansion& expansion_) { + return expansion_.worker_tag == unit_.tag; + }); + + if (it == m_expansions.end()) return; + + if (it->owner == Owner::CONTESTED) // was enroute to build TownHall + it->RemoveOwner(); + + if (it->owner == Owner::SELF) { // TownHall still under construction + // NOTE (impulsecloud): decide whether to cancel or send new worker + // may need military escort to clear region } - m_free_workers.Remove(Worker(unit_)); return; } @@ -152,17 +161,9 @@ void Hub::OnUnitDestroyed(const sc2::Unit& unit_) { case sc2::UNIT_TYPEID::ZERG_HATCHERY: case sc2::UNIT_TYPEID::ZERG_HIVE: case sc2::UNIT_TYPEID::ZERG_LAIR: - for (auto& i : m_expansions) { - if (std::floor(i.town_hall_location.x) != std::floor(unit_.pos.x) || - std::floor(i.town_hall_location.y) != std::floor(unit_.pos.y)) - continue; - - i.owner = Owner::NEUTRAL; - gHistory.info() << "Lost region: (" << - unit_.pos.x << ", " << unit_.pos.y << - ")" << std::endl; - return; - } + RemoveExpansionOwner(unit_); + gHistory.info() << "Lost region: (" << unit_.pos.x + << ", " << unit_.pos.y << ")" << std::endl; return; default: @@ -175,7 +176,7 @@ void Hub::OnUnitIdle(const sc2::Unit& unit_) { case sc2::UNIT_TYPEID::PROTOSS_PROBE: case sc2::UNIT_TYPEID::TERRAN_SCV: case sc2::UNIT_TYPEID::ZERG_DRONE: { - if (m_free_workers.Swap(Worker(unit_), m_busy_workers)) + if (m_busy_workers.Swap(Worker(unit_), m_free_workers)) gHistory.info() << "Our busy worker has finished task" << std::endl; return; @@ -196,6 +197,25 @@ void Hub::OnUnitIdle(const sc2::Unit& unit_) { } } +void Hub::OnUnitEnterVision(const sc2::Unit& unit_) { + switch (unit_.unit_type.ToType()) { + case sc2::UNIT_TYPEID::PROTOSS_NEXUS: + case sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER: + case sc2::UNIT_TYPEID::TERRAN_ORBITALCOMMAND: + case sc2::UNIT_TYPEID::TERRAN_PLANETARYFORTRESS: + case sc2::UNIT_TYPEID::ZERG_HATCHERY: + case sc2::UNIT_TYPEID::ZERG_HIVE: + case sc2::UNIT_TYPEID::ZERG_LAIR: + EnemyOwnsExpansion(unit_); + gHistory.info() << "Enemy owns region: (" << unit_.pos.x + << ", " << unit_.pos.y << ")" << std::endl; + return; + + default: + return; + } +} + bool Hub::IsOccupied(const sc2::Unit& unit_) const { return m_captured_geysers.IsCached(Geyser(unit_)); } @@ -243,13 +263,13 @@ bool Hub::AssignRefineryConstruction(Order* order_, const sc2::Unit* geyser_) { return true; } -bool Hub::AssignBuildTask(Order* order_, const sc2::Point2D& point_) { +sc2::Tag Hub::AssignBuildTask(Order* order_, const sc2::Point2D& point_) { Worker* worker = GetClosestFreeWorker(point_); if (!worker) - return false; + return sc2::NullTag; worker->Build(order_, point_); - return true; + return worker->Tag(); } void Hub::AssignVespeneHarvester(const sc2::Unit& refinery_) { @@ -279,7 +299,7 @@ const Expansions& Hub::GetExpansions() const { return m_expansions; } -const sc2::Point3D* Hub::GetNextExpansion() { +Expansion* Hub::GetNextExpansion() { auto it = std::find_if(m_expansions.begin(), m_expansions.end(), [](const Expansion& expansion_) { return expansion_.owner == Owner::NEUTRAL; @@ -289,7 +309,44 @@ const sc2::Point3D* Hub::GetNextExpansion() { return nullptr; it->owner = Owner::CONTESTED; - return &(it->town_hall_location); + return &(*it); +} + +Expansion* Hub::GetExpansionOfTownhall(const sc2::Unit& unit_) { + auto it = std::find_if(m_expansions.begin(), m_expansions.end(), + [unit_](const Expansion& e) { + return std::floor(e.town_hall_location.x) == std::floor(unit_.pos.x) && + std::floor(e.town_hall_location.y) == std::floor(unit_.pos.y); + }); + + if (it == m_expansions.end()) + return nullptr; + + return &(*it); +} + +void Hub::CaptureExpansion(const sc2::Unit& unit_) { + auto expansion = GetExpansionOfTownhall(unit_); + if (!expansion) + return; + + expansion->SetOwner(unit_, Owner::SELF); +} + +void Hub::EnemyOwnsExpansion(const sc2::Unit& unit_) { + auto expansion = GetExpansionOfTownhall(unit_); + if (!expansion) + return; + + expansion->SetOwner(unit_, Owner::ENEMY); +} + +void Hub::RemoveExpansionOwner(const sc2::Unit& unit_) { + auto expansion = GetExpansionOfTownhall(unit_); + if (!expansion) + return; + + expansion->RemoveOwner(); } std::unique_ptr gHub; diff --git a/src/Hub.h b/src/Hub.h index 97a4838..2d45fe8 100644 --- a/src/Hub.h +++ b/src/Hub.h @@ -123,6 +123,8 @@ struct Hub { void OnUnitIdle(const sc2::Unit& unit_); + void OnUnitEnterVision(const sc2::Unit& unit_); + bool IsOccupied(const sc2::Unit& unit_) const; bool IsTargetOccupied(const sc2::UnitOrder& order_) const; @@ -139,7 +141,7 @@ struct Hub { bool AssignRefineryConstruction(Order* order_, const sc2::Unit* geyser_); - bool AssignBuildTask(Order* order_, const sc2::Point2D& point_); + sc2::Tag AssignBuildTask(Order* order_, const sc2::Point2D& point_); void AssignVespeneHarvester(const sc2::Unit& refinery_); @@ -149,9 +151,19 @@ struct Hub { const Expansions& GetExpansions() const; - const sc2::Point3D* GetNextExpansion(); + Expansion* GetNextExpansion(); + + const Expansion* GetBestMiningExpansionNear(const sc2::Unit* unit_) const; private: + Expansion* GetExpansionOfTownhall(const sc2::Unit& unit_); + + void CaptureExpansion(const sc2::Unit& unit_); + + void EnemyOwnsExpansion(const sc2::Unit& unit_); + + void RemoveExpansionOwner(const sc2::Unit& unit_); + sc2::Race m_current_race; Expansions m_expansions; sc2::UNIT_TYPEID m_current_supply_type; diff --git a/src/blueprints/TownHall.cpp b/src/blueprints/TownHall.cpp index acf70fb..c6d160f 100644 --- a/src/blueprints/TownHall.cpp +++ b/src/blueprints/TownHall.cpp @@ -7,9 +7,15 @@ #include "core/API.h" bool TownHall::Build(Order* order_) { - const sc2::Point3D* town_hall_location = gHub->GetNextExpansion(); - if (!town_hall_location) + Expansion* next_expand = gHub->GetNextExpansion(); + if (!next_expand) return false; - return gHub->AssignBuildTask(order_, *town_hall_location); + sc2::Tag builder = gHub->AssignBuildTask(order_, next_expand->town_hall_location); + if (!builder) + return false; + + next_expand->worker_tag = builder; + + return true; } diff --git a/src/core/Map.cpp b/src/core/Map.cpp index df26fd5..289b810 100644 --- a/src/core/Map.cpp +++ b/src/core/Map.cpp @@ -94,7 +94,23 @@ sc2::Point3D Cluster::Center() const { } // namespace Expansion::Expansion(const sc2::Point3D& town_hall_location_): - town_hall_location(town_hall_location_), owner(Owner::NEUTRAL) { + town_hall_location(town_hall_location_), owner(Owner::NEUTRAL), + town_hall_tag(sc2::NullTag), worker_tag(sc2::NullTag) { +} + +void Expansion::SetOwner(const sc2::Unit& unit_, Owner owner_) { + town_hall_tag = unit_.tag; + owner = owner_; + + // Terran worker_tag should remain as it constructs TownHall + if (gAPI->observer().GetCurrentRace() != sc2::Race::Terran) + worker_tag = sc2::NullTag; +} + +void Expansion::RemoveOwner() { + owner = Owner::NEUTRAL; + town_hall_tag = sc2::NullTag; + worker_tag = sc2::NullTag; } Expansions CalculateExpansionLocations() { @@ -152,5 +168,8 @@ Expansions CalculateExpansionLocations() { start_index += query_size[i.id]; } + // Include start location. TownHall tag will be added during its OnCreated event. + expansions.emplace_back(gAPI->observer().StartingLocation()); + return expansions; } diff --git a/src/core/Map.h b/src/core/Map.h index 54b7500..089e2bd 100644 --- a/src/core/Map.h +++ b/src/core/Map.h @@ -10,17 +10,24 @@ #include enum Owner { - NEUTRAL = 0, - CONTESTED = 1, - ENEMY = 2, - SELF = 3, + NEUTRAL = 0, // No Townhall at expansion, no workers enroute + CONTESTED = 1, // No Townhall, Enemy may be present, Self Worker enroute + ENEMY = 2, // Enemy TownHall at expansion + SELF = 3, // Self TownHall at expansion, possibly under construction }; struct Expansion { explicit Expansion(const sc2::Point3D& town_hall_location_); + void SetOwner(const sc2::Unit& unit_, Owner owner_); + + void RemoveOwner(); + sc2::Point3D town_hall_location; Owner owner; + sc2::Tag town_hall_tag; // valid for Owner::SELF or ENEMY + sc2::Tag worker_tag; // valid for Owner::CONTESTED or SELF + // NOTE (impulsecloud): check for dead builder, send new }; typedef std::vector Expansions; diff --git a/src/plugins/Miner.cpp b/src/plugins/Miner.cpp index 959e95c..f750a8a 100644 --- a/src/plugins/Miner.cpp +++ b/src/plugins/Miner.cpp @@ -20,10 +20,10 @@ void SecureMineralsIncome(Builder* builder_) { auto town_halls = gAPI->observer().GetUnits(sc2::IsTownHall()); for (const auto& i : town_halls()) { - if (i->assigned_harvesters >= i->ideal_harvesters) + if (!i->orders.empty() || i->build_progress != 1.0f) continue; - if (!i->orders.empty()) + if (i->assigned_harvesters >= i->ideal_harvesters + 1) continue; if (builder_->CountScheduledOrders(gHub->GetCurrentWorkerType()) > 0) @@ -73,48 +73,91 @@ void CallDownMULE() { } } -} // namespace +const Expansion* GetBestMiningExpansionNear(const sc2::Unit* unit_) { + if (!unit_) + return nullptr; -void Miner::OnStep(Builder* builder_) { - SecureMineralsIncome(builder_); - SecureVespeneIncome(); + sc2::Point2D worker_loc = unit_->pos; + float target_dist = std::numeric_limits::max(); + const Expansion* target = nullptr; - if (gHub->GetCurrentRace() == sc2::Race::Terran) - CallDownMULE(); + std::vector saturated_expansions; + + // Find closest unsaturated expansion + for (auto& i : gHub->GetExpansions()) { + if (i.owner != Owner::SELF) + continue; + + const sc2::Unit* th = gAPI->observer().GetUnit(i.town_hall_tag); + if (th->build_progress < 1.0f) + continue; + + if (th->assigned_harvesters >= th->ideal_harvesters) { + saturated_expansions.push_back(&i); + continue; + } + + float dist = sc2::DistanceSquared2D(worker_loc, th->pos); + + if (dist < target_dist) { + target = &i; + target_dist = dist; + } + } + + if (target != nullptr) + return target; + + // No unsaturated expansions, send to nearest built + for (auto i : saturated_expansions) { + const sc2::Unit* th = gAPI->observer().GetUnit(i->town_hall_tag); + + float dist = sc2::DistanceSquared2D(worker_loc, th->pos); + + if (dist < target_dist) { + target = i; + target_dist = dist; + } + } + + return target; } -void Miner::OnUnitCreated(const sc2::Unit* unit_, Builder*) { - if (!sc2::IsTownHall()(*unit_)) +void DistrubuteMineralWorker(const sc2::Unit* unit_) { + if (!unit_) return; - auto units = gAPI->observer().GetUnits(sc2::IsVisibleMineralPatch(), - sc2::Unit::Alliance::Neutral); + sc2::Point2D target_loc = gAPI->observer().StartingLocation(); + const Expansion* expansion = GetBestMiningExpansionNear(unit_); + if (expansion) + target_loc = expansion->town_hall_location; + + auto patches = gAPI->observer().GetUnits( + sc2::IsVisibleMineralPatch(), sc2::Unit::Alliance::Neutral); + const sc2::Unit* mineral_target = patches.GetClosestUnit(target_loc); - const sc2::Unit* mineral_target = units.GetClosestUnit(unit_->pos); if (!mineral_target) return; - gAPI->action().Cast(*unit_, sc2::ABILITY_ID::RALLY_WORKERS, *mineral_target); + gAPI->action().Cast(*unit_, sc2::ABILITY_ID::SMART, *mineral_target); } -void Miner::OnUnitIdle(const sc2::Unit* unit_, Builder*) { - auto units = gAPI->observer().GetUnits(sc2::IsVisibleMineralPatch(), - sc2::Unit::Alliance::Neutral); +} // namespace - switch (unit_->unit_type.ToType()) { - case sc2::UNIT_TYPEID::PROTOSS_PROBE: - case sc2::UNIT_TYPEID::TERRAN_SCV: - case sc2::UNIT_TYPEID::ZERG_DRONE: { - const sc2::Unit* mineral_target = units.GetClosestUnit( - gAPI->observer().StartingLocation()); - if (!mineral_target) - return; - - gAPI->action().Cast(*unit_, sc2::ABILITY_ID::SMART, *mineral_target); - break; - } +void Miner::OnStep(Builder* builder_) { + SecureMineralsIncome(builder_); + SecureVespeneIncome(); - default: - break; - } + if (gHub->GetCurrentRace() == sc2::Race::Terran) + CallDownMULE(); +} + +void Miner::OnUnitCreated(const sc2::Unit* unit_, Builder*) { + if (unit_->unit_type == gHub->GetCurrentWorkerType()) + DistrubuteMineralWorker(unit_); +} + +void Miner::OnUnitIdle(const sc2::Unit* unit_, Builder*) { + if (unit_->unit_type == gHub->GetCurrentWorkerType()) + DistrubuteMineralWorker(unit_); } diff --git a/src/plugins/Plugin.h b/src/plugins/Plugin.h index 097dc99..39ba88b 100644 --- a/src/plugins/Plugin.h +++ b/src/plugins/Plugin.h @@ -30,6 +30,9 @@ struct Plugin { virtual void OnUpgradeCompleted(sc2::UpgradeID) { } + virtual void OnUnitEnterVision(const sc2::Unit*, Builder*) { + } + virtual void OnGameEnd() { } }; diff --git a/src/strategies/terran/MarinePush.cpp b/src/strategies/terran/MarinePush.cpp index 4eeb64b..6fa9a03 100644 --- a/src/strategies/terran/MarinePush.cpp +++ b/src/strategies/terran/MarinePush.cpp @@ -4,6 +4,7 @@ #include "Historican.h" #include "MarinePush.h" +#include "Hub.h" namespace { Historican gHistory("strategy.marine_push"); @@ -15,19 +16,20 @@ MarinePush::MarinePush(): Strategy(16.0f) { void MarinePush::OnGameStart(Builder* builder_) { builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_SUPPLYDEPOT); builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); - builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER); builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_REFINERY); builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_ORBITALCOMMAND); builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); - builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKSTECHLAB); + builder_->ScheduleOptionalOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKSTECHLAB); builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); builder_->ScheduleObligatoryOrder(sc2::UPGRADE_ID::SHIELDWALL); builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); - builder_->ScheduleOptionalOrder(sc2::UPGRADE_ID::STIMPACK); - builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKSREACTOR); - builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKSREACTOR); - builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKSREACTOR); - builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKSREACTOR); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); + builder_->ScheduleObligatoryOrder(sc2::UNIT_TYPEID::TERRAN_BARRACKS); } void MarinePush::OnUnitIdle(const sc2::Unit* unit_, Builder* builder_) {