2021-05-16 13:14:17 +02:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "StdInc.h"
|
|
|
|
#include "BuildAnalyzer.h"
|
|
|
|
#include "lib/mapping/CMap.h" //for victory conditions
|
|
|
|
|
|
|
|
extern boost::thread_specific_ptr<CCallback> cb;
|
|
|
|
extern boost::thread_specific_ptr<VCAI> ai;
|
|
|
|
|
2021-05-16 13:15:03 +02:00
|
|
|
void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
|
|
|
{
|
|
|
|
auto townInfo = developmentInfo.town->town;
|
|
|
|
auto creatures = townInfo->creatures;
|
|
|
|
auto buildings = townInfo->getAllBuildings();
|
|
|
|
|
|
|
|
std::map<BuildingID, BuildingID> parentMap;
|
|
|
|
|
|
|
|
for(auto &pair : townInfo->buildings)
|
|
|
|
{
|
|
|
|
if(pair.second->upgrade != -1)
|
|
|
|
{
|
|
|
|
parentMap[pair.second->upgrade] = pair.first;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BuildingID prefixes[] = {BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_FIRST};
|
|
|
|
|
|
|
|
for(int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++)
|
|
|
|
{
|
|
|
|
logAi->trace("Checking dwelling level %d", level);
|
|
|
|
BuildingInfo nextToBuild = BuildingInfo();
|
|
|
|
|
|
|
|
for(BuildingID prefix : prefixes)
|
|
|
|
{
|
|
|
|
BuildingID building = BuildingID(prefix + level);
|
|
|
|
|
|
|
|
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 = {
|
|
|
|
{BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL}
|
|
|
|
};
|
|
|
|
|
|
|
|
if(developmentInfo.existingDwellings.size() >= 2 && cb->getDate(Date::DAY_OF_WEEK) > boost::date_time::Friday)
|
|
|
|
{
|
|
|
|
otherBuildings.push_back({BuildingID::CITADEL, BuildingID::CASTLE});
|
|
|
|
}
|
|
|
|
|
|
|
|
for(auto & buildingSet : otherBuildings)
|
|
|
|
{
|
|
|
|
for(auto & buildingID : buildingSet)
|
|
|
|
{
|
|
|
|
if(!developmentInfo.town->hasBuilt(buildingID))
|
|
|
|
{
|
|
|
|
developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t convertToGold(const TResources & res)
|
|
|
|
{
|
|
|
|
return res[Res::GOLD]
|
|
|
|
+ 75 * (res[Res::WOOD] + res[Res::ORE])
|
|
|
|
+ 125 * (res[Res::GEMS] + res[Res::CRYSTAL] + res[Res::MERCURY] + res[Res::SULFUR]);
|
|
|
|
}
|
|
|
|
|
|
|
|
TResources BuildAnalyzer::getResourcesRequiredNow() const
|
|
|
|
{
|
|
|
|
auto resourcesAvailable = cb->getResourceAmount();
|
|
|
|
auto result = requiredResources - resourcesAvailable;
|
|
|
|
|
|
|
|
result.positive();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TResources BuildAnalyzer::getTotalResourcesRequired() const
|
|
|
|
{
|
|
|
|
auto resourcesAvailable = cb->getResourceAmount();
|
|
|
|
auto result = totalDevelopmentCost - resourcesAvailable;
|
|
|
|
|
|
|
|
result.positive();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BuildAnalyzer::update()
|
|
|
|
{
|
|
|
|
logAi->trace("Start analysing build");
|
|
|
|
|
|
|
|
BuildingInfo bi;
|
|
|
|
|
|
|
|
reset();
|
|
|
|
|
|
|
|
auto towns = cb->getTownsInfo();
|
|
|
|
|
|
|
|
for(const CGTownInstance* town : towns)
|
|
|
|
{
|
|
|
|
logAi->trace("Checking town %s", town->name);
|
|
|
|
|
|
|
|
auto townInfo = town->town;
|
|
|
|
|
|
|
|
developmentInfos.push_back(TownDevelopmentInfo(town));
|
|
|
|
TownDevelopmentInfo & developmentInfo = developmentInfos.back();
|
|
|
|
|
|
|
|
updateTownDwellings(developmentInfo);
|
|
|
|
updateOtherBuildings(developmentInfo);
|
|
|
|
|
|
|
|
requiredResources += developmentInfo.requiredResources;
|
|
|
|
totalDevelopmentCost += developmentInfo.townDevelopmentCost;
|
|
|
|
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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-16 13:14:17 +02:00
|
|
|
void BuildAnalyzer::reset()
|
|
|
|
{
|
2021-05-16 13:15:03 +02:00
|
|
|
requiredResources = TResources();
|
|
|
|
totalDevelopmentCost = TResources();
|
|
|
|
armyCost = TResources();
|
|
|
|
developmentInfos.clear();
|
2021-05-16 13:14:17 +02:00
|
|
|
}
|
2021-05-16 13:15:03 +02:00
|
|
|
|
|
|
|
BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
|
|
|
const CGTownInstance* town,
|
|
|
|
BuildingID toBuild,
|
|
|
|
bool excludeDwellingDependencies) const
|
|
|
|
{
|
|
|
|
BuildingID building = toBuild;
|
|
|
|
auto townInfo = town->town;
|
|
|
|
|
|
|
|
const CBuilding * buildPtr = townInfo->buildings.at(building);
|
|
|
|
const CCreature * creature = nullptr;
|
|
|
|
CreatureID baseCreatureID;
|
|
|
|
|
|
|
|
if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
|
|
|
|
{
|
|
|
|
int level = toBuild - BuildingID::DWELL_FIRST;
|
|
|
|
auto creatures = townInfo->creatures.at(level % GameConstants::CREATURES_PER_TOWN);
|
|
|
|
auto creatureID = creatures.at(level / GameConstants::CREATURES_PER_TOWN);
|
|
|
|
|
|
|
|
baseCreatureID = creatures.front();
|
|
|
|
creature = creatureID.toCreature();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto info = BuildingInfo(buildPtr, creature, baseCreatureID);
|
|
|
|
|
|
|
|
logAi->trace("checking %s", buildPtr->Name());
|
|
|
|
logAi->trace("buildInfo %s", info.toString());
|
|
|
|
|
|
|
|
buildPtr = nullptr;
|
|
|
|
|
|
|
|
if(!town->hasBuilt(building))
|
|
|
|
{
|
|
|
|
auto canBuild = 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
|
|
|
|
{
|
|
|
|
buildPtr = townInfo->buildings.at(building);
|
|
|
|
|
|
|
|
logAi->trace("cant build. Need %d", missingBuildings[0].num);
|
|
|
|
|
|
|
|
BuildingInfo prerequisite = getBuildingOrPrerequisite(town, missingBuildings[0], excludeDwellingDependencies);
|
|
|
|
|
|
|
|
prerequisite.buildCostWithPrerequisits += info.buildCost;
|
|
|
|
prerequisite.creatureCost = info.creatureCost;
|
|
|
|
prerequisite.creatureGrows = info.creatureGrows;
|
|
|
|
prerequisite.creatureLevel = info.creatureLevel;
|
|
|
|
prerequisite.creatureID = info.creatureID;
|
|
|
|
prerequisite.baseCreatureID = info.baseCreatureID;
|
|
|
|
prerequisite.prerequisitesCount++;
|
|
|
|
|
|
|
|
return prerequisite;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
logAi->trace("exists");
|
|
|
|
info.exists = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
TResources BuildAnalyzer::getDailyIncome() const
|
|
|
|
{
|
|
|
|
auto objects = cb->getMyObjects();
|
|
|
|
auto towns = cb->getTownsInfo();
|
|
|
|
TResources dailyIncome = TResources();
|
|
|
|
|
|
|
|
for(const CGObjectInstance* obj : objects)
|
|
|
|
{
|
|
|
|
const CGMine* mine = dynamic_cast<const CGMine*>(obj);
|
|
|
|
|
|
|
|
if(mine)
|
|
|
|
{
|
|
|
|
dailyIncome[mine->producedResource] += mine->producedQuantity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for(const CGTownInstance* town : towns)
|
|
|
|
{
|
|
|
|
dailyIncome += town->dailyIncome();
|
|
|
|
}
|
|
|
|
|
|
|
|
return dailyIncome;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TownDevelopmentInfo::addExistingDwelling(const BuildingInfo & existingDwelling)
|
|
|
|
{
|
|
|
|
existingDwellings.push_back(existingDwelling);
|
|
|
|
|
|
|
|
armyCost += existingDwelling.creatureCost * existingDwelling.creatureGrows;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TownDevelopmentInfo::addBuildingToBuild(const BuildingInfo & nextToBuild)
|
|
|
|
{
|
|
|
|
townDevelopmentCost += nextToBuild.buildCostWithPrerequisits;
|
|
|
|
|
|
|
|
if(nextToBuild.canBuild)
|
|
|
|
{
|
|
|
|
toBuild.push_back(nextToBuild);
|
|
|
|
hasSomethingToBuild = true;
|
|
|
|
}
|
|
|
|
else if(nextToBuild.notEnoughRes)
|
|
|
|
{
|
|
|
|
requiredResources += nextToBuild.buildCost;
|
|
|
|
hasSomethingToBuild = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BuildingInfo::BuildingInfo()
|
|
|
|
{
|
|
|
|
id = BuildingID::NONE;
|
|
|
|
creatureGrows = 0;
|
|
|
|
creatureID = CreatureID::NONE;
|
|
|
|
buildCost = 0;
|
|
|
|
buildCostWithPrerequisits = 0;
|
|
|
|
prerequisitesCount = 0;
|
|
|
|
name = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
BuildingInfo::BuildingInfo(const CBuilding * building, const CCreature * creature, CreatureID baseCreature)
|
|
|
|
{
|
|
|
|
id = building->bid;
|
|
|
|
buildCost = building->resources;
|
|
|
|
buildCostWithPrerequisits = building->resources;
|
|
|
|
dailyIncome = building->produce;
|
|
|
|
exists = false;;
|
|
|
|
prerequisitesCount = 1;
|
|
|
|
name = building->Name();
|
|
|
|
|
|
|
|
if(creature)
|
|
|
|
{
|
|
|
|
creatureGrows = creature->growth;
|
|
|
|
creatureID = creature->idNumber;
|
|
|
|
creatureCost = creature->cost;
|
|
|
|
creatureLevel = creature->level;
|
|
|
|
baseCreatureID = baseCreature;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
creatureGrows = 0;
|
|
|
|
creatureID = CreatureID::NONE;
|
|
|
|
baseCreatureID = CreatureID::NONE;
|
|
|
|
creatureCost = TResources();
|
|
|
|
creatureLevel = 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();
|
|
|
|
}
|