1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

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

This commit is contained in:
Andrii Danylchenko 2021-05-15 21:36:50 +03:00 committed by Andrii Danylchenko
parent e838e70165
commit 8961f1c803
10 changed files with 311 additions and 198 deletions

View File

@ -302,73 +302,6 @@ creInfo infoFromDC(const dwellingContent & dc)
return ci; 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<const CStackInstance *> 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) bool compareHeroStrength(HeroPtr h1, HeroPtr h2)
{ {
return h1->getTotalStrength() < h2->getTotalStrength(); return h1->getTotalStrength() < h2->getTotalStrength();

View File

@ -174,8 +174,6 @@ bool isSafeToVisit(HeroPtr h, crint3 tile);
bool compareHeroStrength(HeroPtr h1, HeroPtr h2); bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2); bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * 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 class CDistanceSorter
{ {

View File

@ -10,14 +10,13 @@
#include "StdInc.h" #include "StdInc.h"
#include "AIhelper.h" #include "AIhelper.h"
#include "ResourceManager.h"
#include "BuildingManager.h"
AIhelper::AIhelper() AIhelper::AIhelper()
{ {
resourceManager.reset(new ResourceManager()); resourceManager.reset(new ResourceManager());
buildingManager.reset(new BuildingManager()); buildingManager.reset(new BuildingManager());
pathfindingManager.reset(new PathfindingManager()); pathfindingManager.reset(new PathfindingManager());
armyManager.reset(new ArmyManager());
} }
AIhelper::~AIhelper() AIhelper::~AIhelper()
@ -34,6 +33,7 @@ void AIhelper::init(CPlayerSpecificInfoCallback * CB)
resourceManager->init(CB); resourceManager->init(CB);
buildingManager->init(CB); buildingManager->init(CB);
pathfindingManager->init(CB); pathfindingManager->init(CB);
armyManager->init(CB);
} }
void AIhelper::setAI(VCAI * AI) void AIhelper::setAI(VCAI * AI)
@ -41,6 +41,7 @@ void AIhelper::setAI(VCAI * AI)
resourceManager->setAI(AI); resourceManager->setAI(AI);
buildingManager->setAI(AI); buildingManager->setAI(AI);
pathfindingManager->setAI(AI); pathfindingManager->setAI(AI);
armyManager->setAI(AI);
} }
bool AIhelper::getBuildingOptions(const CGTownInstance * t) bool AIhelper::getBuildingOptions(const CGTownInstance * t)
@ -152,3 +153,33 @@ void AIhelper::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
{ {
pathfindingManager->updatePaths(heroes, 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<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);
}

View File

@ -16,6 +16,7 @@
#include "ResourceManager.h" #include "ResourceManager.h"
#include "BuildingManager.h" #include "BuildingManager.h"
#include "ArmyManager.h"
#include "Pathfinding/PathfindingManager.h" #include "Pathfinding/PathfindingManager.h"
class ResourceManager; class ResourceManager;
@ -23,7 +24,7 @@ class BuildingManager;
//indirection interface for various modules //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 class VCAI;
friend struct SetGlobalState; //mess? 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<ResourceManager> resourceManager;
std::shared_ptr<BuildingManager> buildingManager; std::shared_ptr<BuildingManager> buildingManager;
std::shared_ptr<PathfindingManager> pathfindingManager; std::shared_ptr<PathfindingManager> pathfindingManager;
std::shared_ptr<ArmyManager> armyManager;
//TODO: vector<IAbstractManager> //TODO: vector<IAbstractManager>
public: public:
AIhelper(); AIhelper();
@ -68,6 +70,13 @@ public:
return pathfindingManager->isTileAccessible(hero, tile); 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: private:
bool notifyGoalCompleted(Goals::TSubgoal goal) override; bool notifyGoalCompleted(Goals::TSubgoal goal) override;

View File

@ -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;
}

View File

@ -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;
};

View File

