mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
Nullkiller AI: stabilization of build and prioritization fixes
This commit is contained in:
parent
de2361650b
commit
400967904b
@ -47,6 +47,12 @@ void AIhelper::setAI(VCAI * AI)
|
||||
heroManager->setAI(AI);
|
||||
}
|
||||
|
||||
void AIhelper::update()
|
||||
{
|
||||
armyManager->update();
|
||||
heroManager->update();
|
||||
}
|
||||
|
||||
bool AIhelper::getBuildingOptions(const CGTownInstance * t)
|
||||
{
|
||||
return buildingManager->getBuildingOptions(t);
|
||||
@ -162,6 +168,16 @@ void AIhelper::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
|
||||
pathfindingManager->updatePaths(heroes, useHeroChain);
|
||||
}
|
||||
|
||||
uint64_t AIhelper::evaluateStackPower(const CCreature * creature, int count) const
|
||||
{
|
||||
return armyManager->evaluateStackPower(creature, count);
|
||||
}
|
||||
|
||||
SlotInfo AIhelper::getTotalCreaturesAvailable(CreatureID creatureID) const
|
||||
{
|
||||
return armyManager->getTotalCreaturesAvailable(creatureID);
|
||||
}
|
||||
|
||||
bool AIhelper::canGetArmy(const CArmedInstance * army, const CArmedInstance * source) const
|
||||
{
|
||||
return armyManager->canGetArmy(army, source);
|
||||
@ -212,11 +228,6 @@ HeroRole AIhelper::getHeroRole(const HeroPtr & hero) const
|
||||
return heroManager->getHeroRole(hero);
|
||||
}
|
||||
|
||||
void AIhelper::updateHeroRoles()
|
||||
{
|
||||
heroManager->updateHeroRoles();
|
||||
}
|
||||
|
||||
float AIhelper::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const
|
||||
{
|
||||
return heroManager->evaluateSecSkill(skill, hero);
|
||||
|
@ -80,14 +80,17 @@ public:
|
||||
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
|
||||
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
|
||||
uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
|
||||
SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
|
||||
|
||||
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
|
||||
HeroRole getHeroRole(const HeroPtr & hero) const override;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
|
||||
void updateHeroRoles() override;
|
||||
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
|
||||
float evaluateHero(const CGHeroInstance * hero) const override;
|
||||
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
bool notifyGoalCompleted(Goals::TSubgoal goal) override;
|
||||
|
||||
|
@ -14,6 +14,336 @@
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
void BuildAnalyzer::reset()
|
||||
{
|
||||
requiredResources = TResources();
|
||||
totalDevelopmentCost = TResources();
|
||||
armyCost = TResources();
|
||||
developmentInfos.clear();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
@ -12,11 +12,82 @@
|
||||
#include "../VCAI.h"
|
||||
#include "../../../lib/ResourceSet.h"
|
||||
|
||||
|
||||
class BuildingInfo
|
||||
{
|
||||
public:
|
||||
BuildingID id;
|
||||
TResources buildCost;
|
||||
TResources buildCostWithPrerequisits;
|
||||
int creatureGrows;
|
||||
uint8_t creatureLevel;
|
||||
TResources creatureCost;
|
||||
CreatureID creatureID;
|
||||
CreatureID baseCreatureID;
|
||||
TResources dailyIncome;
|
||||
uint8_t prerequisitesCount;
|
||||
std::string name;
|
||||
bool exists = false;
|
||||
bool canBuild = false;
|
||||
bool notEnoughRes = false;
|
||||
|
||||
BuildingInfo();
|
||||
|
||||
BuildingInfo(const CBuilding* building, const CCreature* creature, CreatureID baseCreatureID);
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
class TownDevelopmentInfo
|
||||
{
|
||||
public:
|
||||
const CGTownInstance* town;
|
||||
std::vector<BuildingInfo> toBuild;
|
||||
std::vector<BuildingInfo> existingDwellings;
|
||||
TResources townDevelopmentCost;
|
||||
TResources requiredResources;
|
||||
TResources armyCost;
|
||||
int armyScore;
|
||||
int economicsScore;
|
||||
HeroRole townRole;
|
||||
bool hasSomethingToBuild;
|
||||
|
||||
TownDevelopmentInfo(const CGTownInstance* town)
|
||||
:town(town), armyScore(0), economicsScore(0), toBuild(), townDevelopmentCost(), requiredResources(), townRole(HeroRole::SCOUT), hasSomethingToBuild(false)
|
||||
{
|
||||
}
|
||||
|
||||
TownDevelopmentInfo() : TownDevelopmentInfo(nullptr) {}
|
||||
|
||||
void addBuildingToBuild(const BuildingInfo & building);
|
||||
void addExistingDwelling(const BuildingInfo & existingDwelling);
|
||||
};
|
||||
|
||||
class BuildAnalyzer
|
||||
{
|
||||
private:
|
||||
TResources requiredResources;
|
||||
TResources totalDevelopmentCost;
|
||||
std::vector<TownDevelopmentInfo> developmentInfos;
|
||||
TResources armyCost;
|
||||
|
||||
public:
|
||||
void update();
|
||||
|
||||
TResources getResourcesRequiredNow() const;
|
||||
TResources getTotalResourcesRequired() const;
|
||||
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
|
||||
|
||||
TResources getDailyIncome() const;
|
||||
|
||||
private:
|
||||
BuildingInfo getBuildingOrPrerequisite(
|
||||
const CGTownInstance* town,
|
||||
BuildingID toBuild,
|
||||
bool excludeDwellingDependencies = true) const;
|
||||
|
||||
|
||||
void updateTownDwellings(TownDevelopmentInfo & developmentInfo);
|
||||
void updateOtherBuildings(TownDevelopmentInfo & developmentInfo);
|
||||
void reset();
|
||||
};
|
||||
|
@ -49,6 +49,9 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
{
|
||||
for(AIPath & path : ai->ah->getPathsToTile(pos))
|
||||
{
|
||||
if(path.getFirstBlockedAction())
|
||||
continue;
|
||||
|
||||
auto tileDanger = path.getHeroStrength();
|
||||
auto turn = path.turn();
|
||||
auto & node = hitMap[pos.x][pos.y][pos.z];
|
||||
|
@ -168,3 +168,43 @@ ui64 ArmyManager::howManyReinforcementsCanGet(const CCreatureSet * target, const
|
||||
|
||||
return newArmy > oldArmy ? newArmy - oldArmy : 0;
|
||||
}
|
||||
|
||||
uint64_t ArmyManager::evaluateStackPower(const CCreature * creature, int count) const
|
||||
{
|
||||
return creature->AIValue * count;
|
||||
}
|
||||
|
||||
SlotInfo ArmyManager::getTotalCreaturesAvailable(CreatureID creatureID) const
|
||||
{
|
||||
auto creatureInfo = totalArmy.find(creatureID);
|
||||
|
||||
return creatureInfo == totalArmy.end() ? SlotInfo() : creatureInfo->second;
|
||||
}
|
||||
|
||||
void ArmyManager::update()
|
||||
{
|
||||
logAi->trace("Start analysing army");
|
||||
|
||||
std::vector<const CCreatureSet *> total;
|
||||
auto heroes = cb->getHeroesInfo();
|
||||
auto towns = cb->getTownsInfo();
|
||||
|
||||
std::copy(heroes.begin(), heroes.end(), std::back_inserter(total));
|
||||
std::copy(towns.begin(), towns.end(), std::back_inserter(total));
|
||||
|
||||
totalArmy.clear();
|
||||
|
||||
for(auto army : total)
|
||||
{
|
||||
for(auto slot : army->Slots())
|
||||
{
|
||||
totalArmy[slot.second->getCreatureID()].count += slot.second->count;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto army : totalArmy)
|
||||
{
|
||||
army.second.creature = army.first.toCreature();
|
||||
army.second.power = evaluateStackPower(army.second.creature, army.second.count);
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ class DLL_EXPORT IArmyManager //: public: IAbstractManager
|
||||
public:
|
||||
virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
|
||||
virtual void setAI(VCAI * AI) = 0;
|
||||
virtual void update() = 0;
|
||||
virtual bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const = 0;
|
||||
virtual ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const = 0;
|
||||
virtual ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const = 0;
|
||||
@ -37,6 +38,8 @@ public:
|
||||
virtual std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const = 0;
|
||||
virtual std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0;
|
||||
virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const = 0;
|
||||
virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
|
||||
virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
|
||||
};
|
||||
|
||||
class DLL_EXPORT ArmyManager : public IArmyManager
|
||||
@ -44,10 +47,12 @@ class DLL_EXPORT ArmyManager : public IArmyManager
|
||||
private:
|
||||
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
|
||||
VCAI * ai;
|
||||
std::map<CreatureID, SlotInfo> totalArmy;
|
||||
|
||||
public:
|
||||
void init(CPlayerSpecificInfoCallback * CB) override;
|
||||
void setAI(VCAI * AI) override;
|
||||
void update() override;
|
||||
|
||||
bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override;
|
||||
ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override;
|
||||
@ -56,4 +61,6 @@ public:
|
||||
std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
|
||||
std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
|
||||
std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling) const override;
|
||||
uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
|
||||
SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
|
||||
};
|
||||
|
@ -33,6 +33,32 @@ Goals::TGoalVec BuildingBehavior::getTasks()
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
|
||||
TResources resourcesRequired = ai->nullkiller->buildAnalyzer->getResourcesRequiredNow();
|
||||
TResources totalDevelopmentCost = ai->nullkiller->buildAnalyzer->getTotalResourcesRequired();
|
||||
TResources availableResources = cb->getResourceAmount();
|
||||
TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome();
|
||||
|
||||
logAi->trace("Resources amount: %s", availableResources.toString());
|
||||
|
||||
resourcesRequired -= availableResources;
|
||||
resourcesRequired.positive();
|
||||
|
||||
logAi->trace("daily income: %s", dailyIncome.toString());
|
||||
logAi->trace("resources required to develop towns now: %s, total: %s",
|
||||
resourcesRequired.toString(),
|
||||
totalDevelopmentCost.toString());
|
||||
|
||||
auto & developmentInfos = ai->nullkiller->buildAnalyzer->getDevelopmentInfo();
|
||||
|
||||
for(auto & developmentInfo : developmentInfos)
|
||||
{
|
||||
auto town = developmentInfo.town;
|
||||
|
||||
for(auto & buildingInfo : developmentInfo.toBuild)
|
||||
{
|
||||
tasks.push_back(sptr(BuildThis(buildingInfo, developmentInfo)));
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
@ -28,17 +28,6 @@ std::string CaptureObjectsBehavior::toString() const
|
||||
return "Capture objects";
|
||||
}
|
||||
|
||||
std::shared_ptr<const ISpecialAction> getFirstBlockedAction(const AIPath & path)
|
||||
{
|
||||
for(auto node : path.nodes)
|
||||
{
|
||||
if(node.specialAction && !node.specialAction->canAct(node.targetHero))
|
||||
return node.specialAction;
|
||||
}
|
||||
|
||||
return std::shared_ptr<const ISpecialAction>();
|
||||
}
|
||||
|
||||
Goals::TGoalVec CaptureObjectsBehavior::getTasks()
|
||||
{
|
||||
Goals::TGoalVec tasks;
|
||||
@ -76,7 +65,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
|
||||
logAi->trace("Path found %s", path.toString());
|
||||
#endif
|
||||
|
||||
if(getFirstBlockedAction(path))
|
||||
if(path.getFirstBlockedAction())
|
||||
{
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
// TODO: decomposition?
|
||||
@ -88,7 +77,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
|
||||
if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path))
|
||||
{
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy");
|
||||
logAi->trace("Ignore path. Target hero can be killed by enemy. Our power %d", path.heroArmy->getArmyStrength());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
@ -112,7 +101,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
|
||||
hero->name,
|
||||
path.getHeroStrength(),
|
||||
danger,
|
||||
path.armyLoss);
|
||||
path.getTotalArmyLoss());
|
||||
#endif
|
||||
|
||||
if(isSafe)
|
||||
|
@ -40,7 +40,7 @@ Goals::TGoalVec RecruitHeroBehavior::getTasks()
|
||||
if(cb->getHeroesInfo().size() < cb->getTownsInfo().size() + 1
|
||||
|| cb->getResourceAmount(Res::GOLD) > 10000)
|
||||
{
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town)));
|
||||
tasks.push_back(Goals::sptr(Goals::RecruitHero().settown(town).setpriority(3)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "../Behaviors/BuyArmyBehavior.h"
|
||||
#include "../Behaviors/StartupBehavior.h"
|
||||
#include "../Behaviors/DefenceBehavior.h"
|
||||
#include "../Behaviors/BuildingBehavior.h"
|
||||
#include "../Goals/Invalid.h"
|
||||
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
@ -25,6 +26,7 @@ Nullkiller::Nullkiller()
|
||||
{
|
||||
priorityEvaluator.reset(new PriorityEvaluator());
|
||||
dangerHitMap.reset(new DangerHitMapAnalyzer());
|
||||
buildAnalyzer.reset(new BuildAnalyzer());
|
||||
}
|
||||
|
||||
Goals::TSubgoal Nullkiller::choseBestTask(Goals::TGoalVec & tasks) const
|
||||
@ -78,14 +80,17 @@ void Nullkiller::updateAiState()
|
||||
// TODO: move to hero manager
|
||||
auto activeHeroes = ai->getMyHeroes();
|
||||
|
||||
vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{
|
||||
vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool
|
||||
{
|
||||
auto lockedReason = getHeroLockedReason(hero.h);
|
||||
|
||||
return lockedReason == HeroLockedReason::DEFENCE || lockedReason == HeroLockedReason::STARTUP;
|
||||
});
|
||||
|
||||
ai->ah->updatePaths(activeHeroes, true);
|
||||
ai->ah->updateHeroRoles();
|
||||
ai->ah->update();
|
||||
|
||||
buildAnalyzer->update();
|
||||
}
|
||||
|
||||
bool Nullkiller::arePathHeroesLocked(const AIPath & path) const
|
||||
@ -111,7 +116,8 @@ void Nullkiller::makeTurn()
|
||||
choseBestTask(std::make_shared<BuyArmyBehavior>()),
|
||||
choseBestTask(std::make_shared<CaptureObjectsBehavior>()),
|
||||
choseBestTask(std::make_shared<RecruitHeroBehavior>()),
|
||||
choseBestTask(std::make_shared<DefenceBehavior>())
|
||||
choseBestTask(std::make_shared<DefenceBehavior>()),
|
||||
choseBestTask(std::make_shared<BuildingBehavior>())
|
||||
};
|
||||
|
||||
if(cb->getDate(Date::DAY) == 1)
|
||||
@ -140,12 +146,12 @@ void Nullkiller::makeTurn()
|
||||
{
|
||||
logAi->trace(bestTask->completeMessage());
|
||||
}
|
||||
catch(std::exception & e)
|
||||
/*catch(std::exception & e)
|
||||
{
|
||||
logAi->debug("Failed to realize subgoal of type %s, I will stop.", bestTask->name());
|
||||
logAi->debug("The error message was: %s", e.what());
|
||||
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ private:
|
||||
|
||||
public:
|
||||
std::unique_ptr<DangerHitMapAnalyzer> dangerHitMap;
|
||||
std::unique_ptr<BuildAnalyzer> buildAnalyzer;
|
||||
|
||||
Nullkiller();
|
||||
void makeTurn();
|
||||
|
@ -35,11 +35,6 @@ class CGTownInstance;
|
||||
extern boost::thread_specific_ptr<CCallback> cb;
|
||||
extern boost::thread_specific_ptr<VCAI> ai;
|
||||
|
||||
PriorityEvaluator::PriorityEvaluator()
|
||||
{
|
||||
initVisitTile();
|
||||
}
|
||||
|
||||
PriorityEvaluator::~PriorityEvaluator()
|
||||
{
|
||||
delete engine;
|
||||
@ -112,7 +107,8 @@ uint64_t getDwellingScore(const CGObjectInstance * target, bool checkGold)
|
||||
if(creLevel.first && creLevel.second.size())
|
||||
{
|
||||
auto creature = creLevel.second.back().toCreature();
|
||||
if(checkGold && !cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
|
||||
auto creaturesAreFree = creature->level == 1;
|
||||
if(!creaturesAreFree && checkGold && !cb->getResourceAmount().canAfford(creature->cost * creLevel.first))
|
||||
continue;
|
||||
|
||||
score += creature->AIValue * creLevel.first;
|
||||
@ -179,7 +175,7 @@ uint64_t getArmyReward(const CGObjectInstance * target, const CGHeroInstance * h
|
||||
case Obj::SHIPWRECK:
|
||||
case Obj::SHIPWRECK_SURVIVOR:
|
||||
case Obj::WARRIORS_TOMB:
|
||||
return 1500;
|
||||
return 1000;
|
||||
case Obj::ARTIFACT:
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact);
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
@ -208,6 +204,34 @@ float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy)
|
||||
return objectValue / 2.0f + enemy->level / 15.0f;
|
||||
}
|
||||
|
||||
float getResourceRequirementStrength(int resType)
|
||||
{
|
||||
TResources requiredResources = ai->nullkiller->buildAnalyzer->getResourcesRequiredNow();
|
||||
TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome();
|
||||
|
||||
if(requiredResources[resType] == 0)
|
||||
return 0;
|
||||
|
||||
if(dailyIncome[resType] == 0)
|
||||
return 1;
|
||||
|
||||
return (float)requiredResources[resType] / dailyIncome[resType] / 3;
|
||||
}
|
||||
|
||||
float getTotalResourceRequirementStrength(int resType)
|
||||
{
|
||||
TResources requiredResources = ai->nullkiller->buildAnalyzer->getTotalResourcesRequired();
|
||||
TResources dailyIncome = ai->nullkiller->buildAnalyzer->getDailyIncome();
|
||||
|
||||
if(requiredResources[resType] == 0)
|
||||
return 0;
|
||||
|
||||
if(dailyIncome[resType] == 0)
|
||||
return requiredResources[resType] / 30;
|
||||
|
||||
return (float)requiredResources[resType] / dailyIncome[resType] / 30;
|
||||
}
|
||||
|
||||
float getStrategicalValue(const CGObjectInstance * target)
|
||||
{
|
||||
if(!target)
|
||||
@ -215,6 +239,12 @@ float getStrategicalValue(const CGObjectInstance * target)
|
||||
|
||||
switch(target->ID)
|
||||
{
|
||||
case Obj::MINE:
|
||||
return target->subID == Res::GOLD ? 0.8f : 0.05f + 0.3f * getTotalResourceRequirementStrength(target->subID) + 0.5f * getResourceRequirementStrength(target->subID);
|
||||
|
||||
case Obj::RESOURCE:
|
||||
return target->subID == Res::GOLD ? 0 : 0.5f * getResourceRequirementStrength(target->subID);
|
||||
|
||||
case Obj::TOWN:
|
||||
return target->tempOwner == PlayerColor::NEUTRAL ? 0.5 : 1;
|
||||
|
||||
@ -261,7 +291,10 @@ float getSkillReward(const CGObjectInstance * target, const CGHeroInstance * her
|
||||
case Obj::MERCENARY_CAMP:
|
||||
case Obj::SHRINE_OF_MAGIC_GESTURE:
|
||||
case Obj::SHRINE_OF_MAGIC_INCANTATION:
|
||||
case Obj::TREE_OF_KNOWLEDGE:
|
||||
return 1;
|
||||
case Obj::LEARNING_STONE:
|
||||
return 1.0f / std::sqrtf(hero->level);
|
||||
case Obj::ARENA:
|
||||
case Obj::SHRINE_OF_MAGIC_THOUGHT:
|
||||
return 2;
|
||||
@ -339,6 +372,86 @@ int32_t getGoldReward(const CGObjectInstance * target, const CGHeroInstance * he
|
||||
}
|
||||
}
|
||||
|
||||
class ExecuteHeroChainEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override
|
||||
{
|
||||
auto evaluationContext = task->evaluationContext;
|
||||
|
||||
int objId = task->objid;
|
||||
|
||||
if(task->parent)
|
||||
objId = task->parent->objid;
|
||||
|
||||
auto heroPtr = task->hero;
|
||||
const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false);
|
||||
auto day = cb->getDate(Date::DAY);
|
||||
auto hero = heroPtr.get();
|
||||
bool checkGold = evaluationContext.danger == 0;
|
||||
|
||||
evaluationContext.armyLossPersentage = task->evaluationContext.armyLoss / (double)task->evaluationContext.heroStrength;
|
||||
evaluationContext.heroRole = ai->ah->getHeroRole(heroPtr);
|
||||
evaluationContext.goldReward = getGoldReward(target, hero);
|
||||
evaluationContext.armyReward = getArmyReward(target, hero, checkGold);
|
||||
evaluationContext.skillReward = getSkillReward(target, hero, evaluationContext.heroRole);
|
||||
evaluationContext.strategicalValue = getStrategicalValue(target);
|
||||
|
||||
return evaluationContext;
|
||||
}
|
||||
};
|
||||
|
||||
class BuildThisEvaluationContextBuilder : public IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal task) const override
|
||||
{
|
||||
Goals::EvaluationContext evaluationContext;
|
||||
Goals::BuildThis & buildThis = dynamic_cast<Goals::BuildThis &>(*task);
|
||||
auto & bi = buildThis.buildingInfo;
|
||||
|
||||
evaluationContext.goldReward = bi.dailyIncome[Res::GOLD] / 2;
|
||||
evaluationContext.heroRole = HeroRole::MAIN;
|
||||
evaluationContext.movementCostByRole[evaluationContext.heroRole] = bi.prerequisitesCount;
|
||||
evaluationContext.armyReward = 0;
|
||||
evaluationContext.strategicalValue = buildThis.townInfo.armyScore / 50000.0;
|
||||
|
||||
if(bi.creatureID != CreatureID::NONE)
|
||||
{
|
||||
evaluationContext.strategicalValue += 0.5f + 0.1f * bi.creatureLevel;
|
||||
|
||||
if(bi.baseCreatureID == bi.creatureID)
|
||||
{
|
||||
evaluationContext.armyReward = ai->ah->evaluateStackPower(bi.creatureID.toCreature(), bi.creatureGrows);
|
||||
}
|
||||
|
||||
auto creaturesToUpgrade = ai->ah->getTotalCreaturesAvailable(bi.baseCreatureID);
|
||||
auto upgradedPower = ai->ah->evaluateStackPower(bi.creatureID.toCreature(), creaturesToUpgrade.count);
|
||||
|
||||
evaluationContext.armyReward = upgradedPower - creaturesToUpgrade.power;
|
||||
}
|
||||
|
||||
return evaluationContext;
|
||||
}
|
||||
};
|
||||
|
||||
PriorityEvaluator::PriorityEvaluator()
|
||||
{
|
||||
initVisitTile();
|
||||
evaluationContextBuilders[Goals::EXECUTE_HERO_CHAIN] = std::make_shared<ExecuteHeroChainEvaluationContextBuilder>();
|
||||
evaluationContextBuilders[Goals::BUILD_STRUCTURE] = std::make_shared<BuildThisEvaluationContextBuilder>();
|
||||
}
|
||||
|
||||
Goals::EvaluationContext PriorityEvaluator::buildEvaluationContext(Goals::TSubgoal goal) const
|
||||
{
|
||||
auto builder = evaluationContextBuilders.find(goal->goalType);
|
||||
|
||||
if(builder == evaluationContextBuilders.end())
|
||||
return goal->evaluationContext;
|
||||
|
||||
return builder->second->buildEvaluationContext(goal);
|
||||
}
|
||||
|
||||
/// distance
|
||||
/// nearest hero?
|
||||
/// gold income
|
||||
@ -351,45 +464,28 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
if(task->priority > 0)
|
||||
return task->priority;
|
||||
|
||||
auto heroPtr = task->hero;
|
||||
auto evaluationContext = buildEvaluationContext(task);
|
||||
|
||||
if(!heroPtr.validAndSet())
|
||||
return 2;
|
||||
|
||||
int objId = task->objid;
|
||||
|
||||
if(task->parent)
|
||||
objId = task->parent->objid;
|
||||
|
||||
const CGObjectInstance * target = cb->getObj((ObjectInstanceID)objId, false);
|
||||
int rewardType = (evaluationContext.goldReward > 0 ? 1 : 0)
|
||||
+ (evaluationContext.armyReward > 0 ? 1 : 0)
|
||||
+ (evaluationContext.skillReward > 0 ? 1 : 0)
|
||||
+ (evaluationContext.strategicalValue > 0 ? 1 : 0);
|
||||
|
||||
auto day = cb->getDate(Date::DAY);
|
||||
auto hero = heroPtr.get();
|
||||
auto armyTotal = task->evaluationContext.heroStrength;
|
||||
double armyLossPersentage = task->evaluationContext.armyLoss / (double)armyTotal;
|
||||
uint64_t danger = task->evaluationContext.danger;
|
||||
HeroRole heroRole = ai->ah->getHeroRole(heroPtr);
|
||||
int32_t goldReward = getGoldReward(target, hero);
|
||||
bool checkGold = danger == 0;
|
||||
uint64_t armyReward = getArmyReward(target, hero, checkGold);
|
||||
float skillReward = getSkillReward(target, hero, heroRole);
|
||||
float strategicalValue = getStrategicalValue(target);
|
||||
double result = 0;
|
||||
int rewardType = (goldReward > 0 ? 1 : 0) + (armyReward > 0 ? 1 : 0) + (skillReward > 0 ? 1 : 0) + (strategicalValue > 0 ? 1 : 0);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
armyLossPersentageVariable->setValue(armyLossPersentage);
|
||||
heroRoleVariable->setValue(heroRole);
|
||||
mainTurnDistanceVariable->setValue(task->evaluationContext.movementCostByRole[HeroRole::MAIN]);
|
||||
scoutTurnDistanceVariable->setValue(task->evaluationContext.movementCostByRole[HeroRole::SCOUT]);
|
||||
goldRewardVariable->setValue(goldReward);
|
||||
armyRewardVariable->setValue(armyReward);
|
||||
skillRewardVariable->setValue(skillReward);
|
||||
dangerVariable->setValue(danger);
|
||||
armyLossPersentageVariable->setValue(evaluationContext.armyLossPersentage);
|
||||
heroRoleVariable->setValue(evaluationContext.heroRole);
|
||||
mainTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::MAIN]);
|
||||
scoutTurnDistanceVariable->setValue(evaluationContext.movementCostByRole[HeroRole::SCOUT]);
|
||||
goldRewardVariable->setValue(evaluationContext.goldReward);
|
||||
armyRewardVariable->setValue(evaluationContext.armyReward);
|
||||
skillRewardVariable->setValue(evaluationContext.skillReward);
|
||||
dangerVariable->setValue(evaluationContext.danger);
|
||||
rewardTypeVariable->setValue(rewardType);
|
||||
closestHeroRatioVariable->setValue(task->evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(strategicalValue);
|
||||
closestHeroRatioVariable->setValue(evaluationContext.closestWayRatio);
|
||||
strategicalValueVariable->setValue(evaluationContext.strategicalValue);
|
||||
|
||||
engine->process();
|
||||
//engine.process(VISIT_TILE); //TODO: Process only Visit_Tile
|
||||
@ -402,16 +498,16 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task)
|
||||
assert(result >= 0);
|
||||
|
||||
#ifdef VCMI_TRACE_PATHFINDER
|
||||
logAi->trace("Evaluated %s, hero %s, loss: %f, turns: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f",
|
||||
logAi->trace("Evaluated %s, loss: %f, turns main: %f, scout: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f",
|
||||
task->name(),
|
||||
hero->name,
|
||||
armyLossPersentage,
|
||||
task->evaluationContext.movementCost,
|
||||
goldReward,
|
||||
armyReward,
|
||||
danger,
|
||||
heroRole ? "scout" : "main",
|
||||
strategicalValue,
|
||||
evaluationContext.armyLossPersentage,
|
||||
evaluationContext.movementCostByRole[HeroRole::MAIN],
|
||||
evaluationContext.movementCostByRole[HeroRole::SCOUT],
|
||||
evaluationContext.goldReward,
|
||||
evaluationContext.armyReward,
|
||||
evaluationContext.danger,
|
||||
evaluationContext.heroRole ? "scout" : "main",
|
||||
evaluationContext.strategicalValue,
|
||||
result);
|
||||
#endif
|
||||
|
||||
|
@ -10,12 +10,12 @@
|
||||
#pragma once
|
||||
#include "fl/Headers.h"
|
||||
#include "../Goals/Goals.h"
|
||||
#include "../FuzzyEngines.h"
|
||||
|
||||
class VCAI;
|
||||
class CArmedInstance;
|
||||
class CBank;
|
||||
struct SectorMap;
|
||||
class IEvaluationContextBuilder
|
||||
{
|
||||
public:
|
||||
virtual Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal goal) const = 0;
|
||||
};
|
||||
|
||||
class PriorityEvaluator
|
||||
{
|
||||
@ -40,4 +40,7 @@ private:
|
||||
fl::InputVariable * rewardTypeVariable;
|
||||
fl::InputVariable * closestHeroRatioVariable;
|
||||
fl::OutputVariable * value;
|
||||
std::map<Goals::EGoals, std::shared_ptr<IEvaluationContextBuilder>> evaluationContextBuilders;
|
||||
|
||||
Goals::EvaluationContext buildEvaluationContext(Goals::TSubgoal goal) const;
|
||||
};
|
||||
|
@ -96,12 +96,17 @@ namespace Goals
|
||||
{
|
||||
float movementCost;
|
||||
std::map<HeroRole, float> movementCostByRole;
|
||||
float scoutMovementCost;
|
||||
int manaCost;
|
||||
uint64_t danger;
|
||||
float closestWayRatio;
|
||||
uint64_t armyLoss;
|
||||
uint64_t heroStrength;
|
||||
float armyLossPersentage;
|
||||
float armyReward;
|
||||
int32_t goldReward;
|
||||
float skillReward;
|
||||
float strategicalValue;
|
||||
HeroRole heroRole;
|
||||
|
||||
EvaluationContext()
|
||||
: movementCost(0.0),
|
||||
@ -110,7 +115,12 @@ namespace Goals
|
||||
closestWayRatio(1),
|
||||
armyLoss(0),
|
||||
heroStrength(0),
|
||||
movementCostByRole()
|
||||
movementCostByRole(),
|
||||
skillReward(0),
|
||||
goldReward(0),
|
||||
armyReward(0),
|
||||
armyLossPersentage(0),
|
||||
heroRole(HeroRole::SCOUT)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -31,6 +31,11 @@ bool BuildThis::operator==(const BuildThis & other) const
|
||||
return town == other.town && bid == other.bid;
|
||||
}
|
||||
|
||||
std::string BuildThis::name() const
|
||||
{
|
||||
return "Build " + buildingInfo.name + "(" + std::to_string(bid) + ") in " + town->name;
|
||||
}
|
||||
|
||||
TSubgoal BuildThis::whatToDoToAchieve()
|
||||
{
|
||||
auto b = BuildingID(bid);
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CGoal.h"
|
||||
#include "../Analyzers/BuildAnalyzer.h"
|
||||
|
||||
struct HeroPtr;
|
||||
class VCAI;
|
||||
@ -20,10 +21,19 @@ namespace Goals
|
||||
class DLL_EXPORT BuildThis : public CGoal<BuildThis>
|
||||
{
|
||||
public:
|
||||
BuildingInfo buildingInfo;
|
||||
TownDevelopmentInfo townInfo;
|
||||
|
||||
BuildThis() //should be private, but unit test uses it
|
||||
: CGoal(Goals::BUILD_STRUCTURE)
|
||||
{
|
||||
}
|
||||
BuildThis(const BuildingInfo & buildingInfo, const TownDevelopmentInfo & townInfo) //should be private, but unit test uses it
|
||||
: CGoal(Goals::BUILD_STRUCTURE), buildingInfo(buildingInfo), townInfo(townInfo)
|
||||
{
|
||||
bid = buildingInfo.id;
|
||||
town = townInfo.town;
|
||||
}
|
||||
BuildThis(BuildingID Bid, const CGTownInstance * tid)
|
||||
: CGoal(Goals::BUILD_STRUCTURE)
|
||||
{
|
||||
@ -44,5 +54,6 @@ namespace Goals
|
||||
TSubgoal whatToDoToAchieve() override;
|
||||
//bool fulfillsMe(TSubgoal goal) override;
|
||||
virtual bool operator==(const BuildThis & other) const override;
|
||||
virtual std::string name() const override;
|
||||
};
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
|
||||
|
||||
std::string ExecuteHeroChain::name() const
|
||||
{
|
||||
return "ExecuteHeroChain " + targetName;
|
||||
return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->name;
|
||||
}
|
||||
|
||||
std::string ExecuteHeroChain::completeMessage() const
|
||||
|
@ -101,8 +101,10 @@ float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const
|
||||
return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f;
|
||||
}
|
||||
|
||||
void HeroManager::updateHeroRoles()
|
||||
void HeroManager::update()
|
||||
{
|
||||
logAi->trace("Start analysing our heroes");
|
||||
|
||||
std::map<HeroPtr, float> scores;
|
||||
auto myHeroes = ai->getMyHeroes();
|
||||
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
virtual const std::map<HeroPtr, HeroRole> & getHeroRoles() const = 0;
|
||||
virtual int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const = 0;
|
||||
virtual HeroRole getHeroRole(const HeroPtr & hero) const = 0;
|
||||
virtual void updateHeroRoles() = 0;
|
||||
virtual void update() = 0;
|
||||
virtual float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const = 0;
|
||||
virtual float evaluateHero(const CGHeroInstance * hero) const = 0;
|
||||
};
|
||||
@ -64,7 +64,7 @@ public:
|
||||
const std::map<HeroPtr, HeroRole> & getHeroRoles() const override;
|
||||
HeroRole getHeroRole(const HeroPtr & hero) const override;
|
||||
int selectBestSkill(const HeroPtr & hero, const std::vector<SecondarySkill> & skills) const override;
|
||||
void updateHeroRoles() override;
|
||||
void update() override;
|
||||
float evaluateSecSkill(SecondarySkill skill, const CGHeroInstance * hero) const override;
|
||||
float evaluateHero(const CGHeroInstance * hero) const override;
|
||||
|
||||
|
@ -76,7 +76,7 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
|
||||
void AINodeStorage::clear()
|
||||
{
|
||||
actors.clear();
|
||||
heroChainPass = false;
|
||||
heroChainPass = EHeroChainPass::INITIAL;
|
||||
heroChainTurn = 0;
|
||||
heroChainMaxTurns = 1;
|
||||
}
|
||||
@ -262,9 +262,37 @@ bool AINodeStorage::increaseHeroChainTurnLimit()
|
||||
return true;
|
||||
}
|
||||
|
||||
EPathfindingLayer phisycalLayers[2] = {EPathfindingLayer::LAND, EPathfindingLayer::SAIL};
|
||||
|
||||
bool AINodeStorage::calculateHeroChainFinal()
|
||||
{
|
||||
heroChainPass = EHeroChainPass::FINAL;
|
||||
heroChain.resize(0);
|
||||
|
||||
for(auto layer : phisycalLayers)
|
||||
{
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
if(node.turns > heroChainTurn
|
||||
&& node.action != CGPathNode::ENodeAction::UNKNOWN
|
||||
&& node.actor->actorExchangeCount > 1)
|
||||
{
|
||||
heroChain.push_back(&node);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return heroChain.size();
|
||||
}
|
||||
|
||||
bool AINodeStorage::calculateHeroChain()
|
||||
{
|
||||
heroChainPass = true;
|
||||
heroChainPass = EHeroChainPass::CHAIN;
|
||||
heroChain.resize(0);
|
||||
|
||||
std::vector<AIPathNode *> existingChains;
|
||||
@ -273,37 +301,40 @@ bool AINodeStorage::calculateHeroChain()
|
||||
existingChains.reserve(NUM_CHAINS);
|
||||
newChains.reserve(NUM_CHAINS);
|
||||
|
||||
foreach_tile_pos([&](const int3 & pos) {
|
||||
auto layer = EPathfindingLayer::LAND;
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
|
||||
existingChains.resize(0);
|
||||
newChains.resize(0);
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
for(auto layer : phisycalLayers)
|
||||
{
|
||||
foreach_tile_pos([&](const int3 & pos)
|
||||
{
|
||||
if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
|
||||
existingChains.push_back(&node);
|
||||
}
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][layer];
|
||||
|
||||
for(AIPathNode * node : existingChains)
|
||||
{
|
||||
if(node->actor->isMovable)
|
||||
existingChains.resize(0);
|
||||
newChains.resize(0);
|
||||
|
||||
for(AIPathNode & node : chains)
|
||||
{
|
||||
calculateHeroChain(node, existingChains, newChains);
|
||||
if(node.turns <= heroChainTurn && node.action != CGPathNode::ENodeAction::UNKNOWN)
|
||||
existingChains.push_back(&node);
|
||||
}
|
||||
}
|
||||
|
||||
cleanupInefectiveChains(newChains);
|
||||
addHeroChain(newChains);
|
||||
});
|
||||
for(AIPathNode * node : existingChains)
|
||||
{
|
||||
if(node->actor->isMovable)
|
||||
{
|
||||
calculateHeroChain(node, existingChains, newChains);
|
||||
}
|
||||
}
|
||||
|
||||
cleanupInefectiveChains(newChains);
|
||||
addHeroChain(newChains);
|
||||
});
|
||||
}
|
||||
|
||||
return heroChain.size();
|
||||
}
|
||||
|
||||
void AINodeStorage::cleanupInefectiveChains(std::vector<ExchangeCandidate> & result) const
|
||||
{
|
||||
vstd::erase_if(result, [&](ExchangeCandidate & chainInfo) -> bool
|
||||
vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool
|
||||
{
|
||||
auto pos = chainInfo.coord;
|
||||
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
|
||||
@ -320,12 +351,27 @@ void AINodeStorage::calculateHeroChain(
|
||||
{
|
||||
for(AIPathNode * node : variants)
|
||||
{
|
||||
if(node == srcNode
|
||||
|| !node->actor
|
||||
|| node->turns > heroChainTurn
|
||||
if(node == srcNode || !node->actor)
|
||||
continue;
|
||||
|
||||
if(node->turns > heroChainTurn
|
||||
|| (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
|
||||
|| (node->actor->chainMask & srcNode->actor->chainMask) != 0)
|
||||
{
|
||||
#if AI_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
"Skip exchange %s[%x] -> %s[%x] at %s because of %s",
|
||||
node->actor->toString(),
|
||||
node->actor->chainMask,
|
||||
srcNode->actor->toString(),
|
||||
srcNode->actor->chainMask,
|
||||
srcNode->coord.toString(),
|
||||
(node->turns > heroChainTurn
|
||||
? "turn limit"
|
||||
: (node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
|
||||
? "action unknown"
|
||||
: "chain mask"));
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -793,7 +839,7 @@ bool AINodeStorage::hasBetterChain(
|
||||
}
|
||||
}
|
||||
|
||||
if((candidateActor->chainMask & node.actor->chainMask) == 0)
|
||||
if(candidateActor->chainMask != node.actor->chainMask)
|
||||
continue;
|
||||
|
||||
auto nodeActor = node.actor;
|
||||
@ -931,6 +977,17 @@ AIPath::AIPath()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<const ISpecialAction> AIPath::getFirstBlockedAction() const
|
||||
{
|
||||
for(auto node : nodes)
|
||||
{
|
||||
if(node.specialAction && !node.specialAction->canAct(node.targetHero))
|
||||
return node.specialAction;
|
||||
}
|
||||
|
||||
return std::shared_ptr<const ISpecialAction>();
|
||||
}
|
||||
|
||||
int3 AIPath::firstTileToGet() const
|
||||
{
|
||||
if(nodes.size())
|
||||
@ -1005,7 +1062,7 @@ uint64_t AIPath::getTotalArmyLoss() const
|
||||
return armyLoss + targetObjectArmyLoss;
|
||||
}
|
||||
|
||||
std::string AIPath::toString()
|
||||
std::string AIPath::toString() const
|
||||
{
|
||||
std::stringstream str;
|
||||
|
||||
|
@ -79,7 +79,9 @@ struct AIPath
|
||||
|
||||
uint64_t getHeroStrength() const;
|
||||
|
||||
std::string toString();
|
||||
std::string toString() const;
|
||||
|
||||
std::shared_ptr<const ISpecialAction> AIPath::getFirstBlockedAction() const;
|
||||
};
|
||||
|
||||
struct ExchangeCandidate : public AIPathNode
|
||||
@ -88,6 +90,15 @@ struct ExchangeCandidate : public AIPathNode
|
||||
AIPathNode * otherParent;
|
||||
};
|
||||
|
||||
enum EHeroChainPass
|
||||
{
|
||||
INITIAL, // single heroes unlimited distance
|
||||
|
||||
CHAIN, // chains with limited distance
|
||||
|
||||
FINAL // same as SINGLE but for heroes from CHAIN pass
|
||||
};
|
||||
|
||||
class AINodeStorage : public INodeStorage
|
||||
{
|
||||
private:
|
||||
@ -100,7 +111,7 @@ private:
|
||||
std::unique_ptr<FuzzyHelper> dangerEvaluator;
|
||||
std::vector<std::shared_ptr<ChainActor>> actors;
|
||||
std::vector<CGPathNode *> heroChain;
|
||||
bool heroChainPass; // true if we need to calculate hero chain
|
||||
EHeroChainPass heroChainPass; // true if we need to calculate hero chain
|
||||
int heroChainTurn;
|
||||
int heroChainMaxTurns;
|
||||
PlayerColor playerID;
|
||||
@ -146,7 +157,7 @@ public:
|
||||
bool isMovementIneficient(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
|
||||
{
|
||||
// further chain distribution is calculated as the last stage
|
||||
if(heroChainPass && destination.node->turns > heroChainTurn)
|
||||
if(heroChainPass == EHeroChainPass::CHAIN && destination.node->turns > heroChainTurn)
|
||||
return true;
|
||||
|
||||
return hasBetterChain(source, destination);
|
||||
@ -169,6 +180,7 @@ public:
|
||||
const std::set<const CGHeroInstance *> getAllHeroes() const;
|
||||
void clear();
|
||||
bool calculateHeroChain();
|
||||
bool calculateHeroChainFinal();
|
||||
|
||||
uint64_t evaluateDanger(const int3 & tile, const CGHeroInstance * hero) const
|
||||
{
|
||||
|
@ -66,21 +66,21 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
|
||||
|
||||
do
|
||||
{
|
||||
logAi->trace("Recalculate paths pass %d", pass++);
|
||||
cb->calculatePaths(config);
|
||||
|
||||
if(useHeroChain)
|
||||
do
|
||||
{
|
||||
logAi->trace("Recalculate chain pass %d", pass);
|
||||
logAi->trace("Recalculate paths pass %d", pass++);
|
||||
cb->calculatePaths(config);
|
||||
} while(useHeroChain && storage->calculateHeroChain());
|
||||
|
||||
continueCalculation = storage->calculateHeroChain();
|
||||
if(!useHeroChain)
|
||||
break;
|
||||
|
||||
if(!continueCalculation)
|
||||
{
|
||||
logAi->trace("Increase chain turn limit");
|
||||
|
||||
continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain();
|
||||
}
|
||||
if(storage->calculateHeroChainFinal())
|
||||
{
|
||||
logAi->trace("Recalculate paths pass final");
|
||||
cb->calculatePaths(config);
|
||||
}
|
||||
|
||||
continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain();
|
||||
} while(continueCalculation);
|
||||
}
|
||||
|
@ -853,10 +853,10 @@ void VCAI::makeTurn()
|
||||
logAi->debug("Making turn thread has been interrupted. We'll end without calling endTurn.");
|
||||
return;
|
||||
}
|
||||
catch (std::exception & e)
|
||||
/*catch (std::exception & e)
|
||||
{
|
||||
logAi->debug("Making turn thread has caught an exception: %s", e.what());
|
||||
}
|
||||
}*/
|
||||
|
||||
endTurn();
|
||||
}
|
||||
@ -1079,7 +1079,12 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
|
||||
moveCreaturesToHero(h->visitedTown);
|
||||
|
||||
townVisitsThisWeek[h].insert(h->visitedTown);
|
||||
ah->updateHeroRoles();
|
||||
|
||||
if(!ai->nullkiller)
|
||||
{
|
||||
ah->update();
|
||||
}
|
||||
|
||||
if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
|
||||
{
|
||||
if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
|
||||
@ -1994,9 +1999,11 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
|
||||
doChannelProbing();
|
||||
}
|
||||
|
||||
if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT)
|
||||
if(path.nodes[0].action == CGPathNode::BLOCKING_VISIT || path.nodes[0].action == CGPathNode::BATTLE)
|
||||
{
|
||||
ret = h && i == 0; // when we take resource we do not reach its position. We even might not move
|
||||
// when we take resource we do not reach its position. We even might not move
|
||||
// also guarded town is not get visited automatically after capturing
|
||||
ret = h && i == 0;
|
||||
}
|
||||
}
|
||||
if(h)
|
||||
@ -2485,6 +2492,8 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
|
||||
}
|
||||
cb->recruitHero(t, hero);
|
||||
|
||||
ai->ah->update();
|
||||
|
||||
if(t->visitingHero)
|
||||
moveHeroToTile(t->visitablePos(), t->visitingHero.get());
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user