mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-27 22:49:25 +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.size() == 2
|
||||||
&& components.front().type == ComponentType::RESOURCE
|
&& components.front().type == ComponentType::RESOURCE
|
||||||
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|
&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
|
||||||
|| nullkiller->buildAnalyzer->isGoldPressureHigh()))
|
|| nullkiller->buildAnalyzer->isGoldPressureOverMax()))
|
||||||
{
|
{
|
||||||
sel = 1;
|
sel = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
* Full text of license available in license.txt file, in main folder
|
* Full text of license available in license.txt file, in main folder
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
#include <boost/range/algorithm/sort.hpp>
|
||||||
|
|
||||||
#include "../StdInc.h"
|
#include "../StdInc.h"
|
||||||
#include "../Engine/Nullkiller.h"
|
#include "../Engine/Nullkiller.h"
|
||||||
#include "../Engine/Nullkiller.h"
|
#include "../Engine/Nullkiller.h"
|
||||||
@@ -16,7 +18,235 @@
|
|||||||
namespace NK2AI
|
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++)
|
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))
|
if (!developmentInfo.town->hasBuilt(buildID))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID, armyManager, cb);
|
||||||
developmentInfo.addExistingDwelling(info);
|
developmentInfo.addExistingDwelling(info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -46,14 +276,16 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
|||||||
if (developmentInfo.town->hasBuilt(buildID))
|
if (developmentInfo.town->hasBuilt(buildID))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID);
|
const auto & info = getBuildingOrPrerequisite(developmentInfo.town, buildID, armyManager, cb);
|
||||||
if (info.canBuild || info.notEnoughRes)
|
if (info.canBuild || info.notEnoughRes)
|
||||||
developmentInfo.addBuildingToBuild(info);
|
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");
|
logAi->trace("Checking other buildings");
|
||||||
|
|
||||||
@@ -62,7 +294,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
|
|||||||
{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
|
{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_1});
|
||||||
otherBuildings.push_back({BuildingID::HORDE_2});
|
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))
|
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;
|
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(
|
BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||||
const CGTownInstance * town,
|
const CGTownInstance * town,
|
||||||
BuildingID toBuild,
|
BuildingID toBuild,
|
||||||
bool excludeDwellingDependencies) const
|
std::unique_ptr<ArmyManager> & armyManager,
|
||||||
|
std::shared_ptr<CCallback> & cb,
|
||||||
|
bool excludeDwellingDependencies)
|
||||||
{
|
{
|
||||||
BuildingID building = toBuild;
|
BuildingID building = toBuild;
|
||||||
auto townInfo = town->getTown();
|
auto townInfo = town->getTown();
|
||||||
@@ -234,19 +363,19 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|||||||
creature = creatureID.toCreature();
|
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());
|
//logAi->trace("checking %s buildInfo %s", info.name, info.toString());
|
||||||
|
|
||||||
int highestFort = 0;
|
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))
|
if(!town->hasBuilt(building))
|
||||||
{
|
{
|
||||||
auto canBuild = ai->cb->canBuildStructure(town, building);
|
auto canBuild = cb->canBuildStructure(town, building);
|
||||||
|
|
||||||
if(canBuild == EBuildingState::ALLOWED)
|
if(canBuild == EBuildingState::ALLOWED)
|
||||||
{
|
{
|
||||||
@@ -281,7 +410,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num);
|
logAi->trace("cant build %d. Need %d", toBuild.getNum(), missingBuildings[0].num);
|
||||||
#endif
|
#endif
|
||||||
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
|
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], armyManager, cb, excludeDwellingDependencies);
|
||||||
|
|
||||||
prerequisite.buildCostWithPrerequisites += info.buildCost;
|
prerequisite.buildCostWithPrerequisites += info.buildCost;
|
||||||
prerequisite.creatureCost = info.creatureCost;
|
prerequisite.creatureCost = info.creatureCost;
|
||||||
@@ -330,141 +459,18 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BuildAnalyzer::updateDailyIncome()
|
int32_t BuildAnalyzer::approximateInGold(const TResources & res)
|
||||||
{
|
{
|
||||||
auto objects = ai->cb->getMyObjects();
|
// TODO: Would it make sense to use the marketplace rate of the player?
|
||||||
auto towns = ai->cb->getTownsInfo();
|
return res[EGameResID::GOLD]
|
||||||
|
+ 75 * (res[EGameResID::WOOD] + res[EGameResID::ORE])
|
||||||
dailyIncome = TResources();
|
+ 125 * (res[EGameResID::GEMS] + res[EGameResID::CRYSTAL] + res[EGameResID::MERCURY] + res[EGameResID::SULFUR]);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BuildAnalyzer::hasAnyBuilding(FactionID alignment, BuildingID bid) const
|
TResources BuildAnalyzer::withoutGold(TResources other)
|
||||||
{
|
{
|
||||||
for(auto tdi : developmentInfos)
|
other[GameResID::GOLD] = 0;
|
||||||
{
|
return other;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
namespace NK2AI
|
namespace NK2AI
|
||||||
{
|
{
|
||||||
|
class ArmyManager;
|
||||||
|
|
||||||
class Nullkiller;
|
class Nullkiller;
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ public:
|
|||||||
const CCreature * creature,
|
const CCreature * creature,
|
||||||
CreatureID baseCreature,
|
CreatureID baseCreature,
|
||||||
const CGTownInstance * town,
|
const CGTownInstance * town,
|
||||||
Nullkiller * ai);
|
const std::unique_ptr<ArmyManager> & armyManager);
|
||||||
|
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
};
|
};
|
||||||
@@ -96,20 +97,23 @@ public:
|
|||||||
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
|
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
|
||||||
TResources getDailyIncome() const { return dailyIncome; }
|
TResources getDailyIncome() const { return dailyIncome; }
|
||||||
float getGoldPressure() const { return goldPressure; }
|
float getGoldPressure() const { return goldPressure; }
|
||||||
bool isGoldPressureHigh() const;
|
bool isGoldPressureOverMax() const;
|
||||||
bool hasAnyBuilding(FactionID alignment, BuildingID bid) const;
|
bool isBuilt(FactionID alignment, BuildingID bid) const;
|
||||||
|
|
||||||
private:
|
void reset();
|
||||||
BuildingInfo getBuildingOrPrerequisite(
|
|
||||||
|
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,
|
const CGTownInstance* town,
|
||||||
BuildingID toBuild,
|
BuildingID toBuild,
|
||||||
bool excludeDwellingDependencies = true) const;
|
std::unique_ptr<ArmyManager> & armyManager,
|
||||||
|
std::shared_ptr<CCallback> & cb,
|
||||||
|
bool excludeDwellingDependencies = true);
|
||||||
void updateTownDwellings(TownDevelopmentInfo & developmentInfo);
|
static int32_t approximateInGold(const TResources & res);
|
||||||
void updateOtherBuildings(TownDevelopmentInfo & developmentInfo);
|
static TResources withoutGold(TResources other);
|
||||||
void updateDailyIncome();
|
|
||||||
void reset();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Goals::TGoalVec BuildingBehavior::decompose(const Nullkiller * ai) const
|
|||||||
totalDevelopmentCost.toString());
|
totalDevelopmentCost.toString());
|
||||||
|
|
||||||
auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo();
|
auto & developmentInfos = ai->buildAnalyzer->getDevelopmentInfo();
|
||||||
auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureHigh();
|
auto isGoldPressureLow = !ai->buildAnalyzer->isGoldPressureOverMax();
|
||||||
|
|
||||||
ai->dangerHitMap->updateHitMap();
|
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;
|
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;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const Nullkiller * ai, const CGT
|
|||||||
|
|
||||||
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
if(scoutReinforcement >= armyToGetOrBuy.upgradeValue
|
||||||
&& ai->getFreeGold() >20000
|
&& ai->getFreeGold() >20000
|
||||||
&& !ai->buildAnalyzer->isGoldPressureHigh())
|
&& !ai->buildAnalyzer->isGoldPressureOverMax())
|
||||||
{
|
{
|
||||||
Composition recruitHero;
|
Composition recruitHero;
|
||||||
|
|
||||||
|
|||||||
@@ -130,9 +130,9 @@ Goals::TGoalVec RecruitHeroBehavior::decompose(const Nullkiller * ai) const
|
|||||||
{
|
{
|
||||||
if (ai->cb->getHeroesInfo().size() == 0
|
if (ai->cb->getHeroesInfo().size() == 0
|
||||||
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
|| treasureSourcesCount > ai->cb->getHeroesInfo().size() * 5
|
||||||
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !ai->buildAnalyzer->isGoldPressureHigh()))
|
|| (bestHeroToHire->getArmyCost() > GameConstants::HERO_GOLD_COST / 2.0 && (bestClosestThreat < 1 || !ai->buildAnalyzer->isGoldPressureOverMax()))
|
||||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureHigh() && haveCapitol)
|
|| (ai->getFreeResources()[EGameResID::GOLD] > 10000 && !ai->buildAnalyzer->isGoldPressureOverMax() && haveCapitol)
|
||||||
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureHigh()))
|
|| (ai->getFreeResources()[EGameResID::GOLD] > 30000 && !ai->buildAnalyzer->isGoldPressureOverMax()))
|
||||||
{
|
{
|
||||||
tasks.push_back(Goals::sptr(Goals::RecruitHero(bestTownToHireFrom, bestHeroToHire).setpriority((float)3 / (ourHeroes.size() + 1))));
|
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};
|
goals[0] = {behavior};
|
||||||
|
|
||||||
while(goals[0].size())
|
while(!goals[0].empty())
|
||||||
{
|
{
|
||||||
bool fromCache;
|
bool fromCache;
|
||||||
TSubgoal current = goals[depth].back();
|
TSubgoal current = goals[depth].back();
|
||||||
@@ -61,7 +61,7 @@ void DeepDecomposer::decompose(TGoalVec & results, TSubgoal behavior, int depthL
|
|||||||
goals[depth + 1].clear();
|
goals[depth + 1].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for(TSubgoal subgoal : subgoals)
|
for(const TSubgoal & subgoal : subgoals)
|
||||||
{
|
{
|
||||||
if(subgoal->invalid())
|
if(subgoal->invalid())
|
||||||
continue;
|
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++;
|
depth++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
#include "StdInc.h"
|
#include "StdInc.h"
|
||||||
#include "Nullkiller.h"
|
#include "Nullkiller.h"
|
||||||
|
|
||||||
|
#include <boost/range/algorithm/sort.hpp>
|
||||||
#include "../AIGateway.h"
|
#include "../AIGateway.h"
|
||||||
#include "../Behaviors/CaptureObjectsBehavior.h"
|
#include "../Behaviors/CaptureObjectsBehavior.h"
|
||||||
#include "../Behaviors/RecruitHeroBehavior.h"
|
#include "../Behaviors/RecruitHeroBehavior.h"
|
||||||
@@ -61,12 +63,7 @@ bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
|
|||||||
return cb->getPlayerState(teamMateID)->isHuman();
|
return cb->getPlayerState(teamMateID)->isHuman();
|
||||||
});
|
});
|
||||||
|
|
||||||
if(hasHumanInTeam)
|
return !hasHumanInTeam;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
|
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));
|
armyFormation.reset(new ArmyFormation(cb, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskPlanItem::TaskPlanItem(TSubgoal task)
|
TaskPlanItem::TaskPlanItem(const TSubgoal & task)
|
||||||
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
|
:task(task), affectedObjects(task->asTask()->getAffectedObjects())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -136,7 +133,7 @@ Goals::TTaskVec TaskPlan::getTasks() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskPlan::merge(TSubgoal task)
|
void TaskPlan::merge(const TSubgoal & task)
|
||||||
{
|
{
|
||||||
TGoalVec blockers;
|
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);
|
tasks.emplace_back(task);
|
||||||
@@ -173,13 +170,13 @@ Goals::TTask Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
|
|||||||
return taskptr(Invalid());
|
return taskptr(Invalid());
|
||||||
}
|
}
|
||||||
|
|
||||||
for(TSubgoal & task : tasks)
|
for(const TSubgoal & task : tasks)
|
||||||
{
|
{
|
||||||
if(task->asTask()->priority <= 0)
|
if(task->asTask()->priority <= 0)
|
||||||
task->asTask()->priority = priorityEvaluator->evaluate(task);
|
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;
|
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++)
|
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)
|
if (task->asTask()->priority <= 0 || priorityTier != PriorityEvaluator::PriorityTier::BUILDINGS)
|
||||||
task->asTask()->priority = evaluator->evaluate(task, priorityTier);
|
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;
|
return g2->asTask()->priority < g1->asTask()->priority;
|
||||||
});
|
});
|
||||||
|
|
||||||
for(TSubgoal & task : tasks)
|
for(const TSubgoal & task : tasks)
|
||||||
{
|
{
|
||||||
taskPlan.merge(task);
|
taskPlan.merge(task);
|
||||||
}
|
}
|
||||||
@@ -216,22 +213,15 @@ Goals::TTaskVec Nullkiller::buildPlan(TGoalVec & tasks, int priorityTier) const
|
|||||||
return taskPlan.getTasks();
|
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();
|
makingTurnInterrupption.interruptionPoint();
|
||||||
|
logAi->debug("Decomposing behavior %s", behavior->toString());
|
||||||
logAi->debug("Checking behavior %s", behavior->toString());
|
|
||||||
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
decomposer->decompose(results, behavior, decompositionMaxDepth);
|
decomposer->decompose(results, behavior, decompositionMaxDepth);
|
||||||
|
|
||||||
makingTurnInterrupption.interruptionPoint();
|
makingTurnInterrupption.interruptionPoint();
|
||||||
|
logAi->debug("Decomposing behavior %s done in %ld", behavior->toString(), timeElapsed(start));
|
||||||
logAi->debug(
|
|
||||||
"Behavior %s. Time taken %ld",
|
|
||||||
behavior->toString(),
|
|
||||||
timeElapsed(start));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nullkiller::resetState()
|
void Nullkiller::resetState()
|
||||||
@@ -266,6 +256,7 @@ void Nullkiller::updateState(bool partialUpdate)
|
|||||||
activeHero = nullptr;
|
activeHero = nullptr;
|
||||||
setTargetObject(-1);
|
setTargetObject(-1);
|
||||||
decomposer->reset();
|
decomposer->reset();
|
||||||
|
|
||||||
buildAnalyzer->update();
|
buildAnalyzer->update();
|
||||||
|
|
||||||
if (!pathfinderInvalidated)
|
if (!pathfinderInvalidated)
|
||||||
@@ -380,35 +371,10 @@ void Nullkiller::makeTurn()
|
|||||||
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
for(int i = 1; i <= settings->getMaxPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; i++)
|
||||||
{
|
{
|
||||||
updateState();
|
updateState();
|
||||||
Goals::TTask bestTask = taskptr(Goals::Invalid());
|
|
||||||
|
|
||||||
for(int j = 1; j <= settings->getMaxPriorityPass() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME; j++)
|
if (!makeTurnHelperPriorityPass(tasks, i)) return;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
tasks.clear();
|
||||||
decompose(tasks, sptr(CaptureObjectsBehavior()), 1);
|
decompose(tasks, sptr(CaptureObjectsBehavior()), 1);
|
||||||
decompose(tasks, sptr(ClusterBehavior()), MAX_DEPTH);
|
decompose(tasks, sptr(ClusterBehavior()), MAX_DEPTH);
|
||||||
decompose(tasks, sptr(DefenceBehavior()), MAX_DEPTH);
|
decompose(tasks, sptr(DefenceBehavior()), MAX_DEPTH);
|
||||||
@@ -419,17 +385,10 @@ void Nullkiller::makeTurn()
|
|||||||
decompose(tasks, sptr(ExplorationBehavior()), MAX_DEPTH);
|
decompose(tasks, sptr(ExplorationBehavior()), MAX_DEPTH);
|
||||||
|
|
||||||
TTaskVec selectedTasks;
|
TTaskVec selectedTasks;
|
||||||
|
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
|
||||||
int prioOfTask = 0;
|
int prioOfTask = 0;
|
||||||
#endif
|
|
||||||
|
|
||||||
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
|
for (int prio = PriorityEvaluator::PriorityTier::INSTAKILL; prio <= PriorityEvaluator::PriorityTier::MAX_PRIORITY_TIER; ++prio)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
|
||||||
prioOfTask = prio;
|
prioOfTask = prio;
|
||||||
#endif
|
|
||||||
|
|
||||||
selectedTasks = buildPlan(tasks, prio);
|
selectedTasks = buildPlan(tasks, prio);
|
||||||
if (!selectedTasks.empty() || settings->isUseFuzzy())
|
if (!selectedTasks.empty() || settings->isUseFuzzy())
|
||||||
break;
|
break;
|
||||||
@@ -446,7 +405,6 @@ void Nullkiller::makeTurn()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool hasAnySuccess = false;
|
bool hasAnySuccess = false;
|
||||||
|
|
||||||
for(const auto& selectedTask : selectedTasks)
|
for(const auto& selectedTask : selectedTasks)
|
||||||
{
|
{
|
||||||
if(cb->getPlayerStatus(playerID) != EPlayerStatus::INGAME)
|
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))
|
if((settings->isUseFuzzy() && selectedTask->priority < MIN_PRIORITY) || (!settings->isUseFuzzy() && selectedTask->priority <= 0))
|
||||||
{
|
{
|
||||||
auto heroes = cb->getHeroesInfo();
|
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;
|
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);
|
logAi->trace("Goal %s has too low priority. It is not worth doing it.", taskDescription);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,13 +468,9 @@ void Nullkiller::makeTurn()
|
|||||||
{
|
{
|
||||||
if(hasAnySuccess)
|
if(hasAnySuccess)
|
||||||
break;
|
break;
|
||||||
else
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hasAnySuccess = true;
|
|
||||||
}
|
}
|
||||||
|
hasAnySuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAnySuccess |= handleTrading();
|
hasAnySuccess |= handleTrading();
|
||||||
@@ -533,11 +486,48 @@ void Nullkiller::makeTurn()
|
|||||||
|
|
||||||
if(i == settings->getMaxPass())
|
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
|
bool Nullkiller::areAffectedObjectsPresent(Goals::TTask task) const
|
||||||
{
|
{
|
||||||
auto affectedObjs = task->getAffectedObjects();
|
auto affectedObjs = task->getAffectedObjects();
|
||||||
@@ -661,7 +651,7 @@ bool Nullkiller::handleTrading()
|
|||||||
|
|
||||||
if (i == GameResID::GOLD)
|
if (i == GameResID::GOLD)
|
||||||
{
|
{
|
||||||
if (income[i] > 0 && !buildAnalyzer->isGoldPressureHigh())
|
if (income[i] > 0 && !buildAnalyzer->isGoldPressureOverMax())
|
||||||
okToSell = true;
|
okToSell = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ struct TaskPlanItem
|
|||||||
std::vector<ObjectInstanceID> affectedObjects;
|
std::vector<ObjectInstanceID> affectedObjects;
|
||||||
Goals::TSubgoal task;
|
Goals::TSubgoal task;
|
||||||
|
|
||||||
TaskPlanItem(Goals::TSubgoal goal);
|
TaskPlanItem(const Goals::TSubgoal& goal);
|
||||||
};
|
};
|
||||||
|
|
||||||
class TaskPlan
|
class TaskPlan
|
||||||
@@ -70,7 +70,7 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
Goals::TTaskVec getTasks() const;
|
Goals::TTaskVec getTasks() const;
|
||||||
void merge(Goals::TSubgoal task);
|
void merge(const Goals::TSubgoal& task);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Nullkiller
|
class Nullkiller
|
||||||
@@ -115,6 +115,7 @@ public:
|
|||||||
~Nullkiller();
|
~Nullkiller();
|
||||||
void init(std::shared_ptr<CCallback> cb, AIGateway * gateway);
|
void init(std::shared_ptr<CCallback> cb, AIGateway * gateway);
|
||||||
void makeTurn();
|
void makeTurn();
|
||||||
|
bool makeTurnHelperPriorityPass(Goals::TGoalVec& tempResults, int passIndex);
|
||||||
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
|
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
|
||||||
bool isHeroLocked(const CGHeroInstance * hero) const;
|
bool isHeroLocked(const CGHeroInstance * hero) const;
|
||||||
HeroPtr getActiveHero() { return activeHero; }
|
HeroPtr getActiveHero() { return activeHero; }
|
||||||
@@ -142,7 +143,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
void resetState();
|
void resetState();
|
||||||
void updateState(bool partialUpdate = false);
|
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::TTask choseBestTask(Goals::TGoalVec & tasks) const;
|
||||||
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const;
|
Goals::TTaskVec buildPlan(Goals::TGoalVec & tasks, int priorityTier) const;
|
||||||
bool executeTask(Goals::TTask task);
|
bool executeTask(Goals::TTask task);
|
||||||
|
|||||||
@@ -1267,7 +1267,7 @@ public:
|
|||||||
|
|
||||||
uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
|
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;
|
return 0;
|
||||||
|
|
||||||
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
|
auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
|
||||||
@@ -1616,7 +1616,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task, int priorityTier)
|
|||||||
score += 1000;
|
score += 1000;
|
||||||
auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources();
|
auto resourcesAvailable = evaluationContext.evaluator.ai->getFreeResources();
|
||||||
auto income = ai->buildAnalyzer->getDailyIncome();
|
auto income = ai->buildAnalyzer->getDailyIncome();
|
||||||
if(ai->buildAnalyzer->isGoldPressureHigh())
|
if(ai->buildAnalyzer->isGoldPressureOverMax())
|
||||||
score /= evaluationContext.buildingCost.marketValue();
|
score /= evaluationContext.buildingCost.marketValue();
|
||||||
if (!resourcesAvailable.canAfford(evaluationContext.buildingCost))
|
if (!resourcesAvailable.canAfford(evaluationContext.buildingCost))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ using namespace Goals;
|
|||||||
BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
||||||
: ElementarGoal(Goals::BUILD_STRUCTURE)
|
: 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(
|
buildingInfo = BuildingInfo(
|
||||||
tid->getTown()->buildings.at(Bid).get(),
|
tid->getTown()->buildings.at(Bid).get(),
|
||||||
nullptr,
|
nullptr,
|
||||||
CreatureID::NONE,
|
CreatureID::NONE,
|
||||||
tid,
|
tid,
|
||||||
nullptr);
|
std::unique_ptr<ArmyManager>(nullptr));
|
||||||
|
|
||||||
bid = Bid.getNum();
|
bid = Bid.getNum();
|
||||||
town = tid;
|
town = tid;
|
||||||
|
|||||||
Reference in New Issue
Block a user