#include "ProductionManager.h" #include "Util.h" #include "IDABot.h" ProductionManager::ProductionManager(IDABot & bot) : m_bot (bot) , m_buildingManager (bot) , m_queue (bot) { } void ProductionManager::setBuildOrder(const BuildOrder & buildOrder) { m_queue.clearAll(); for (size_t i(0); i<buildOrder.size(); ++i) { m_queue.queueAsLowestPriority(buildOrder[i], true); } } void ProductionManager::onStart() { m_buildingManager.onStart(); //setBuildOrder(m_bot.Strategy().getOpeningBookBuildOrder()); } void ProductionManager::onFrame() { fixBuildOrderDeadlock(); manageBuildOrderQueue(); // TODO: if nothing is currently building, get a new goal from the strategy manager // TODO: detect if there's a build order deadlock once per second // TODO: triggers for game things like cloaked units etc m_buildingManager.onFrame(); drawProductionInformation(); } // on unit destroy void ProductionManager::onUnitDestroy(const Unit & unit) { // TODO: might have to re-do build order if a vital unit died } void ProductionManager::manageBuildOrderQueue() { // if there is nothing in the queue, oh well if (m_queue.isEmpty()) { return; } // the current item to be used BuildOrderItem & currentItem = m_queue.getHighestPriorityItem(); // while there is still something left in the queue while (!m_queue.isEmpty()) { // this is the unit which can produce the currentItem Unit producer = getProducer(currentItem.type); // check to see if we can make it right now bool canMake = canMakeNow(producer, currentItem.type); // TODO: if it's a building and we can't make it yet, predict the worker movement to the location // if we can make the current item if (producer.isValid() && canMake) { // create it and remove it from the _queue create(producer, currentItem); m_queue.removeCurrentHighestPriorityItem(); // don't actually loop around in here break; } // otherwise, if we can skip the current item else if (m_queue.canSkipItem()) { // skip it m_queue.skipItem(); // and get the next one currentItem = m_queue.getNextHighestPriorityItem(); } else { // so break out break; } } } void ProductionManager::fixBuildOrderDeadlock() { if (m_queue.isEmpty()) { return; } BuildOrderItem & currentItem = m_queue.getHighestPriorityItem(); // check to see if we have the prerequisites for the topmost item bool hasRequired = m_bot.Data(currentItem.type).requiredUnits.empty(); for (auto & required : m_bot.Data(currentItem.type).requiredUnits) { if (m_bot.UnitInfo().getUnitTypeCount(Players::Self, required, false) > 0 || m_buildingManager.isBeingBuilt(required)) { hasRequired = true; break; } } if (!hasRequired) { std::cout << currentItem.type.getName() << " needs " << m_bot.Data(currentItem.type).requiredUnits[0].getName() << "\n"; m_queue.queueAsHighestPriority(MetaType(m_bot.Data(currentItem.type).requiredUnits[0], m_bot), true); fixBuildOrderDeadlock(); return; } // build the producer of the unit if we don't have one bool hasProducer = m_bot.Data(currentItem.type).whatBuilds.empty(); for (auto & producer : m_bot.Data(currentItem.type).whatBuilds) { if (m_bot.UnitInfo().getUnitTypeCount(Players::Self, producer, false) > 0 || m_buildingManager.isBeingBuilt(producer)) { hasProducer = true; break; } } if (!hasProducer) { m_queue.queueAsHighestPriority(MetaType(m_bot.Data(currentItem.type).whatBuilds[0], m_bot), true); fixBuildOrderDeadlock(); } // build a refinery if we don't have one and the thing costs gas auto refinery = Util::GetRefinery(m_bot.GetPlayerRace(Players::Self), m_bot); if (m_bot.Data(currentItem.type).gasCost > 0 && m_bot.UnitInfo().getUnitTypeCount(Players::Self, refinery, false) == 0) { m_queue.queueAsHighestPriority(MetaType(refinery, m_bot), true); } // build supply if we need some auto supplyProvider = Util::GetSupplyProvider(m_bot.GetPlayerRace(Players::Self), m_bot); if (m_bot.Data(currentItem.type).supplyCost > (m_bot.GetMaxSupply() - m_bot.GetCurrentSupply()) && !m_buildingManager.isBeingBuilt(supplyProvider)) { m_queue.queueAsHighestPriority(MetaType(supplyProvider, m_bot), true); } } Unit ProductionManager::getProducer(const MetaType & type, CCPosition closestTo) { // get all the types of units that cna build this type auto & producerTypes = m_bot.Data(type).whatBuilds; // make a set of all candidate producers std::vector<Unit> candidateProducers; for (auto unit : m_bot.UnitInfo().getUnits(Players::Self)) { // reasons a unit can not train the desired type if (std::find(producerTypes.begin(), producerTypes.end(), unit.getType()) == producerTypes.end()) { continue; } if (!unit.isCompleted()) { continue; } if (m_bot.Data(unit).isBuilding && unit.isTraining()) { continue; } if (unit.isFlying()) { continue; } // TODO: if unit is not powered continue // TODO: if the type is an addon, some special cases // TODO: if the type requires an addon and the producer doesn't have one // if we haven't cut it, add it to the set of candidates candidateProducers.push_back(unit); } return getClosestUnitToPosition(candidateProducers, closestTo); } Unit ProductionManager::getClosestUnitToPosition(const std::vector<Unit> & units, CCPosition closestTo) { if (units.size() == 0) { return Unit(); } // if we don't care where the unit is return the first one we have if (closestTo.x == 0 && closestTo.y == 0) { return units[0]; } Unit closestUnit; double minDist = std::numeric_limits<double>::max(); for (auto & unit : units) { double distance = Util::Dist(unit, closestTo); if (!closestUnit.isValid() || distance < minDist) { closestUnit = unit; minDist = distance; } } return closestUnit; } // this function will check to see if all preconditions are met and then create a unit void ProductionManager::create(const Unit & producer, BuildOrderItem & item) { if (!producer.isValid()) { return; } // if we're dealing with a building if (item.type.isBuilding()) { if (item.type.getUnitType().isMorphedBuilding()) { producer.morph(item.type.getUnitType()); } else { m_buildingManager.addBuildingTask(item.type.getUnitType(), Util::GetTilePosition(m_bot.GetStartLocation())); } } // if we're dealing with a non-building unit else if (item.type.isUnit()) { producer.train(item.type.getUnitType()); } else if (item.type.isUpgrade()) { // TODO: UPGRADES //Micro::SmartAbility(producer, m_bot.Data(item.type.getUpgradeID()).buildAbility, m_bot); } } bool ProductionManager::canMakeNow(const Unit & producer, const MetaType & type) { if (!producer.isValid() || !meetsReservedResources(type)) { return false; } #ifdef SC2API sc2::AvailableAbilities available_abilities = m_bot.Query()->GetAbilitiesForUnit(producer.getUnitPtr()); // quick check if the unit can't do anything it certainly can't build the thing we want if (available_abilities.abilities.empty()) { return false; } else { // check to see if one of the unit's available abilities matches the build ability type sc2::AbilityID MetaTypeAbility = m_bot.Data(type).buildAbility; for (const sc2::AvailableAbility & available_ability : available_abilities.abilities) { if (available_ability.ability_id == MetaTypeAbility) { return true; } } } return false; #else bool canMake = meetsReservedResources(type); if (canMake) { if (type.isUnit()) { canMake = BWAPI::Broodwar->canMake(type.getUnitType().getAPIUnitType(), producer.getUnitPtr()); } else if (type.isTech()) { canMake = BWAPI::Broodwar->canResearch(type.getTechType(), producer.getUnitPtr()); } else if (type.isUpgrade()) { canMake = BWAPI::Broodwar->canUpgrade(type.getUpgrade(), producer.getUnitPtr()); } else { BOT_ASSERT(false, "Unknown type"); } } return canMake; #endif } bool ProductionManager::detectBuildOrderDeadlock() { // TODO: detect build order deadlocks here return false; } int ProductionManager::getFreeMinerals() { return m_bot.GetMinerals() - m_buildingManager.getReservedMinerals(); } int ProductionManager::getFreeGas() { return m_bot.GetGas() - m_buildingManager.getReservedGas(); } // return whether or not we meet resources, including building reserves bool ProductionManager::meetsReservedResources(const MetaType & type) { // return whether or not we meet the resources int minerals = m_bot.Data(type).mineralCost; int gas = m_bot.Data(type).gasCost; return (m_bot.Data(type).mineralCost <= getFreeMinerals()) && (m_bot.Data(type).gasCost <= getFreeGas()); } void ProductionManager::drawProductionInformation() { if (!m_bot.Config().DrawProductionInfo) { return; } std::stringstream ss; ss << "Production Information\n\n"; for (auto & unit : m_bot.UnitInfo().getUnits(Players::Self)) { if (unit.isBeingConstructed()) { //ss << sc2::UnitTypeToName(unit.unit_type) << " " << unit.build_progress << "\n"; } } ss << m_queue.getQueueInformation(); m_bot.Map().drawTextScreen(0.01f, 0.01f, ss.str(), CCColor(255, 255, 0)); }