Przeglądaj źródła

AI: fix hero exchange logic, allow splitting weakest-fastest creature, refactoring

Andrii Danylchenko 6 lat temu
rodzic
commit
ffdf5ad180

+ 0 - 67
AI/VCAI/AIUtility.cpp

@@ -297,73 +297,6 @@ creInfo infoFromDC(const dwellingContent & dc)
 	return ci;
 }
 
-ui64 howManyReinforcementsCanBuy(const CArmedInstance * 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 CArmedInstance * h, const CGTownInstance * t)
-{
-	ui64 ret = 0;
-	int freeHeroSlots = GameConstants::ARMY_SIZE - h->stacksCount();
-	std::vector<const CStackInstance *> toMove;
-	for(auto const slot : t->Slots())
-	{
-		//can be merged woth another stack?
-		SlotID dst = h->getSlotFor(slot.second->getCreatureID());
-		if(h->hasStackAtSlot(dst))
-			ret += t->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();

+ 0 - 2
AI/VCAI/AIUtility.h

@@ -173,8 +173,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 CArmedInstance * h, const CGDwelling * t);
-ui64 howManyReinforcementsCanGet(const CArmedInstance * h, const CGTownInstance * t);
 
 class CDistanceSorter
 {

+ 33 - 2
AI/VCAI/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<HeroPtr> heroes)
 {
 	pathfindingManager->updatePaths(heroes);
 }
+
+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<SlotInfo> AIhelper::getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const
+{
+	return armyManager->getBestArmy(target, source);
+}
+
+std::vector<SlotInfo>::iterator AIhelper::getWeakestCreature(std::vector<SlotInfo> & army) const
+{
+	return armyManager->getWeakestCreature(army);
+}
+
+std::vector<SlotInfo> AIhelper::getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const
+{
+	return armyManager->getSortedSlots(target, source);
+}

+ 10 - 1
AI/VCAI/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> resourceManager;
 	std::shared_ptr<BuildingManager> buildingManager;
 	std::shared_ptr<PathfindingManager> pathfindingManager;
+	std::shared_ptr<ArmyManager> armyManager;
 	//TODO: vector<IAbstractManager>
 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<SlotInfo> getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const override;
+	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
+	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
+
 private:
 	bool notifyGoalCompleted(Goals::TSubgoal goal) override;
 

+ 158 - 0
AI/VCAI/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<SlotInfo> 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<const CCreature *, SlotInfo> creToPower;
+	std::vector<SlotInfo> 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<SlotInfo>::iterator ArmyManager::getWeakestCreature(std::vector<SlotInfo> & 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<SlotInfo> 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;
+}

+ 57 - 0
AI/VCAI/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<SlotInfo> getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const = 0;
+	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;
+};
+
+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<SlotInfo> getBestArmy(const CCreatureSet * target, const CCreatureSet * source) const override;
+	std::vector<SlotInfo>::iterator getWeakestCreature(std::vector<SlotInfo> & army) const override;
+	std::vector<SlotInfo> getSortedSlots(const CCreatureSet * target, const CCreatureSet * source) const override;
+};

+ 2 - 0
AI/VCAI/CMakeLists.txt

@@ -21,6 +21,7 @@ set(VCAI_SRCS
 		Pathfinding/Rules/AIPreviousNodeRule.cpp
 		AIUtility.cpp
 		AIhelper.cpp
+		ArmyManager.cpp
 		ResourceManager.cpp
 		BuildingManager.cpp
 		SectorMap.cpp
@@ -71,6 +72,7 @@ set(VCAI_HEADERS
 		Pathfinding/Rules/AIPreviousNodeRule.h
 		AIUtility.h
 		AIhelper.h
+		ArmyManager.h
 		ResourceManager.h
 		BuildingManager.h
 		SectorMap.h

+ 5 - 5
AI/VCAI/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<int> 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<const CGDwelling *>(obj);
 
-				ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero.get(), dwelling));
+				ui32 val = std::min<ui32>(value, ai->ah->howManyReinforcementsCanBuy(hero.get(), dwelling));
 
 				if(val)
 				{

+ 47 - 120
AI/VCAI/VCAI.cpp

@@ -332,9 +332,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);
 		}
 
@@ -1056,132 +1056,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<const CCreature *, int> 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<const CCreature *> 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<const CCreature *, int> & 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<const CCreature *, int> 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<const CCreature *> 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<const CCreature *, int> & 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)) //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);
-
-						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);
+						auto weakest = ah->getWeakestCreature(bestArmy);
+						
+						if(weakest->creature == targetCreature)
+						{
+							if(1 == source->getStackCount(j))
+								break;
+
+							// 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);
 				}
 			}
 		}
@@ -1461,15 +1388,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<const CGTownInstance *> townsReachable;
@@ -1507,11 +1434,11 @@ void VCAI::wander(HeroPtr h)
 			else if(cb->getResourceAmount(Res::GOLD) >= GameConstants::HERO_GOLD_COST)
 			{
 				std::vector<const CGTownInstance *> 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;

+ 0 - 1
AI/VCAI/VCAI.h

@@ -214,7 +214,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);