@ -22,6 +22,7 @@ set(VCAI_SRCS
Pathfinding/Rules/AIPreviousNodeRule.cpp Pathfinding/Rules/AIPreviousNodeRule.cpp
AIUtility.cpp AIUtility.cpp
AIhelper.cpp AIhelper.cpp
ArmyManager.cpp
ResourceManager.cpp ResourceManager.cpp
BuildingManager.cpp BuildingManager.cpp
SectorMap.cpp SectorMap.cpp
@ -78,6 +79,7 @@ set(VCAI_HEADERS
Pathfinding/Rules/AIPreviousNodeRule.h Pathfinding/Rules/AIPreviousNodeRule.h
AIUtility.h AIUtility.h
AIhelper.h AIhelper.h
ArmyManager.h
ResourceManager.h ResourceManager.h
BuildingManager.h BuildingManager.h
SectorMap.h SectorMap.h

View File

@ -62,7 +62,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
if(waysToVisit.size()) if(waysToVisit.size())
{ {
//grab army from town //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)) if(!vstd::contains(ai->townVisitsThisWeek[hero], t))
vstd::concatenate(ret, waysToVisit); vstd::concatenate(ret, waysToVisit);
@ -73,8 +73,8 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
{ {
std::vector<int> values = { std::vector<int> values = {
value, value,
(int)howManyReinforcementsCanBuy(t->getUpperArmy(), t), (int)ai->ah->howManyReinforcementsCanBuy(t->getUpperArmy(), t),
(int)howManyReinforcementsCanBuy(hero.get(), t) }; (int)ai->ah->howManyReinforcementsCanBuy(hero.get(), t) };
int val = *std::min_element(values.begin(), values.end()); int val = *std::min_element(values.begin(), values.end());
@ -113,7 +113,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
return true; return true;
else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)) else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true))
return 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; return true;
else if(ai->getGoal(h)->goalType == GATHER_ARMY) else if(ai->getGoal(h)->goalType == GATHER_ARMY)
return true; return true;
@ -148,7 +148,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
{ {
auto dwelling = dynamic_cast<const CGDwelling *>(obj); 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) if(val)
{ {

View File

@ -340,9 +340,9 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
} }
else //regular criteria else //regular criteria
{ {
if(firstHero->getFightingStrength() > secondHero->getFightingStrength() && canGetArmy(firstHero, secondHero)) if(firstHero->getFightingStrength() > secondHero->getFightingStrength() && ah->canGetArmy(firstHero, secondHero))
transferFrom2to1(firstHero, secondHero); transferFrom2to1(firstHero, secondHero);
else if(canGetArmy(secondHero, firstHero)) else if(ah->canGetArmy(secondHero, firstHero))
transferFrom2to1(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<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) void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArmedInstance * source)
{ {
const CArmedInstance * armies[] = {destinationArmy, source}; const CArmedInstance * armies[] = {destinationArmy, source};
//we calculate total strength for each creature type available in armies auto bestArmy = ah->getSortedSlots(destinationArmy, source);
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 //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(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. generate a new stack with 1 creature which is weak but fast
|| armyPtr == destinationArmy || 0 < destinationArmy->getStackCount(SlotID(i))) //can't take away last creature without split if(armyPtr == source
&& source->needsLastStack()
&& source->stacksCount() == 1
&& (!destinationArmy->hasStackAtSlot(i) || destinationArmy->getCreature(i) == targetCreature))
{ {
cb->mergeOrSwapStacks(armyPtr, destinationArmy, SlotID(j), SlotID(i)); auto weakest = ah->getWeakestCreature(bestArmy);
}
else if(weakest->creature == targetCreature)
{ {
//TODO: Improve logic by splitting weakest creature, instead of creature that becomes last stack if(1 == source->getStackCount(j))
SlotID sourceSlot = SlotID(j); break;
auto lastStackCount = armyPtr->getStackCount(sourceSlot);
if(lastStackCount > 1) //we can perform exchange if we need creature and split is possible // move all except 1 of weakest creature from source to destination
{ cb->splitStack(
SlotID destinationSlot = SlotID(i); source,
//check if there are some creatures of same type in destination army slots - add to them instead of first available empty slot if possible destinationArmy,
for(int candidateSlot = 0; candidateSlot < GameConstants::ARMY_SIZE; candidateSlot++) j,
{ destinationArmy->getSlotFor(targetCreature),
auto creatureInSlot = destinationArmy->getCreature(SlotID(candidateSlot)); destinationArmy->getStackCount(i) + source->getStackCount(j) - 1);
if(creatureInSlot && (creatureInSlot->idNumber == armyPtr->getCreature(SlotID(j))->idNumber))
{ break;
destinationSlot = SlotID(candidateSlot); }
break; else
} {
} // Source last stack is not weakest. Move 1 of weakest creature from destination to source
//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 cb->splitStack(
auto destinationSlotCreatureCount = destinationArmy->getStackCount(destinationSlot); destinationArmy,
cb->splitStack(armyPtr, destinationArmy, sourceSlot, destinationSlot, lastStackCount + destinationSlotCreatureCount - 1); 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) if(cb->getVisitableObjs(h->visitablePos()).size() > 1)
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate 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(); const CGHeroInstance * hptr = h.get();
auto r1 = howManyReinforcementsCanGet(hptr, lhs), auto r1 = ah->howManyReinforcementsCanGet(hptr, lhs),
r2 = howManyReinforcementsCanGet(hptr, rhs); r2 = ah->howManyReinforcementsCanGet(hptr, rhs);
if (r1 != r2) if (r1 != r2)
return r1 < r2; return r1 < r2;
else else
return howManyReinforcementsCanBuy(hptr, lhs) < howManyReinforcementsCanBuy(hptr, rhs); return ah->howManyReinforcementsCanBuy(hptr, lhs) < ah->howManyReinforcementsCanBuy(hptr, rhs);
}; };
std::vector<const CGTownInstance *> townsReachable; std::vector<const CGTownInstance *> townsReachable;
@ -1533,11 +1459,11 @@ void VCAI::wander(HeroPtr h)
else if(cb->getResourceAmount(Res::GOLD) >= GameConstants::HERO_GOLD_COST) else if(cb->getResourceAmount(Res::GOLD) >= GameConstants::HERO_GOLD_COST)
{ {
std::vector<const CGTownInstance *> towns = cb->getTownsInfo(); 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()) for(const CGHeroInstance * h : cb->getHeroesInfo())
{ {
if(!t->getArmyStrength() || howManyReinforcementsCanGet(h, t)) if(!t->getArmyStrength() || ah->howManyReinforcementsCanGet(h, t))
return true; return true;
} }
return false; return false;

View File

@ -216,7 +216,6 @@ public:
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const; bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const;
//void recruitCreatures(const CGTownInstance * t); //void recruitCreatures(const CGTownInstance * t);
void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter); 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 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 pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);
void moveCreaturesToHero(const CGTownInstance * t); void moveCreaturesToHero(const CGTownInstance * t);