From 8961f1c803ba7b9694b094721cb0e8a7adfdea34 Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sat, 15 May 2021 21:36:50 +0300 Subject: [PATCH] AI: fix hero exchange logic, allow splitting weakest-fastest creature, refactoring --- AI/Nullkiller/AIUtility.cpp | 67 ------------ AI/Nullkiller/AIUtility.h | 2 - AI/Nullkiller/AIhelper.cpp | 35 +++++- AI/Nullkiller/AIhelper.h | 11 +- AI/Nullkiller/ArmyManager.cpp | 158 +++++++++++++++++++++++++++ AI/Nullkiller/ArmyManager.h | 57 ++++++++++ AI/Nullkiller/CMakeLists.txt | 2 + AI/Nullkiller/Goals/GatherArmy.cpp | 10 +- AI/Nullkiller/VCAI.cpp | 166 ++++++++--------------------- AI/Nullkiller/VCAI.h | 1 - 10 files changed, 311 insertions(+), 198 deletions(-) create mode 100644 AI/Nullkiller/ArmyManager.cpp create mode 100644 AI/Nullkiller/ArmyManager.h diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 50b517d4c..84d1ac295 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -302,73 +302,6 @@ creInfo infoFromDC(const dwellingContent & dc) return ci; } -ui64 howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) -{ - ui64 aivalue = 0; - TResources availableRes = cb->getResourceAmount(); - int freeHeroSlots = GameConstants::ARMY_SIZE - h->stacksCount(); - - for(auto const dc : t->creatures) - { - creInfo ci = infoFromDC(dc); - - if(!ci.count || ci.creID == -1) - continue; - - vstd::amin(ci.count, availableRes / ci.cre->cost); //max count we can afford - - if(ci.count && ci.creID != -1) //valid creature at this level - { - //can be merged with another stack? - SlotID dst = h->getSlotFor(ci.creID); - if(!h->hasStackAtSlot(dst)) //need another new slot for this stack - { - if(!freeHeroSlots) //no more place for stacks - continue; - else - freeHeroSlots--; //new slot will be occupied - } - - //we found matching occupied or free slot - aivalue += ci.count * ci.cre->AIValue; - availableRes -= ci.cre->cost * ci.count; - } - } - - return aivalue; -} - -ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) -{ - ui64 ret = 0; - int freeHeroSlots = GameConstants::ARMY_SIZE - target->stacksCount(); - std::vector toMove; - for(auto const slot : source->Slots()) - { - //can be merged woth another stack? - SlotID dst = target->getSlotFor(slot.second->getCreatureID()); - if(target->hasStackAtSlot(dst)) - ret += source->getPower(slot.first); - else - toMove.push_back(slot.second); - } - boost::sort(toMove, [](const CStackInstance * lhs, const CStackInstance * rhs) - { - return lhs->getPower() < rhs->getPower(); - }); - for(auto & stack : boost::adaptors::reverse(toMove)) - { - if(freeHeroSlots) - { - ret += stack->getPower(); - freeHeroSlots--; - } - else - break; - } - return ret; -} - bool compareHeroStrength(HeroPtr h1, HeroPtr h2) { return h1->getTotalStrength() < h2->getTotalStrength(); diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index 954b72631..4d6adb061 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -174,8 +174,6 @@ bool isSafeToVisit(HeroPtr h, crint3 tile); bool compareHeroStrength(HeroPtr h1, HeroPtr h2); bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2); bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2); -ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source); -ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source); class CDistanceSorter { diff --git a/AI/Nullkiller/AIhelper.cpp b/AI/Nullkiller/AIhelper.cpp index b334ec58f..c277ad05d 100644 --- a/AI/Nullkiller/AIhelper.cpp +++ b/AI/Nullkiller/AIhelper.cpp @@ -10,14 +10,13 @@ #include "StdInc.h" #include "AIhelper.h" -#include "ResourceManager.h" -#include "BuildingManager.h" AIhelper::AIhelper() { resourceManager.reset(new ResourceManager()); buildingManager.reset(new BuildingManager()); pathfindingManager.reset(new PathfindingManager()); + armyManager.reset(new ArmyManager()); } AIhelper::~AIhelper() @@ -34,6 +33,7 @@ void AIhelper::init(CPlayerSpecificInfoCallback * CB) resourceManager->init(CB); buildingManager->init(CB); pathfindingManager->init(CB); + armyManager->init(CB); } void AIhelper::setAI(VCAI * AI) @@ -41,6 +41,7 @@ void AIhelper::setAI(VCAI * AI) resourceManager->setAI(AI); buildingManager->setAI(AI); pathfindingManager->setAI(AI); + armyManager->setAI(AI); } bool AIhelper::getBuildingOptions(const CGTownInstance * t) @@ -152,3 +153,33 @@ void AIhelper::updatePaths(std::vector heroes, bool useHeroChain) { pathfindingManager->updatePaths(heroes, useHeroChain); } + +bool AIhelper::canGetArmy(const CArmedInstance * army, const CArmedInstance * source) const +{ + return armyManager->canGetArmy(army, source); +} + +ui64 AIhelper::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) const +{ + return armyManager->howManyReinforcementsCanBuy(h, t); +} + +ui64 AIhelper::howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const +{ + return armyManager->howManyReinforcementsCanGet(target, source); +} + +std::vector AIhelper::getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const +{ + return armyManager->getBestArmy(target, source); +} + +std::vector::iterator AIhelper::getWeakestCreature(std::vector & army) const +{ + return armyManager->getWeakestCreature(army); +} + +std::vector AIhelper::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const +{ + return armyManager->getSortedSlots(target, source); +} \ No newline at end of file diff --git a/AI/Nullkiller/AIhelper.h b/AI/Nullkiller/AIhelper.h index 50e6074a2..7f8e55fd0 100644 --- a/AI/Nullkiller/AIhelper.h +++ b/AI/Nullkiller/AIhelper.h @@ -16,6 +16,7 @@ #include "ResourceManager.h" #include "BuildingManager.h" +#include "ArmyManager.h" #include "Pathfinding/PathfindingManager.h" class ResourceManager; @@ -23,7 +24,7 @@ class BuildingManager; //indirection interface for various modules -class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager +class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager, public IArmyManager { friend class VCAI; friend struct SetGlobalState; //mess? @@ -31,6 +32,7 @@ class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, pu std::shared_ptr resourceManager; std::shared_ptr buildingManager; std::shared_ptr pathfindingManager; + std::shared_ptr armyManager; //TODO: vector public: AIhelper(); @@ -68,6 +70,13 @@ public: return pathfindingManager->isTileAccessible(hero, tile); } + bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override; + ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override; + ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const override; + std::vector getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const override; + std::vector::iterator getWeakestCreature(std::vector & army) const override; + std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; + private: bool notifyGoalCompleted(Goals::TSubgoal goal) override; diff --git a/AI/Nullkiller/ArmyManager.cpp b/AI/Nullkiller/ArmyManager.cpp new file mode 100644 index 000000000..a07320c58 --- /dev/null +++ b/AI/Nullkiller/ArmyManager.cpp @@ -0,0 +1,158 @@ +/* +* BuildingManager.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 "ArmyManager.h" + +#include "../../CCallback.h" +#include "../../lib/mapObjects/MapObjects.h" + +void ArmyManager::init(CPlayerSpecificInfoCallback * CB) +{ + cb = CB; +} + +void ArmyManager::setAI(VCAI * AI) +{ + ai = AI; +} + +std::vector ArmyManager::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const +{ + const CCreatureSet * armies[] = { target, source }; + + //we calculate total strength for each creature type available in armies + std::map creToPower; + std::vector resultingArmy; + + for(auto armyPtr : armies) + { + for(auto & i : armyPtr->Slots()) + { + auto & slotInfp = creToPower[i.second->type]; + + slotInfp.creature = i.second->type; + slotInfp.power += i.second->getPower(); + slotInfp.count += i.second->count; + } + } + + for(auto pair : creToPower) + resultingArmy.push_back(pair.second); + + boost::sort(resultingArmy, [](SlotInfo & left, SlotInfo & right) -> bool + { + return left.power > right.power; + }); + + return resultingArmy; +} + +std::vector::iterator ArmyManager::getWeakestCreature(std::vector & army) const +{ + auto weakest = boost::min_element(army, [](SlotInfo & left, SlotInfo & right) -> bool + { + if(left.creature->level != right.creature->level) + return left.creature->level < right.creature->level; + + return left.creature->Speed() > right.creature->Speed(); + }); + + return weakest; +} + +std::vector ArmyManager::getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const +{ + auto resultingArmy = getSortedSlots(target, source); + + if(resultingArmy.size() > GameConstants::ARMY_SIZE) + { + resultingArmy.resize(GameConstants::ARMY_SIZE); + } + else if(source->needsLastStack()) + { + auto weakest = getWeakestCreature(resultingArmy); + + if(weakest->count == 1) + { + resultingArmy.erase(weakest); + } + else + { + weakest->power -= weakest->power / weakest->count; + weakest->count--; + } + } + + return resultingArmy; +} + +bool ArmyManager::canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const +{ + //TODO: merge with pickBestCreatures + //if (ai->primaryHero().h == source) + if(target->tempOwner != source->tempOwner) + { + logAi->error("Why are we even considering exchange between heroes from different players?"); + return false; + } + + return 0 < howManyReinforcementsCanGet(target, source); +} + +ui64 ArmyManager::howManyReinforcementsCanBuy(const CCreatureSet * h, const CGDwelling * t) const +{ + ui64 aivalue = 0; + TResources availableRes = cb->getResourceAmount(); + int freeHeroSlots = GameConstants::ARMY_SIZE - h->stacksCount(); + + for(auto const dc : t->creatures) + { + creInfo ci = infoFromDC(dc); + + if(!ci.count || ci.creID == -1) + continue; + + vstd::amin(ci.count, availableRes / ci.cre->cost); //max count we can afford + + if(ci.count && ci.creID != -1) //valid creature at this level + { + //can be merged with another stack? + SlotID dst = h->getSlotFor(ci.creID); + if(!h->hasStackAtSlot(dst)) //need another new slot for this stack + { + if(!freeHeroSlots) //no more place for stacks + continue; + else + freeHeroSlots--; //new slot will be occupied + } + + //we found matching occupied or free slot + aivalue += ci.count * ci.cre->AIValue; + availableRes -= ci.cre->cost * ci.count; + } + } + + return aivalue; +} + +ui64 ArmyManager::howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const +{ + auto bestArmy = getBestArmy(target, source); + uint64_t newArmy = 0; + uint64_t oldArmy = target->getArmyStrength(); + + for(auto & slot : bestArmy) + { + newArmy += slot.power; + } + + return newArmy > oldArmy ? newArmy - oldArmy : 0; +} diff --git a/AI/Nullkiller/ArmyManager.h b/AI/Nullkiller/ArmyManager.h new file mode 100644 index 000000000..7579207da --- /dev/null +++ b/AI/Nullkiller/ArmyManager.h @@ -0,0 +1,57 @@ +/* +* ArmyManager.h, 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 +* +*/ + +#pragma once + +#include "AIUtility.h" + +#include "../../lib/GameConstants.h" +#include "../../lib/VCMI_Lib.h" +#include "../../lib/CTownHandler.h" +#include "../../lib/CBuildingHandler.h" +#include "VCAI.h" + +struct SlotInfo +{ + const CCreature * creature; + int count; + uint64_t power; +}; + +class DLL_EXPORT IArmyManager //: public: IAbstractManager +{ +public: + virtual void init(CPlayerSpecificInfoCallback * CB) = 0; + virtual void setAI(VCAI * AI) = 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; + virtual std::vector getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const = 0; + virtual std::vector::iterator getWeakestCreature(std::vector & army) const = 0; + virtual std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const = 0; +}; + +class DLL_EXPORT ArmyManager : public IArmyManager +{ +private: + CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback + VCAI * ai; + +public: + void init(CPlayerSpecificInfoCallback * CB) override; + void setAI(VCAI * AI) override; + + bool canGetArmy(const CArmedInstance * target, const CArmedInstance * source) const override; + ui64 howManyReinforcementsCanBuy(const CCreatureSet * target, const CGDwelling * source) const override; + ui64 howManyReinforcementsCanGet(const CCreatureSet * target, const CCreatureSet * source) const override; + std::vector getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const override; + std::vector::iterator getWeakestCreature(std::vector & army) const override; + std::vector getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override; +}; diff --git a/AI/Nullkiller/CMakeLists.txt b/AI/Nullkiller/CMakeLists.txt index 71d2f92c1..6fb512f3c 100644 --- a/AI/Nullkiller/CMakeLists.txt +++ b/AI/Nullkiller/CMakeLists.txt @@ -22,6 +22,7 @@ set(VCAI_SRCS Pathfinding/Rules/AIPreviousNodeRule.cpp AIUtility.cpp AIhelper.cpp + ArmyManager.cpp ResourceManager.cpp BuildingManager.cpp SectorMap.cpp @@ -78,6 +79,7 @@ set(VCAI_HEADERS Pathfinding/Rules/AIPreviousNodeRule.h AIUtility.h AIhelper.h + ArmyManager.h ResourceManager.h BuildingManager.h SectorMap.h diff --git a/AI/Nullkiller/Goals/GatherArmy.cpp b/AI/Nullkiller/Goals/GatherArmy.cpp index 7cc935a3b..826037689 100644 --- a/AI/Nullkiller/Goals/GatherArmy.cpp +++ b/AI/Nullkiller/Goals/GatherArmy.cpp @@ -62,7 +62,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() if(waysToVisit.size()) { //grab army from town - if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t)) + if(!t->visitingHero && ai->ah->howManyReinforcementsCanGet(hero.get(), t)) { if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) vstd::concatenate(ret, waysToVisit); @@ -73,8 +73,8 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() { std::vector values = { value, - (int)howManyReinforcementsCanBuy(t->getUpperArmy(), t), - (int)howManyReinforcementsCanBuy(hero.get(), t) }; + (int)ai->ah->howManyReinforcementsCanBuy(t->getUpperArmy(), t), + (int)ai->ah->howManyReinforcementsCanBuy(hero.get(), t) }; int val = *std::min_element(values.begin(), values.end()); @@ -113,7 +113,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() return true; else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)) return true; - else if(!ai->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue + else if(!ai->ah->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue return true; else if(ai->getGoal(h)->goalType == GATHER_ARMY) return true; @@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() { auto dwelling = dynamic_cast(obj); - ui32 val = std::min(value, howManyReinforcementsCanBuy(hero.get(), dwelling)); + ui32 val = std::min(value, ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling)); if(val) { diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index 4ee889a42..970773029 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -340,9 +340,9 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q } else //regular criteria { - if(firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy(firstHero, secondHero)) + if(firstHero->getFightingStrength() > secondHero->getFightingStrength() && ah->canGetArmy(firstHero, secondHero)) transferFrom2to1(firstHero, secondHero); - else if(canGetArmy(secondHero, firstHero)) + else if(ah->canGetArmy(secondHero, firstHero)) transferFrom2to1(secondHero, firstHero); } @@ -1081,133 +1081,59 @@ void VCAI::moveCreaturesToHero(const CGTownInstance * t) } } -bool VCAI::canGetArmy(const CGHeroInstance * army, const CGHeroInstance * source) -{ - //TODO: merge with pickBestCreatures - //if (ai->primaryHero().h == source) - if(army->tempOwner != source->tempOwner) - { - logAi->error("Why are we even considering exchange between heroes from different players?"); - return false; - } - - const CArmedInstance * armies[] = {army, source}; - - //we calculate total strength for each creature type available in armies - std::map creToPower; - for(auto armyPtr : armies) - { - for(auto & i : armyPtr->Slots()) - { - creToPower[i.second->type] += i.second->getPower(); - } - } - //TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc) - int armySize = creToPower.size(); - armySize = std::min(armySize, GameConstants::ARMY_SIZE); - std::vector bestArmy; //types that'll be in final dst army - for(int i = 0; i < armySize; i++) //pick the creatures from which we can get most power, as many as dest can fit - { - typedef const std::pair & CrePowerPair; - auto creIt = boost::max_element(creToPower, [](CrePowerPair lhs, CrePowerPair rhs) - { - return lhs.second < rhs.second; - }); - bestArmy.push_back(creIt->first); - creToPower.erase(creIt); - if(creToPower.empty()) - break; - } - - //foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs - for(int i = 0; i < bestArmy.size(); i++) //i-th strongest creature type will go to i-th slot - { - for(auto armyPtr : armies) - { - for(int j = 0; j < GameConstants::ARMY_SIZE; j++) - { - if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && armyPtr != army) //it's a searched creature not in dst ARMY - { - if(!(armyPtr->needsLastStack() && (armyPtr->stacksCount() == 1) && armyPtr->getStackCount(SlotID(j)) < 2)) //can't take away or split last creature - return true; //at least one exchange will be performed - else - return false; //no further exchange possible - } - } - } - } - return false; -} - void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArmedInstance * source) { const CArmedInstance * armies[] = {destinationArmy, source}; - //we calculate total strength for each creature type available in armies - std::map creToPower; - for(auto armyPtr : armies) - { - for(auto & i : armyPtr->Slots()) - { - creToPower[i.second->type] += i.second->getPower(); - } - } - //TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc) - int armySize = creToPower.size(); - - armySize = std::min(armySize, GameConstants::ARMY_SIZE); - std::vector bestArmy; //types that'll be in final dst army - for(int i = 0; i < armySize; i++) //pick the creatures from which we can get most power, as many as dest can fit - { - typedef const std::pair & CrePowerPair; - auto creIt = boost::max_element(creToPower, [](CrePowerPair lhs, CrePowerPair rhs) - { - return lhs.second < rhs.second; - }); - bestArmy.push_back(creIt->first); - creToPower.erase(creIt); - if(creToPower.empty()) - break; - } + auto bestArmy = ah->getSortedSlots(destinationArmy, source); //foreach best type -> iterate over slots in both armies and if it's the appropriate type, send it to the slot where it belongs - for(int i = 0; i < bestArmy.size(); i++) //i-th strongest creature type will go to i-th slot + for(SlotID i = SlotID(0); i.getNum() < bestArmy.size() && i.validSlot(); i.advance(1)) //i-th strongest creature type will go to i-th slot { + const CCreature * targetCreature = bestArmy[i.getNum()].creature; + for(auto armyPtr : armies) { - for(int j = 0; j < GameConstants::ARMY_SIZE; j++) + for(SlotID j = SlotID(0); j.validSlot(); j.advance(1)) { - if(armyPtr->getCreature(SlotID(j)) == bestArmy[i] && (i != j || armyPtr != destinationArmy)) //it's a searched creature not in dst SLOT + if(armyPtr->getCreature(j) == targetCreature && (i != j || armyPtr != destinationArmy)) //it's a searched creature not in dst SLOT { - if(!armyPtr->needsLastStack() || armyPtr->stacksCount() != 1 - || armyPtr == destinationArmy || 0 < destinationArmy->getStackCount(SlotID(i))) //can't take away last creature without split + //can't take away last creature without split. generate a new stack with 1 creature which is weak but fast + if(armyPtr == source + && source->needsLastStack() + && source->stacksCount() == 1 + && (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature)) { - cb->mergeOrSwapStacks(armyPtr, destinationArmy, SlotID(j), SlotID(i)); - } - else - { - //TODO: Improve logic by splitting weakest creature, instead of creature that becomes last stack - SlotID sourceSlot = SlotID(j); - auto lastStackCount = armyPtr->getStackCount(sourceSlot); + auto weakest = ah->getWeakestCreature(bestArmy); + + if(weakest->creature == targetCreature) + { + if(1 == source->getStackCount(j)) + break; - if(lastStackCount > 1) //we can perform exchange if we need creature and split is possible - { - SlotID destinationSlot = SlotID(i); - //check if there are some creatures of same type in destination army slots - add to them instead of first available empty slot if possible - for(int candidateSlot = 0; candidateSlot < GameConstants::ARMY_SIZE; candidateSlot++) - { - auto creatureInSlot = destinationArmy->getCreature(SlotID(candidateSlot)); - if(creatureInSlot && (creatureInSlot->idNumber == armyPtr->getCreature(SlotID(j))->idNumber)) - { - destinationSlot = SlotID(candidateSlot); - break; - } - } - //last cb->splitStack argument is total amount of creatures expected after exchange so if slot is not empty we need to add to existing creatures - auto destinationSlotCreatureCount = destinationArmy->getStackCount(destinationSlot); - cb->splitStack(armyPtr, destinationArmy, sourceSlot, destinationSlot, lastStackCount + destinationSlotCreatureCount - 1); + // move all except 1 of weakest creature from source to destination + cb->splitStack( + source, + destinationArmy, + j, + destinationArmy->getSlotFor(targetCreature), + destinationArmy->getStackCount(i) + source->getStackCount(j) - 1); + + break; + } + else + { + // Source last stack is not weakest. Move 1 of weakest creature from destination to source + cb->splitStack( + destinationArmy, + source, + destinationArmy->getSlotFor(weakest->creature), + source->getFreeSlot(), + 1); } } + + cb->mergeOrSwapStacks(armyPtr, destinationArmy, j, i); } } } @@ -1487,15 +1413,15 @@ void VCAI::wander(HeroPtr h) if(cb->getVisitableObjs(h->visitablePos()).size() > 1) moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate - auto compareReinforcements = [h](const CGTownInstance * lhs, const CGTownInstance * rhs) -> bool + auto compareReinforcements = [&](const CGTownInstance * lhs, const CGTownInstance * rhs) -> bool { const CGHeroInstance * hptr = h.get(); - auto r1 = howManyReinforcementsCanGet(hptr, lhs), - r2 = howManyReinforcementsCanGet(hptr, rhs); + auto r1 = ah->howManyReinforcementsCanGet(hptr, lhs), + r2 = ah->howManyReinforcementsCanGet(hptr, rhs); if (r1 != r2) return r1 < r2; else - return howManyReinforcementsCanBuy(hptr, lhs) < howManyReinforcementsCanBuy(hptr, rhs); + return ah->howManyReinforcementsCanBuy(hptr, lhs) < ah->howManyReinforcementsCanBuy(hptr, rhs); }; std::vector townsReachable; @@ -1533,11 +1459,11 @@ void VCAI::wander(HeroPtr h) else if(cb->getResourceAmount(Res::GOLD) >= GameConstants::HERO_GOLD_COST) { std::vector towns = cb->getTownsInfo(); - vstd::erase_if(towns, [](const CGTownInstance * t) -> bool + vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool { for(const CGHeroInstance * h : cb->getHeroesInfo()) { - if(!t->getArmyStrength() || howManyReinforcementsCanGet(h, t)) + if(!t->getArmyStrength() || ah->howManyReinforcementsCanGet(h, t)) return true; } return false; diff --git a/AI/Nullkiller/VCAI.h b/AI/Nullkiller/VCAI.h index e4d3251b7..5d986b742 100644 --- a/AI/Nullkiller/VCAI.h +++ b/AI/Nullkiller/VCAI.h @@ -216,7 +216,6 @@ public: bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const; //void recruitCreatures(const CGTownInstance * t); void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter); - bool canGetArmy(const CGHeroInstance * h, const CGHeroInstance * source); //can we get any better stacks from other hero? void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr); void moveCreaturesToHero(const CGTownInstance * t);