mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
squashed: BuildAnalyzer wip; Nulkiller decompose; Nulkiller makeTurnHelperPriorityPass; Nulkiller minor refactoring
This commit is contained in:
@@ -716,7 +716,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
|
||||
&& components.size() == 2
|
||||
&& components.front().type == ComponentType::RESOURCE
|
||||
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|
||||
|| nullkiller->buildAnalyzer->isGoldPressureHigh()))
|
||||
|| nullkiller->buildAnalyzer->isGoldPressureOverMax()))
|
||||
{
|
||||
sel = 1;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#include <boost/range/algorithm/sort.hpp>
|
||||
|
||||
#include "../StdInc.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
@@ -16,7 +18,235 @@
|
||||
namespace NK2AI
|
||||
{
|
||||
|
||||
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
TResources BuildAnalyzer::getResourcesRequiredNow() const
|
||||
{
|
||||
auto resourcesAvailable = ai->getFreeResources();
|
||||
auto result = withoutGold(armyCost) + requiredResources - resourcesAvailable;
|
||||
result.positive();
|
||||
return result;
|
||||
}
|
||||
|
||||
TResources BuildAnalyzer::getTotalResourcesRequired() const
|
||||
{
|
||||
auto resourcesAvailable = ai->getFreeResources();
|
||||
auto result = totalDevelopmentCost + withoutGold(armyCost) - resourcesAvailable;
|
||||
result.positive();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BuildAnalyzer::isGoldPressureOverMax() const
|
||||
{
|
||||
return goldPressure > ai->settings->getMaxGoldPressure();
|
||||
}
|
||||
|
||||
void BuildAnalyzer::update()
|
||||
{
|
||||
logAi->trace("Start BuildAnalyzer::update");
|
||||
BuildingInfo bi;
|
||||
reset();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
float economyDevelopmentCost = 0;
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
if(town->built >= cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
|
||||
continue; // Not much point in trying anything - can't built in this town anymore today
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Checking town %s", town->getNameTranslated());
|
||||
#endif
|
||||
|
||||
developmentInfos.push_back(TownDevelopmentInfo(town));
|
||||
TownDevelopmentInfo & tdi = developmentInfos.back();
|
||||
|
||||
updateTownDwellings(tdi, ai->armyManager, ai->cb);
|
||||
updateOtherBuildings(tdi, ai->armyManager, ai->cb);
|
||||
requiredResources += tdi.requiredResources;
|
||||
totalDevelopmentCost += tdi.townDevelopmentCost;
|
||||
|
||||
for(auto building : tdi.toBuild)
|
||||
{
|
||||
if (building.dailyIncome[EGameResID::GOLD] > 0)
|
||||
economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD];
|
||||
}
|
||||
|
||||
armyCost += tdi.armyCost;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
for(const auto & bi : tdi.toBuild)
|
||||
logAi->trace("Building preferences %s", bi.toString());
|
||||
#endif
|
||||
}
|
||||
|
||||
boost::range::sort(developmentInfos, [](const TownDevelopmentInfo & tdi1, const TownDevelopmentInfo & tdi2) -> bool
|
||||
{
|
||||
auto val1 = approximateInGold(tdi1.armyCost) - approximateInGold(tdi1.townDevelopmentCost);
|
||||
auto val2 = approximateInGold(tdi2.armyCost) - approximateInGold(tdi2.townDevelopmentCost);
|
||||
return val1 > val2;
|
||||
});
|
||||
|
||||
dailyIncome = calculateDailyIncome(ai->cb->getMyObjects(), ai->cb->getTownsInfo());
|
||||
goldPressure = calculateGoldPressure(ai->getLockedResources()[EGameResID::GOLD],
|
||||
(float)armyCost[EGameResID::GOLD],
|
||||
economyDevelopmentCost,
|
||||
ai->getFreeGold(),
|
||||
(float)dailyIncome[EGameResID::GOLD]);
|
||||
}
|
||||
|
||||
void BuildAnalyzer::reset()
|
||||
{
|
||||
requiredResources = TResources();
|
||||
totalDevelopmentCost = TResources();
|
||||
armyCost = TResources();
|
||||
developmentInfos.clear();
|
||||
}
|
||||
|
||||
bool BuildAnalyzer::isBuilt(FactionID alignment, BuildingID bid) const
|
||||
{
|
||||
for(auto tdi : developmentInfos)
|
||||
{
|
||||
if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling)
|
||||
{
|
||||
existingDwellings.push_back(existingDwelling);
|
||||
|
||||
armyCost += existingDwelling.armyCost;
|
||||
armyStrength += existingDwelling.armyStrength;
|
||||
}
|
||||
|
||||
void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
|
||||
{
|
||||
townDevelopmentCost += nextToBuild.buildCostWithPrerequisites;
|
||||
townDevelopmentCost += BuildAnalyzer::withoutGold(nextToBuild.armyCost);
|
||||
|
||||
if(nextToBuild.canBuild)
|
||||
{
|
||||
hasSomethingToBuild = true;
|
||||
toBuild.push_back(nextToBuild);
|
||||
}
|
||||
else if(nextToBuild.notEnoughRes)
|
||||
{
|
||||
requiredResources += nextToBuild.buildCost;
|
||||
hasSomethingToBuild = true;
|
||||
toBuild.push_back(nextToBuild);
|
||||
}
|
||||
}
|
||||
|
||||
BuildingInfo::BuildingInfo()
|
||||
{
|
||||
id = BuildingID::NONE;
|
||||
creatureGrows = 0;
|
||||
creatureID = CreatureID::NONE;
|
||||
buildCost = 0;
|
||||
buildCostWithPrerequisites = 0;
|
||||
prerequisitesCount = 0;
|
||||
name.clear();
|
||||
armyStrength = 0;
|
||||
}
|
||||
|
||||
BuildingInfo::BuildingInfo(
|
||||
const CBuilding * building,
|
||||
const CCreature * creature,
|
||||
CreatureID baseCreature,
|
||||
const CGTownInstance * town,
|
||||
const std::unique_ptr<ArmyManager> & armyManager)
|
||||
{
|
||||
id = building->bid;
|
||||
buildCost = building->resources;
|
||||
buildCostWithPrerequisites = building->resources;
|
||||
dailyIncome = building->produce;
|
||||
exists = town->hasBuilt(id);
|
||||
prerequisitesCount = 1;
|
||||
name = building->getNameTranslated();
|
||||
|
||||
if(creature)
|
||||
{
|
||||
creatureGrows = creature->getGrowth();
|
||||
creatureID = creature->getId();
|
||||
creatureCost = creature->getFullRecruitCost();
|
||||
creatureLevel = creature->getLevel();
|
||||
baseCreatureID = baseCreature;
|
||||
|
||||
if(exists)
|
||||
{
|
||||
creatureGrows = town->creatureGrowth(creatureLevel - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(id.isDwelling())
|
||||
{
|
||||
creatureGrows = creature->getGrowth();
|
||||
|
||||
if(town->hasBuilt(BuildingID::CASTLE))
|
||||
creatureGrows *= 2;
|
||||
else if(town->hasBuilt(BuildingID::CITADEL))
|
||||
creatureGrows += creatureGrows / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
creatureGrows = creature->getHorde();
|
||||
}
|
||||
}
|
||||
|
||||
armyStrength = armyManager->evaluateStackPower(creature, creatureGrows);
|
||||
armyCost = creatureCost * creatureGrows;
|
||||
}
|
||||
else
|
||||
{
|
||||
creatureGrows = 0;
|
||||
creatureID = CreatureID::NONE;
|
||||
baseCreatureID = CreatureID::NONE;
|
||||
creatureCost = TResources();
|
||||
armyCost = TResources();
|
||||
creatureLevel = 0;
|
||||
armyStrength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildingInfo::toString() const
|
||||
{
|
||||
return name + ", cost: " + buildCost.toString()
|
||||
+ ", creature: " + std::to_string(creatureGrows) + " x " + std::to_string(creatureLevel)
|
||||
+ " x " + creatureCost.toString()
|
||||
+ ", daily: " + dailyIncome.toString();
|
||||
}
|
||||
|
||||
float BuildAnalyzer::calculateGoldPressure(TResource lockedGold, float armyCostGold, float economyDevelopmentCost, float freeGold, float dailyIncomeGold)
|
||||
{
|
||||
auto pressure = (lockedGold + armyCostGold + economyDevelopmentCost) / (1 + 2 * freeGold + dailyIncomeGold * 7.0f);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Gold pressure: %f", pressure);
|
||||
#endif
|
||||
|
||||
return pressure;
|
||||
}
|
||||
|
||||
TResources BuildAnalyzer::calculateDailyIncome(std::vector<const CGObjectInstance *> objects, std::vector<const CGTownInstance *> townInfos)
|
||||
{
|
||||
auto result = TResources();
|
||||
|
||||
for(const CGObjectInstance * obj : objects)
|
||||
{
|
||||
if(const CGMine * mine = dynamic_cast<const CGMine *>(obj))
|
||||
result += mine->dailyIncome();
|
||||
}
|
||||
|
||||
for(const CGTownInstance * town : townInfos)
|
||||
{
|
||||
result += town->dailyIncome();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo, std::unique_ptr<ArmyManager> & armyManager, std::shared_ptr<CCallback> & cb)
|
||||
{
|
||||
for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
|
||||
{
|
||||
@@ -35,7 +265,7 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
if (!developmentInfo.town->hasBuilt(buildID))
|
||||
continue;
|
||||
|
||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID, armyManager, cb);
|
||||
developmentInfo.addExistingDwelling(info);
|
||||
break;
|
||||
}
|
||||
@@ -46,14 +276,16 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
if (developmentInfo.town->hasBuilt(buildID))
|
||||
continue;
|
||||
|
||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID, armyManager, cb);
|
||||
if (info.canBuild || info.notEnoughRes)
|
||||
developmentInfo.addBuildingToBuild(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
||||
void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo,
|
||||
std::unique_ptr<ArmyManager> & armyManager,
|
||||
std::shared_ptr<CCallback> & cb)
|
||||
{
|
||||
logAi->trace("Checking other buildings");
|
||||
|
||||
@@ -62,7 +294,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
||||
{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
|
||||
};
|
||||
|
||||
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > 4)
|
||||
if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > 4)
|
||||
{
|
||||
otherBuildings.push_back({BuildingID::HORDE_1});
|
||||
otherBuildings.push_back({BuildingID::HORDE_2});
|
||||
@@ -82,122 +314,19 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
||||
{
|
||||
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID))
|
||||
{
|
||||
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
|
||||
|
||||
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID, armyManager, cb));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t convertToGold(const TResources & res)
|
||||
{
|
||||
return res[EGameResID::GOLD]
|
||||
+ 75 * (res[EGameResID::WOOD] + res[EGameResID::ORE])
|
||||
+ 125 * (res[EGameResID::GEMS] + res[EGameResID::CRYSTAL] + res[EGameResID::MERCURY] + res[EGameResID::SULFUR]);
|
||||
}
|
||||
|
||||
TResources withoutGold(TResources other)
|
||||
{
|
||||
other[GameResID::GOLD] = 0;
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
TResources BuildAnalyzer::getResourcesRequiredNow() const
|
||||
{
|
||||
auto resourcesAvailable = ai->getFreeResources();
|
||||
auto result = withoutGold(armyCost) + requiredResources - resourcesAvailable;
|
||||
|
||||
result.positive();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TResources BuildAnalyzer::getTotalResourcesRequired() const
|
||||
{
|
||||
auto resourcesAvailable = ai->getFreeResources();
|
||||
auto result = totalDevelopmentCost + withoutGold(armyCost) - resourcesAvailable;
|
||||
|
||||
result.positive();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BuildAnalyzer::isGoldPressureHigh() const
|
||||
{
|
||||
return goldPressure > ai->settings->getMaxGoldPressure();
|
||||
}
|
||||
|
||||
void BuildAnalyzer::update()
|
||||
{
|
||||
logAi->trace("Start analysing build");
|
||||
|
||||
BuildingInfo bi;
|
||||
|
||||
reset();
|
||||
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
float economyDevelopmentCost = 0;
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
if(town->built >= cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP))
|
||||
continue; // Not much point in trying anything - can't built in this town anymore today
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Checking town %s", town->getNameTranslated());
|
||||
#endif
|
||||
developmentInfos.push_back(TownDevelopmentInfo(town));
|
||||
TownDevelopmentInfo & developmentInfo = developmentInfos.back();
|
||||
|
||||
updateTownDwellings(developmentInfo);
|
||||
updateOtherBuildings(developmentInfo);
|
||||
|
||||
requiredResources += developmentInfo.requiredResources;
|
||||
totalDevelopmentCost += developmentInfo.townDevelopmentCost;
|
||||
for(auto building : developmentInfo.toBuild)
|
||||
{
|
||||
if (building.dailyIncome[EGameResID::GOLD] > 0)
|
||||
economyDevelopmentCost += building.buildCostWithPrerequisites[EGameResID::GOLD];
|
||||
}
|
||||
armyCost += developmentInfo.armyCost;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
for(auto bi : developmentInfo.toBuild)
|
||||
logAi->trace("Building preferences %s", bi.toString());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::sort(developmentInfos.begin(), developmentInfos.end(), [](const TownDevelopmentInfo & t1, const TownDevelopmentInfo & t2) -> bool
|
||||
{
|
||||
auto val1 = convertToGold(t1.armyCost) - convertToGold(t1.townDevelopmentCost);
|
||||
auto val2 = convertToGold(t2.armyCost) - convertToGold(t2.townDevelopmentCost);
|
||||
|
||||
return val1 > val2;
|
||||
});
|
||||
|
||||
updateDailyIncome();
|
||||
|
||||
goldPressure = (ai->getLockedResources()[EGameResID::GOLD] + (float)armyCost[EGameResID::GOLD] + economyDevelopmentCost) / (1 + 2 * ai->getFreeGold() + (float)dailyIncome[EGameResID::GOLD] * 7.0f);
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("Gold pressure: %f", goldPressure);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BuildAnalyzer::reset()
|
||||
{
|
||||
requiredResources = TResources();
|
||||
totalDevelopmentCost = TResources();
|
||||
armyCost = TResources();
|
||||
developmentInfos.clear();
|
||||
}
|
||||
|
||||
BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
const CGTownInstance * town,
|
||||
BuildingID toBuild,
|
||||
bool excludeDwellingDependencies) const
|
||||
std::unique_ptr<ArmyManager> & armyManager,
|
||||
std::shared_ptr<CCallback> & cb,
|
||||
bool excludeDwellingDependencies)
|
||||
{
|
||||
BuildingID building = toBuild;
|
||||
auto townInfo = town->getTown();
|
||||
@@ -234,19 +363,19 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
creature = creatureID.toCreature();
|
||||
}
|
||||
|
||||
auto info = BuildingInfo(buildPtr.get(), creature, baseCreatureID, town, ai);
|
||||
auto info = BuildingInfo(buildPtr.get(), creature, baseCreatureID, town, armyManager);
|
||||
|
||||
//logAi->trace("checking %s buildInfo %s", info.name, info.toString());
|
||||
|
||||
int highestFort = 0;
|
||||
for (auto twn : ai->cb->getTownsInfo())
|
||||
for (auto ti : cb->getTownsInfo())
|
||||
{
|
||||
highestFort = std::max(highestFort, (int)twn->fortLevel());
|
||||
highestFort = std::max(highestFort, (int)ti->fortLevel());
|
||||
}
|
||||
|
||||
if(!town->hasBuilt(building))
|
||||
{
|
||||
auto canBuild = ai->cb->canBuildStructure(town, building);
|
||||
auto canBuild = cb->canBuildStructure(town, building);
|
||||
|
||||
if(canBuild == EBuildingState::ALLOWED)
|
||||
{
|
||||
@@ -281,7 +410,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num);
|
||||
#endif
|
||||
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
|
||||
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], armyManager, cb, excludeDwellingDependencies);
|
||||
|
||||
prerequisite.buildCostWithPrerequisites += info.buildCost;
|
||||
prerequisite.creatureCost = info.creatureCost;
|
||||
@@ -330,141 +459,18 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
return info;
|
||||
}
|
||||
|
||||
void BuildAnalyzer::updateDailyIncome()
|
||||
int32_t BuildAnalyzer::approximateInGold(const TResources & res)
|
||||
{
|
||||
auto objects = ai->cb->getMyObjects();
|
||||
auto towns = ai->cb->getTownsInfo();
|
||||
|
||||
dailyIncome = TResources();
|
||||
|
||||
for(const CGObjectInstance* obj : objects)
|
||||
{
|
||||
const CGMine* mine = dynamic_cast<const CGMine*>(obj);
|
||||
|
||||
if(mine)
|
||||
dailyIncome += mine->dailyIncome();
|
||||
}
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
{
|
||||
dailyIncome += town->dailyIncome();
|
||||
}
|
||||
// TODO: Would it make sense to use the marketplace rate of the player?
|
||||
return res[EGameResID::GOLD]
|
||||
+ 75 * (res[EGameResID::WOOD] + res[EGameResID::ORE])
|
||||
+ 125 * (res[EGameResID::GEMS] + res[EGameResID::CRYSTAL] + res[EGameResID::MERCURY] + res[EGameResID::SULFUR]);
|
||||
}
|
||||
|
||||
bool BuildAnalyzer::hasAnyBuilding(FactionID alignment, BuildingID bid) const
|
||||
TResources BuildAnalyzer::withoutGold(TResources other)
|
||||
{
|
||||
for(auto tdi : developmentInfos)
|
||||
{
|
||||
if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling)
|
||||
{
|
||||
existingDwellings.push_back(existingDwelling);
|
||||
|
||||
armyCost += existingDwelling.armyCost;
|
||||
armyStrength += existingDwelling.armyStrength;
|
||||
}
|
||||
|
||||
void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
|
||||
{
|
||||
townDevelopmentCost += nextToBuild.buildCostWithPrerequisites;
|
||||
townDevelopmentCost += withoutGold(nextToBuild.armyCost);
|
||||
|
||||
if(nextToBuild.canBuild)
|
||||
{
|
||||
hasSomethingToBuild = true;
|
||||
toBuild.push_back(nextToBuild);
|
||||
}
|
||||
else if(nextToBuild.notEnoughRes)
|
||||
{
|
||||
requiredResources += nextToBuild.buildCost;
|
||||
hasSomethingToBuild = true;
|
||||
toBuild.push_back(nextToBuild);
|
||||
}
|
||||
}
|
||||
|
||||
BuildingInfo::BuildingInfo()
|
||||
{
|
||||
id = BuildingID::NONE;
|
||||
creatureGrows = 0;
|
||||
creatureID = CreatureID::NONE;
|
||||
buildCost = 0;
|
||||
buildCostWithPrerequisites = 0;
|
||||
prerequisitesCount = 0;
|
||||
name.clear();
|
||||
armyStrength = 0;
|
||||
}
|
||||
|
||||
BuildingInfo::BuildingInfo(
|
||||
const CBuilding * building,
|
||||
const CCreature * creature,
|
||||
CreatureID baseCreature,
|
||||
const CGTownInstance * town,
|
||||
Nullkiller * ai)
|
||||
{
|
||||
id = building->bid;
|
||||
buildCost = building->resources;
|
||||
buildCostWithPrerequisites = building->resources;
|
||||
dailyIncome = building->produce;
|
||||
exists = town->hasBuilt(id);
|
||||
prerequisitesCount = 1;
|
||||
name = building->getNameTranslated();
|
||||
|
||||
if(creature)
|
||||
{
|
||||
creatureGrows = creature->getGrowth();
|
||||
creatureID = creature->getId();
|
||||
creatureCost = creature->getFullRecruitCost();
|
||||
creatureLevel = creature->getLevel();
|
||||
baseCreatureID = baseCreature;
|
||||
|
||||
if(exists)
|
||||
{
|
||||
creatureGrows = town->creatureGrowth(creatureLevel - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(id.isDwelling())
|
||||
{
|
||||
creatureGrows = creature->getGrowth();
|
||||
|
||||
if(town->hasBuilt(BuildingID::CASTLE))
|
||||
creatureGrows *= 2;
|
||||
else if(town->hasBuilt(BuildingID::CITADEL))
|
||||
creatureGrows += creatureGrows / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
creatureGrows = creature->getHorde();
|
||||
}
|
||||
}
|
||||
|
||||
armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);
|
||||
armyCost = creatureCost * creatureGrows;
|
||||
}
|
||||
else
|
||||
{
|
||||
creatureGrows = 0;
|
||||
creatureID = CreatureID::NONE;
|
||||
baseCreatureID = CreatureID::NONE;
|
||||
creatureCost = TResources();
|
||||
armyCost = TResources();
|
||||
creatureLevel = 0;
|
||||
armyStrength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildingInfo::toString() const
|
||||
{
|
||||
return name + ", cost: " + buildCost.toString()
|
||||
+ ", creature: " + std::to_string(creatureGrows) + " x " + std::to_string(creatureLevel)
|
||||
+ " x " + creatureCost.toString()
|
||||
+ ", daily: " + dailyIncome.toString();
|
||||
other[GameResID::GOLD] = 0;
|
||||
return other;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
namespace NK2AI
|
||||
{
|
||||
class ArmyManager;
|
||||
|
||||
class Nullkiller;
|
||||
|
||||
@@ -44,7 +45,7 @@ public:
|
||||
const CCreature * creature,
|
||||
CreatureID baseCreature,
|
||||
const CGTownInstance * town,
|
||||
Nullkiller * ai);
|
||||
const std::unique_ptr<ArmyManager> & armyManager);
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
@@ -96,20 +97,23 @@ public:
|
||||
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
|
||||
TResources getDailyIncome() const { return dailyIncome; }
|
||||
float getGoldPressure() const { return goldPressure; }
|
||||
bool isGoldPressureHigh() const;
|
||||
bool hasAnyBuilding(FactionID alignment, BuildingID bid) const;
|
||||
bool isGoldPressureOverMax() const;
|
||||
bool isBuilt(FactionID alignment, BuildingID bid) const;
|
||||
|
||||
private:
|
||||
BuildingInfo getBuildingOrPrerequisite(
|
||||
void reset();
|
||||
|
||||
static float calculateGoldPressure(TResource lockedGold, float armyCostGold, float economyDevelopmentCost, float freeGold, float dailyIncomeGold);
|
||||
static TResources calculateDailyIncome(std::vector<const CGObjectInstance *> objects, std::vector<const CGTownInstance *> townInfos);
|
||||
static void updateTownDwellings(TownDevelopmentInfo& developmentInfo, std::unique_ptr<ArmyManager>& armyManager, std::shared_ptr<CCallback>& cb);
|
||||
static void updateOtherBuildings(TownDevelopmentInfo& developmentInfo, std::unique_ptr<ArmyManager>& armyManager, std::shared_ptr<CCallback>& cb);
|
||||
static BuildingInfo getBuildingOrPrerequisite(
|
||||
const CGTownInstance* town,
|
||||
BuildingID toBuild,
|
||||
bool excludeDwellingDependencies = true) const;
|
||||
|
||||
|
||||
void updateTownDwellings(TownDevelopmentInfo & developmentInfo);
|
||||
void updateOtherBuildings(TownDevelopmentInfo & developmentInfo);
|
||||
void updateDailyIncome();
|
||||
void reset();
|
||||
std::unique_ptr<ArmyManager> & armyManager,
|
||||
std::shared_ptr<CCallback> & cb,
|
||||
bool excludeDwellingDependencies = true);
|
||||
static int32_t approximateInGold(const TResources & res);
|
||||
static TResources withoutGold(TResources other);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const
|
||||
totalDevelopmentCost.toString());
|
||||
|
||||
auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo();
|
||||
auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureHigh();
|
||||
auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureOverMax();
|
||||
|
||||
ai->dangerHitMap->updateHitMap();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ Goals::TGoalVec BuyArmyBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
uint8_t closestThreat = ai->dangerHitMap->getTileThreat(town->visitablePos()).fastestDanger.turn;
|
||||
|
||||
if (closestThreat >=2 && ai->buildAnalyzer->isGoldPressureHigh() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
|
||||
if (closestThreat >=2 && ai->buildAnalyzer->isGoldPressureOverMax() && !town->hasBuilt(BuildingID::CITY_HALL) && cb->canBuildStructure(town, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
|
||||
{
|
||||
return tasks;
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
||||
|
||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||
&& ai->getFreeGold() >20000
|
||||
&& !ai->buildAnalyzer->isGoldPressureHigh())
|
||||
&& !ai->buildAnalyzer->isGoldPressureOverMax())
|
||||
{
|
||||
Composition recruitHero;
|
||||
|
||||
|
||||
@@ -130,9 +130,9 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
||||
{
|
||||
if (ai->cb->getHeroesInfo().size() == 0
|
||||
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
||||
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
||||
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !ai->buildAnalyzer->isGoldPressureOverMax()))
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureOverMax() && haveCapitol)
|
||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureOverMax()))
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1))));
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ void DeepDecomposer::decompose(TGoalVec & results, TSubgoal behavior, int depthL
|
||||
|
||||
goals[0] = {behavior};
|
||||
|
||||
while(goals[0].size())
|
||||
while(!goals[0].empty())
|
||||
{
|
||||
bool fromCache;
|
||||
TSubgoal current = goals[depth].back();
|
||||
@@ -61,7 +61,7 @@ void DeepDecomposer::decompose(TGoalVec & results, TSubgoal behavior, int depthL
|
||||
goals[depth + 1].clear();
|
||||
}
|
||||
|
||||
for(TSubgoal subgoal : subgoals)
|
||||
for(const TSubgoal & subgoal : subgoals)
|
||||
{
|
||||
if(subgoal->invalid())
|
||||
continue;
|
||||
@@ -104,7 +104,7 @@ void DeepDecomposer::decompose(TGoalVec & results, TSubgoal behavior, int depthL
|
||||
}
|
||||
}
|
||||
|
||||
if(depth < depthLimit - 1 && goals[depth + 1].size())
|
||||
if(depth < depthLimit - 1 && !goals[depth + 1].empty())
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "Nullkiller.h"
|
||||
|
||||
#include <boost/range/algorithm/sort.hpp>
|
||||
#include "../AIGateway.h"
|
||||
#include "../Behaviors/CaptureObjectsBehavior.h"
|
||||
#include "../Behaviors/RecruitHeroBehavior.h"
|
||||
@@ -61,12 +63,7 @@ bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
||||
return cb->getPlayerState(teamMateID)->isHuman();
|
||||
});
|
||||
|
||||
if(hasHumanInTeam)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !hasHumanInTeam;
|
||||
}
|
||||
|
||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
@@ -117,7 +114,7 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
||||
armyFormation.reset(new ArmyFormation(cb, this));
|
||||
}
|
||||
|
||||
TaskPlanItem::TaskPlanItem(TSubgoal task)
|
||||
TaskPlanItem::TaskPlanItem(const TSubgoal & task)
|
||||
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
|
||||
{
|
||||
}
|
||||
@@ -136,7 +133,7 @@ Goals::TTaskVec TaskPlan::getTasks() const
|
||||
return result;
|
||||
}
|
||||
|
||||
void TaskPlan::merge(TSubgoal task)
|
||||
void TaskPlan::merge(const TSubgoal & task)
|
||||
{
|
||||
TGoalVec blockers;
|
||||
|
||||
@@ -158,9 +155,9 @@ void TaskPlan::merge(TSubgoal task)
|
||||
}
|
||||
}
|
||||
|
||||
vstd::erase_if(tasks, [&](const TaskPlanItem & task)
|
||||
vstd::erase_if(tasks, [&](const TaskPlanItem & task2)
|
||||
{
|
||||
return vstd::contains(blockers, task.task);
|
||||
return vstd::contains(blockers, task2.task);
|
||||
});
|
||||
|
||||
tasks.emplace_back(task);
|
||||
@@ -173,13 +170,13 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
|
||||
return taskptr(Invalid());
|
||||
}
|
||||
|
||||
for(TSubgoal & task : tasks)
|
||||
for(const TSubgoal & task : tasks)
|
||||
{
|
||||
if(task->asTask()->priority <= 0)
|
||||
task->asTask()->priority = priorityEvaluator->evaluate(task);
|
||||
}
|
||||
|
||||
auto bestTask = *vstd::maxElementByFun(tasks, [](Goals::TSubgoal task) -> float
|
||||
auto bestTask = *vstd::maxElementByFun(tasks, [](const Goals::TSubgoal& task) -> float
|
||||
{
|
||||
return task->asTask()->priority;
|
||||
});
|
||||
@@ -197,18 +194,18 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
|
||||
|
||||
for(size_t i = r.begin(); i != r.end(); i++)
|
||||
{
|
||||
auto task = tasks[i];
|
||||
const auto & task = tasks[i];
|
||||
if (task->asTask()->priority <= 0 || priorityTier != PriorityEvaluator::PriorityTier::BUILDINGS)
|
||||
task->asTask()->priority = evaluator->evaluate(task, priorityTier);
|
||||
}
|
||||
});
|
||||
|
||||
std::sort(tasks.begin(), tasks.end(), [](TSubgoal g1, TSubgoal g2) -> bool
|
||||
boost::range::sort(tasks, [](const TSubgoal& g1, const TSubgoal& g2) -> bool
|
||||
{
|
||||
return g2->asTask()->priority < g1->asTask()->priority;
|
||||
});
|
||||
|
||||
for(TSubgoal & task : tasks)
|
||||
for(const TSubgoal & task : tasks)
|
||||
{
|
||||
taskPlan.merge(task);
|
||||
}
|
||||
@@ -216,22 +213,15 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
|
||||
return taskPlan.getTasks();
|
||||
}
|
||||
|
||||
void Nullkiller::decompose(Goals::TGoalVec & results, Goals::TSubgoal behavior, int decompositionMaxDepth) const
|
||||
void Nullkiller::decompose(Goals::TGoalVec & results, const Goals::TSubgoal& behavior, int decompositionMaxDepth) const
|
||||
{
|
||||
makingTurnInterrupption.interruptionPoint();
|
||||
|
||||
logAi->debug("Checking behavior %s", behavior->toString());
|
||||
|
||||
logAi->debug("Decomposing behavior %s", behavior->toString());
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
decomposer->decompose(results, behavior, decompositionMaxDepth);
|
||||
|
||||
makingTurnInterrupption.interruptionPoint();
|
||||
|
||||
logAi->debug(
|
||||
"Behavior %s. Time taken %ld",
|
||||
behavior->toString(),
|
||||
timeElapsed(start));
|
||||
logAi->debug("Decomposing behavior %s done in %ld", behavior->toString(), timeElapsed(start));
|
||||
}
|
||||
|
||||
void Nullkiller::resetState()
|
||||
@@ -266,6 +256,7 @@ void Nullkiller::updateState(bool partialUpdate)
|
||||
activeHero = nullptr;
|
||||
setTargetObject(-1);
|
||||
decomposer->reset();
|
||||
|
||||
buildAnalyzer->update();
|
||||
|
||||
if (!pathfinderInvalidated)
|
||||
@@ -380,35 +371,10 @@ void Nullkiller::makeTurn()
|
||||
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
||||
{
|
||||
updateState();
|
||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
||||
|
||||
for(int j = 1; j <= settings->getMaxPriorityPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; j++)
|
||||
{
|
||||
tasks.clear();
|
||||
|
||||
decompose(tasks, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(tasks, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(tasks, sptr(BuildingBehavior()), 1);
|
||||
|
||||
bestTask = choseBestTask(tasks);
|
||||
if(bestTask->priority > 0)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Pass %d: Performing prio 0 task %s with prio: %d", i, bestTask->toString(), bestTask->priority);
|
||||
#endif
|
||||
|
||||
if(!executeTask(bestTask))
|
||||
return;
|
||||
|
||||
updateState(bestTask->getHero() == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
tasks.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!makeTurnHelperPriorityPass(tasks, i)) return;
|
||||
|
||||
tasks.clear();
|
||||
decompose(tasks, sptr(CaptureObjectsBehavior()), 1);
|
||||
decompose(tasks, sptr(ClusterBehavior()), MAX_DEPTH);
|
||||
decompose(tasks, sptr(DefenceBehavior()), MAX_DEPTH);
|
||||
@@ -419,17 +385,10 @@ void Nullkiller::makeTurn()
|
||||
decompose(tasks, sptr(ExplorationBehavior()), MAX_DEPTH);
|
||||
|
||||
TTaskVec selectedTasks;
|
||||
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
int prioOfTask = 0;
|
||||
#endif
|
||||
|
||||
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
prioOfTask = prio;
|
||||
#endif
|
||||
|
||||
selectedTasks = buildPlan(tasks, prio);
|
||||
if (!selectedTasks.empty() || settings->isUseFuzzy())
|
||||
break;
|
||||
@@ -446,7 +405,6 @@ void Nullkiller::makeTurn()
|
||||
}
|
||||
|
||||
bool hasAnySuccess = false;
|
||||
|
||||
for(const auto& selectedTask : selectedTasks)
|
||||
{
|
||||
if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
|
||||
@@ -480,7 +438,7 @@ void Nullkiller::makeTurn()
|
||||
if((settings->isUseFuzzy() && selectedTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && selectedTask->priority <= 0))
|
||||
{
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
const auto hasMp = vstd::contains_if(heroes, [](const CGHeroInstance * h) -> bool
|
||||
{
|
||||
return h->movementPointsRemaining() > 100;
|
||||
});
|
||||
@@ -499,7 +457,6 @@ void Nullkiller::makeTurn()
|
||||
}
|
||||
|
||||
logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -511,13 +468,9 @@ void Nullkiller::makeTurn()
|
||||
{
|
||||
if(hasAnySuccess)
|
||||
break;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasAnySuccess = true;
|
||||
return;
|
||||
}
|
||||
hasAnySuccess = true;
|
||||
}
|
||||
|
||||
hasAnySuccess |= handleTrading();
|
||||
@@ -533,11 +486,48 @@ void Nullkiller::makeTurn()
|
||||
|
||||
if(i == settings->getMaxPass())
|
||||
{
|
||||
logAi->warn("Maxpass exceeded. Terminating AI turn.");
|
||||
logAi->warn("MaxPass reached. Terminating AI turn.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Nullkiller::makeTurnHelperPriorityPass(Goals::TGoalVec & tempResults, int passIndex)
|
||||
{
|
||||
Goals::TTask bestPrioPassTask = taskptr(Goals::Invalid());
|
||||
for(int i = 1; i <= settings->getMaxPriorityPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
||||
{
|
||||
tempResults.clear();
|
||||
|
||||
decompose(tempResults, sptr(RecruitHeroBehavior()), 1);
|
||||
decompose(tempResults, sptr(BuyArmyBehavior()), 1);
|
||||
decompose(tempResults, sptr(BuildingBehavior()), 1);
|
||||
|
||||
bestPrioPassTask = choseBestTask(tempResults);
|
||||
if(bestPrioPassTask->priority > 0)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logAi->info("Pass %d: Performing priorityPass %d task %s with prio: %d", passIndex, i, bestPrioPassTask->toString(), bestPrioPassTask->priority);
|
||||
#endif
|
||||
|
||||
if(!executeTask(bestPrioPassTask))
|
||||
return false;
|
||||
|
||||
// TODO: Inspect why it's ok to do a partial update if condition is true
|
||||
updateState(bestPrioPassTask->getHero() == nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(i == settings->getMaxPriorityPass())
|
||||
{
|
||||
logAi->warn("MaxPriorityPass reached. Terminating priorityPass loop.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
|
||||
{
|
||||
auto affectedObjs = task->getAffectedObjects();
|
||||
@@ -661,7 +651,7 @@ bool Nullkiller::handleTrading()
|
||||
|
||||
if (i == GameResID::GOLD)
|
||||
{
|
||||
if (income[i] > 0 && !buildAnalyzer->isGoldPressureHigh())
|
||||
if (income[i] > 0 && !buildAnalyzer->isGoldPressureOverMax())
|
||||
okToSell = true;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -60,7 +60,7 @@ struct TaskPlanItem
|
||||
std::vector<ObjectInstanceID> affectedObjects;
|
||||
Goals::TSubgoal task;
|
||||
|
||||
TaskPlanItem(Goals::TSubgoal goal);
|
||||
TaskPlanItem(const Goals::TSubgoal& goal);
|
||||
};
|
||||
|
||||
class TaskPlan
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
|
||||
public:
|
||||
Goals::TTaskVec getTasks() const;
|
||||
void merge(Goals::TSubgoal task);
|
||||
void merge(const Goals::TSubgoal& task);
|
||||
};
|
||||
|
||||
class Nullkiller
|
||||
@@ -115,6 +115,7 @@ public:
|
||||
~Nullkiller();
|
||||
void init(std::shared_ptr<CCallback> cb, AIGateway * gateway);
|
||||
void makeTurn();
|
||||
bool makeTurnHelperPriorityPass(Goals::TGoalVec& tempResults, int passIndex);
|
||||
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
|
||||
bool isHeroLocked(const CGHeroInstance * hero) const;
|
||||
HeroPtr getActiveHero() { return activeHero; }
|
||||
@@ -142,7 +143,7 @@ public:
|
||||
private:
|
||||
void resetState();
|
||||
void updateState(bool partialUpdate = false);
|
||||
void decompose(Goals::TGoalVec & results, Goals::TSubgoal behavior, int decompositionMaxDepth) const;
|
||||
void decompose(Goals::TGoalVec & results, const Goals::TSubgoal& behavior, int decompositionMaxDepth) const;
|
||||
Goals::TTask choseBestTask(Goals::TGoalVec & tasks) const;
|
||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const;
|
||||
bool executeTask(Goals::TTask task);
|
||||
|
||||
@@ -1267,7 +1267,7 @@ public:
|
||||
|
||||
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
|
||||
{
|
||||
if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id))
|
||||
if(ai->buildAnalyzer->isBuilt(town->getFactionID(), bi.id))
|
||||
return 0;
|
||||
|
||||
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
|
||||
@@ -1616,7 +1616,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
||||
score += 1000;
|
||||
auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources();
|
||||
auto income = ai->buildAnalyzer->getDailyIncome();
|
||||
if(ai->buildAnalyzer->isGoldPressureHigh())
|
||||
if(ai->buildAnalyzer->isGoldPressureOverMax())
|
||||
score /= evaluationContext.buildingCost.marketValue();
|
||||
if (!resourcesAvailable.canAfford(evaluationContext.buildingCost))
|
||||
{
|
||||
|
||||
@@ -22,12 +22,15 @@ using namespace Goals;
|
||||
BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
||||
: ElementarGoal(Goals::BUILD_STRUCTURE)
|
||||
{
|
||||
// FIXME: Remove this constructor (the parent constructor BuildThis::BuildThis)
|
||||
// Seems like StartupBehavior is instantiating via this BuildThis constructor
|
||||
// Or needs to be unit tested to ensure there's no problem with the limited constructor params
|
||||
buildingInfo = BuildingInfo(
|
||||
tid->getTown()->buildings.at(Bid).get(),
|
||||
nullptr,
|
||||
CreatureID::NONE,
|
||||
tid,
|
||||
nullptr);
|
||||
std::unique_ptr<ArmyManager>(nullptr));
|
||||
|
||||
bid = Bid.getNum();
|
||||
town = tid;
|
||||
|
||||
Reference in New Issue
Block a user