1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00
vcmi/AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

462 lines
12 KiB
C++
Raw Normal View History

/*
* BuildAnalyzer.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
2021-05-16 13:55:57 +02:00
#include "../StdInc.h"
2021-05-16 13:55:47 +02:00
#include "../Engine/Nullkiller.h"
2021-05-16 13:57:29 +02:00
#include "../Engine/Nullkiller.h"
2024-07-21 12:49:40 +02:00
#include "../../../lib/entities/building/CBuilding.h"
2022-09-26 20:01:07 +02:00
namespace NKAI
{
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
{
auto townInfo = developmentInfo.town->getTown();
auto creatures = townInfo->creatures;
auto buildings = townInfo->getAllBuildings();
std::map<BuildingID, BuildingID> parentMap;
for(auto &pair : townInfo->buildings)
{
if(pair.second->upgrade != BuildingID::NONE)
{
parentMap[pair.second->upgrade] = pair.first;
}
}
for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
{
logAi->trace("Checking dwelling level %d", level);
BuildingInfo nextToBuild = BuildingInfo();
2024-08-10 00:29:30 +02:00
for(int upgradeIndex : {1, 0})
{
2024-08-10 00:29:30 +02:00
BuildingID building = BuildingID(BuildingID::getDwellingFromLevel(level, upgradeIndex));
if(!vstd::contains(buildings, building))
continue; // no such building in town
auto info = getBuildingOrPrerequisite(developmentInfo.town, building);
if(info.exists)
{
developmentInfo.addExistingDwelling(info);
break;
}
nextToBuild = info;
}
if(nextToBuild.id != BuildingID::NONE)
{
developmentInfo.addBuildingToBuild(nextToBuild);
}
}
}
void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
{
logAi->trace("Checking other buildings");
std::vector<std::vector<BuildingID>> otherBuildings = {
2023-07-27 14:58:49 +02:00
{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL},
{BuildingID::MAGES_GUILD_3, BuildingID::MAGES_GUILD_5}
};
if(developmentInfo.existingDwellings.size() >= 2 && ai->cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
{
2023-07-27 14:58:49 +02:00
otherBuildings.push_back({BuildingID::HORDE_1});
otherBuildings.push_back({BuildingID::HORDE_2});
}
otherBuildings.push_back({ BuildingID::CITADEL, BuildingID::CASTLE });
otherBuildings.push_back({ BuildingID::RESOURCE_SILO });
otherBuildings.push_back({ BuildingID::SPECIAL_1 });
otherBuildings.push_back({ BuildingID::SPECIAL_2 });
otherBuildings.push_back({ BuildingID::SPECIAL_3 });
otherBuildings.push_back({ BuildingID::SPECIAL_4 });
otherBuildings.push_back({ BuildingID::MARKETPLACE });
for(auto & buildingSet : otherBuildings)
{
for(auto & buildingID : buildingSet)
{
if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID))
{
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
break;
}
}
}
}
int32_t convertToGold(const TResources & res)
{
2023-04-05 02:26:29 +02:00
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
{
2021-05-16 14:39:38 +02:00
auto resourcesAvailable = ai->getFreeResources();
auto result = withoutGold(armyCost) + requiredResources - resourcesAvailable;
result.positive();
return result;
}
TResources BuildAnalyzer::getTotalResourcesRequired() const
{
2021-05-16 14:39:38 +02:00
auto resourcesAvailable = ai->getFreeResources();
auto result = totalDevelopmentCost + withoutGold(armyCost) - resourcesAvailable;
result.positive();
return result;
}
2024-04-16 23:10:15 +02:00
bool BuildAnalyzer::isGoldPressureHigh() const
2024-02-25 12:39:19 +02:00
{
2024-04-16 23:10:15 +02:00
return goldPressure > ai->settings->getMaxGoldPressure();
2024-02-25 12:39:19 +02:00
}
void BuildAnalyzer::update()
{
logAi->trace("Start analysing build");
BuildingInfo bi;
reset();
auto towns = ai->cb->getTownsInfo();
float economyDevelopmentCost = 0;
for(const CGTownInstance* town : towns)
{
logAi->trace("Checking town %s", town->getNameTranslated());
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;
for(auto bi : developmentInfo.toBuild)
{
logAi->trace("Building preferences %s", bi.toString());
}
}
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);
2024-04-16 23:10:49 +02:00
logAi->trace("Gold pressure: %f", goldPressure);
}
void BuildAnalyzer::reset()
{
requiredResources = TResources();
totalDevelopmentCost = TResources();
armyCost = TResources();
developmentInfos.clear();
}
BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
2021-05-16 13:57:29 +02:00
const CGTownInstance * town,
BuildingID toBuild,
bool excludeDwellingDependencies) const
{
BuildingID building = toBuild;
auto townInfo = town->getTown();
const CBuilding * buildPtr = townInfo->buildings.at(building);
const CCreature * creature = nullptr;
CreatureID baseCreatureID;
2023-07-27 14:58:49 +02:00
int creatureLevel = -1;
int creatureUpgrade = 0;
if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
{
2024-08-06 23:28:45 +02:00
creatureLevel = BuildingID::getLevelFromDwelling(toBuild);
creatureUpgrade = BuildingID::getUpgradedFromDwelling(toBuild);
2023-07-27 14:58:49 +02:00
}
else if(toBuild == BuildingID::HORDE_1 || toBuild == BuildingID::HORDE_1_UPGR)
{
creatureLevel = townInfo->hordeLvl.at(0);
}
else if(toBuild == BuildingID::HORDE_2 || toBuild == BuildingID::HORDE_2_UPGR)
{
creatureLevel = townInfo->hordeLvl.at(1);
}
if(creatureLevel >= 0)
{
auto creatures = townInfo->creatures.at(creatureLevel);
auto creatureID = creatures.size() > creatureUpgrade
? creatures.at(creatureUpgrade)
: creatures.front();
baseCreatureID = creatures.front();
creature = creatureID.toCreature();
}
2021-05-16 13:57:29 +02:00
auto info = BuildingInfo(buildPtr, creature, baseCreatureID, town, ai);
2021-05-16 14:39:38 +02:00
logAi->trace("checking %s", info.name);
logAi->trace("buildInfo %s", info.toString());
int highestFort = 0;
for (auto twn : ai->cb->getTownsInfo())
{
highestFort = std::max(highestFort, (int)twn->fortLevel());
}
if(!town->hasBuilt(building))
{
auto canBuild = ai->cb->canBuildStructure(town, building);
if(canBuild == EBuildingState::ALLOWED)
{
info.canBuild = true;
}
else if(canBuild == EBuildingState::NO_RESOURCES)
{
logAi->trace("cant build. Not enough resources. Need %s", info.buildCost.toString());
info.notEnoughRes = true;
}
else if(canBuild == EBuildingState::PREREQUIRES)
{
auto buildExpression = town->genBuildingRequirements(building, false);
auto missingBuildings = buildExpression.getFulfillmentCandidates([&](const BuildingID & id) -> bool
{
return town->hasBuilt(id);
});
auto otherDwelling = [](const BuildingID & id) -> bool
{
return BuildingID::DWELL_FIRST <= id && id <= BuildingID::DWELL_UP_LAST;
};
if(vstd::contains_if(missingBuildings, otherDwelling))
{
logAi->trace("cant build. Need other dwelling");
}
else if(missingBuildings[0] != toBuild)
{
logAi->trace("cant build. Need %d", missingBuildings[0].num);
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
prerequisite.buildCostWithPrerequisites += info.buildCost;
prerequisite.creatureCost = info.creatureCost;
prerequisite.creatureGrows = info.creatureGrows;
prerequisite.creatureLevel = info.creatureLevel;
prerequisite.creatureID = info.creatureID;
prerequisite.baseCreatureID = info.baseCreatureID;
prerequisite.prerequisitesCount++;
2021-05-16 14:39:38 +02:00
prerequisite.armyCost = info.armyCost;
bool haveSameOrBetterFort = false;
if (prerequisite.id == BuildingID::FORT && highestFort >= CGTownInstance::EFortLevel::FORT)
haveSameOrBetterFort = true;
if (prerequisite.id == BuildingID::CITADEL && highestFort >= CGTownInstance::EFortLevel::CITADEL)
haveSameOrBetterFort = true;
if (prerequisite.id == BuildingID::CASTLE && highestFort >= CGTownInstance::EFortLevel::CASTLE)
haveSameOrBetterFort = true;
if(!haveSameOrBetterFort)
prerequisite.dailyIncome = info.dailyIncome;
return prerequisite;
}
else
{
logAi->trace("Cant build. The building requires itself as prerequisite");
return info;
}
}
}
else
{
logAi->trace("exists");
info.exists = true;
}
return info;
}
void BuildAnalyzer::updateDailyIncome()
{
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();
}
}
2021-05-16 13:45:45 +02:00
bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
{
for(auto tdi : developmentInfos)
{
if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
2021-05-16 13:45:45 +02:00
return true;
}
return false;
}
void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling)
{
existingDwellings.push_back(existingDwelling);
2021-05-16 14:39:38 +02:00
armyCost += existingDwelling.armyCost;
2021-05-16 13:57:29 +02:00
armyStrength += existingDwelling.armyStrength;
}
void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
{
townDevelopmentCost += nextToBuild.buildCostWithPrerequisites;
townDevelopmentCost += withoutGold(nextToBuild.armyCost);
if(nextToBuild.canBuild)
{
hasSomethingToBuild = true;
2021-05-16 14:39:38 +02:00
toBuild.push_back(nextToBuild);
}
else if(nextToBuild.notEnoughRes)
{
requiredResources += nextToBuild.buildCost;
hasSomethingToBuild = true;
2021-05-16 14:39:38 +02:00
toBuild.push_back(nextToBuild);
}
}
BuildingInfo::BuildingInfo()
{
id = BuildingID::NONE;
creatureGrows = 0;
creatureID = CreatureID::NONE;
buildCost = 0;
buildCostWithPrerequisites = 0;
prerequisitesCount = 0;
name.clear();
2021-05-16 13:57:29 +02:00
armyStrength = 0;
}
2021-05-16 13:57:29 +02:00
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;
2021-05-16 13:57:29 +02:00
exists = town->hasBuilt(id);
prerequisitesCount = 1;
name = building->getNameTranslated();
if(creature)
{
2023-04-05 02:26:29 +02:00
creatureGrows = creature->getGrowth();
creatureID = creature->getId();
creatureCost = creature->getFullRecruitCost();
creatureLevel = creature->getLevel();
baseCreatureID = baseCreature;
2021-05-16 13:57:29 +02:00
if(exists)
{
2021-05-16 14:39:38 +02:00
creatureGrows = town->creatureGrowth(creatureLevel - 1);
2021-05-16 13:57:29 +02:00
}
else
{
2023-07-27 14:58:49 +02:00
if(BuildingID::DWELL_FIRST <= id && id <= BuildingID::DWELL_UP_LAST)
{
creatureGrows = creature->getGrowth();
2021-05-16 13:57:29 +02:00
2023-07-27 14:58:49 +02:00
if(town->hasBuilt(BuildingID::CASTLE))
creatureGrows *= 2;
else if(town->hasBuilt(BuildingID::CITADEL))
creatureGrows += creatureGrows / 2;
}
else
{
creatureGrows = creature->getHorde();
}
2021-05-16 13:57:29 +02:00
}
armyStrength = ai->armyManager->evaluateStackPower(creature, creatureGrows);
armyCost = creatureCost * creatureGrows;
}
else
{
creatureGrows = 0;
creatureID = CreatureID::NONE;
baseCreatureID = CreatureID::NONE;
creatureCost = TResources();
2021-05-16 13:57:29 +02:00
armyCost = TResources();
creatureLevel = 0;
2021-05-16 13:57:29 +02:00
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();
2022-09-22 10:49:55 +02:00
}
2022-09-26 20:01:07 +02:00
}