1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Merge pull request #477 from vcmi/VCAI_Rework

VCAI main loop rework + lots of discovered bugs and caveats fixed
This commit is contained in:
DjWarmonger 2018-08-08 22:19:17 +02:00 committed by GitHub
commit a0909468de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 556 additions and 358 deletions

View File

@ -357,6 +357,26 @@ bool isSafeToVisit(HeroPtr h, crint3 tile)
return true; //there's no danger
}
bool isObjectRemovable(const CGObjectInstance * obj)
{
//FIXME: move logic to object property!
switch (obj->ID)
{
case Obj::MONSTER:
case Obj::RESOURCE:
case Obj::CAMPFIRE:
case Obj::TREASURE_CHEST:
case Obj::ARTIFACT:
case Obj::BORDERGUARD:
return true;
break;
default:
return false;
break;
}
}
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
{
// TODO: Such information should be provided by pathfinder
@ -422,7 +442,7 @@ bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should
if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE)
return false;
auto gate = dynamic_cast<const CGKeys *>(cb->getTile(tileToHit)->topVisitableObj());
return !gate->wasMyColorVisited(ai->playerID);
return !gate->passableFor(ai->playerID);
}
bool isBlockVisitObj(const int3 & pos)
{

View File

@ -167,6 +167,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj);
ui64 evaluateDanger(const CGObjectInstance * obj);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
bool isSafeToVisit(HeroPtr h, crint3 tile);
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
bool compareMovement(HeroPtr lhs, HeroPtr rhs);
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);

View File

@ -116,13 +116,13 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
return desc;
}
//TODO: virtualize if code gets complex?
bool Goals::AbstractGoal::operator==(AbstractGoal & g)
{
/*this operator checks if goals are EQUIVALENT, ie. if they represent same objective
it does not not check isAbstract or isElementar, as this is up to VCAI decomposition logic
*/
if(g.goalType != goalType)
return false;
if(g.isElementar != isElementar) //elementar goals fulfill long term non-elementar goals (VisitTile)
return false;
switch(goalType)
{
@ -169,14 +169,62 @@ bool Goals::AbstractGoal::operator==(AbstractGoal & g)
case TRADE: //TODO
return (resID == g.resID); //every hero may collect resources
break;
case BUILD: //abstract build is indentical, TODO: consider building anything in town
return true;
break;
case GATHER_TROOPS:
case ISSUE_COMMAND:
case BUILD: //TODO: should be decomposed to build specific structures
default:
return false;
}
}
bool Goals::AbstractGoal::operator<(AbstractGoal & g) //for std::unique
{
//TODO: make sure it gets goals consistent with == operator
if (goalType < g.goalType)
return true;
if (goalType > g.goalType)
return false;
if (hero < g.hero)
return true;
if (hero > g.hero)
return false;
if (tile < g.tile)
return true;
if (g.tile < tile)
return false;
if (objid < g.objid)
return true;
if (objid > g.objid)
return false;
if (town < g.town)
return true;
if (town > g.town)
return false;
if (value < g.value)
return true;
if (value > g.value)
return false;
if (priority < g.priority)
return true;
if (priority > g.priority)
return false;
if (resID < g.resID)
return true;
if (resID > g.resID)
return false;
if (bid < g.bid)
return true;
if (bid > g.bid)
return false;
if (aid < g.aid)
return true;
if (aid > g.aid)
return false;
return false;
}
//TODO: find out why the following are not generated automatically on MVS?
namespace Goals
@ -208,8 +256,15 @@ namespace Goals
return *get() == *rhs.get(); //comparison for Goals is overloaded, so they don't need to be identical to match
}
bool BuyArmy::operator==(BuyArmy & g)
bool TSubgoal::operator<(const TSubgoal & rhs) const
{
return get() < rhs.get(); //compae by value
}
bool BuyArmy::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
//if (hero && hero != g.hero)
// return false;
return town == g.town;
@ -237,8 +292,10 @@ TSubgoal Trade::whatToDoToAchieve()
{
return iAmElementar();
}
bool Trade::operator==(CollectRes & g)
bool Trade::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
if (g.resID == resID)
if (g.value == value) //TODO: not sure if that logic is consitent
return true;
@ -480,6 +537,13 @@ TSubgoal GetObj::whatToDoToAchieve()
return sptr(Goals::ClearWayTo(pos).sethero(hero));
}
bool Goals::GetObj::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
return g.objid == objid;
}
bool GetObj::fulfillsMe(TSubgoal goal)
{
if(goal->goalType == Goals::VISIT_TILE) //visiting tile visits object at same time
@ -520,6 +584,13 @@ TSubgoal VisitHero::whatToDoToAchieve()
return sptr(Goals::Invalid());
}
bool Goals::VisitHero::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
return g.hero == hero && g.objid == objid;
}
bool VisitHero::fulfillsMe(TSubgoal goal)
{
//TODO: VisitObj shoudl not be used for heroes, but...
@ -556,6 +627,13 @@ TSubgoal ClearWayTo::whatToDoToAchieve()
return (fh->chooseSolution(getAllPossibleSubgoals()));
}
bool Goals::ClearWayTo::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
return g.goalType == goalType && g.tile == tile;
}
bool Goals::ClearWayTo::fulfillsMe(TSubgoal goal)
{
if (goal->goalType == Goals::VISIT_TILE)
@ -595,6 +673,7 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
{
//FIXME: this way we'll not visit gate and activate quest :?
ret.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(tileToHit)->visitableObjects.back()->subID)));
return ret; //only option
}
auto topObj = cb->getTopObj(tileToHit);
@ -621,13 +700,16 @@ TGoalVec ClearWayTo::getAllPossibleSubgoals()
}
else
{
//TODO: we should be able to return apriopriate quest here (VCAI::striveToQuest)
//TODO: we should be able to return apriopriate quest here
//ret.push_back(ai->questToGoal());
//however, visiting obj for firts time will give us quest
logAi->debug("Quest guard blocks the way to %s", tile.toString());
continue; //do not access quets guard if we can't complete the quest
}
return ret; //try complete quest as the only option
}
}
if(isSafeToVisit(h, tileToHit)) //this makes sense only if tile is guarded, but there i no quest object
if(isSafeToVisit(h, tileToHit)) //this makes sense only if tile is guarded, but there is no quest object
{
ret.push_back(sptr(Goals::VisitTile(tileToHit).sethero(h)));
}
@ -809,8 +891,10 @@ TSubgoal RecruitHero::whatToDoToAchieve()
return ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res
}
bool Goals::RecruitHero::operator==(RecruitHero & g)
bool Goals::RecruitHero::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
//TODO: check town and hero
return true; //for now, recruiting any hero will do
}
@ -842,6 +926,13 @@ TSubgoal VisitTile::whatToDoToAchieve()
return ret;
}
bool Goals::VisitTile::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
return g.goalType == goalType && g.tile == tile;
}
TGoalVec VisitTile::getAllPossibleSubgoals()
{
assert(cb->isInTheMap(tile));
@ -896,6 +987,13 @@ TSubgoal DigAtTile::whatToDoToAchieve()
return sptr(Goals::VisitTile(tile));
}
bool Goals::DigAtTile::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
return g.goalType == goalType && g.tile == tile;
}
TSubgoal BuildThis::whatToDoToAchieve()
{
auto b = BuildingID(bid);
@ -920,8 +1018,13 @@ TSubgoal BuildThis::whatToDoToAchieve()
}
if (town) //we have specific town to build this
{
auto res = town->town->buildings.at(BuildingID(bid))->resources;
return ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
if (cb->canBuildStructure(town, b) != EBuildingState::ALLOWED) //FIXME: decompose further? kind of mess if we're here
throw cannotFulfillGoalException("Not possible to build");
else
{
auto res = town->town->buildings.at(BuildingID(bid))->resources;
return ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
}
}
else
throw cannotFulfillGoalException("Cannot find town to build this");
@ -1118,8 +1221,10 @@ bool CollectRes::fulfillsMe(TSubgoal goal)
return false;
}
bool Goals::CollectRes::operator==(CollectRes & g)
bool Goals::CollectRes::operator==(AbstractGoal & g)
{
if (g.goalType != goalType)
return false;
if (g.resID == resID)
if (g.value == value) //TODO: not sure if that logic is consitent
return true;
@ -1305,9 +1410,40 @@ TGoalVec Conquer::getAllPossibleSubgoals()
return ret;
}
TGoalVec Goals::Build::getAllPossibleSubgoals()
{
TGoalVec ret;
for (const CGTownInstance * t : cb->getTownsInfo())
{
//start fresh with every town
ah->getBuildingOptions(t);
auto ib = ah->immediateBuilding();
if (ib.is_initialized())
{
ret.push_back(sptr(Goals::BuildThis(ib.get().bid, t).setpriority(2))); //prioritize buildings we can build quick
}
else //try build later
{
auto eb = ah->expensiveBuilding();
if (eb.is_initialized())
{
auto pb = eb.get(); //gather resources for any we can't afford
auto goal = ah->whatToDo(pb.price, sptr(Goals::BuildThis(pb.bid, t).setpriority(0.5)));
ret.push_back(goal);
}
}
}
if (ret.empty())
throw cannotFulfillGoalException("BUILD has been realized as much as possible."); //who catches it and what for?
else
return ret;
}
TSubgoal Build::whatToDoToAchieve()
{
return iAmElementar();
return fh->chooseSolution(getAllPossibleSubgoals());
}
bool Goals::Build::fulfillsMe(TSubgoal goal)
@ -1386,7 +1522,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
return true;
else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true))
return true;
else if(!ai->canGetArmy(heroDummy.h, h))
else if(!ai->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue
return true;
else if(ai->getGoal(h)->goalType == Goals::GATHER_ARMY)
return true;
@ -1396,9 +1532,11 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
for(auto h : otherHeroes)
{
// Go to the other hero if we are faster
ret.push_back(sptr(Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
if (!vstd::contains(ai->visitedHeroes[hero], h)) //visit only once each turn //FIXME: this is only bug workaround
ret.push_back(sptr(Goals::VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero)));
// Let the other hero come to us
ret.push_back(sptr(Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
if (!vstd::contains(ai->visitedHeroes[h], hero))
ret.push_back(sptr(Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h)));
}
std::vector<const CGObjectInstance *> objs;
@ -1420,7 +1558,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
{
auto creature = VLC->creh->creatures[creatureID];
if(ah->freeResources().canAfford(creature->cost))
objs.push_back(obj);
objs.push_back(obj); //TODO: reserve resources?
}
}
}
@ -1456,10 +1594,10 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
if(ret.empty())
{
if(hero == ai->primaryHero() || value >= 1.1f) // FIXME: check PR388
if(hero == ai->primaryHero())
ret.push_back(sptr(Goals::Explore()));
else //workaround to break loop - seemingly there are no ways to explore left
throw goalFulfilledException(sptr(Goals::GatherArmy(0).sethero(hero)));
else
throw cannotFulfillGoalException("No ways to gather army");
}
return ret;

View File

@ -28,8 +28,10 @@ class DLL_EXPORT TSubgoal : public std::shared_ptr<Goals::AbstractGoal>
{
public:
bool operator==(const TSubgoal & rhs) const;
bool operator<(const TSubgoal & rhs) const;
//TODO: serialize?
};
typedef std::vector<TSubgoal> TGoalVec;
enum EGoals
@ -136,6 +138,7 @@ public:
virtual float accept(FuzzyHelper * f);
virtual bool operator==(AbstractGoal & g);
bool operator<(AbstractGoal & g); //final
virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check
{
return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately
@ -273,10 +276,7 @@ public:
{
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
};
@ -331,9 +331,9 @@ public:
{
town = Town; //where to buy this army
value = val; //expressed in AI unit strength
priority = 2;//TODO: evaluate?
priority = 3;//TODO: evaluate?
}
bool operator==(BuyArmy & g);
bool operator==(AbstractGoal & g) override;
bool fulfillsMe(TSubgoal goal) override;
TSubgoal whatToDoToAchieve() override;
@ -368,7 +368,7 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool operator==(RecruitHero & g);
bool operator==(AbstractGoal & g) override;
};
class DLL_EXPORT BuildThis : public CGoal<BuildThis>
@ -416,7 +416,7 @@ public:
TSubgoal whatToDoToAchieve() override;
TSubgoal whatToDoToTrade();
bool fulfillsMe(TSubgoal goal) override; //TODO: Trade
bool operator==(CollectRes & g);
bool operator==(AbstractGoal & g) override;
};
class DLL_EXPORT Trade : public CGoal<Trade>
@ -435,7 +435,7 @@ public:
priority = 10; //do it immediately
}
TSubgoal whatToDoToAchieve() override;
bool operator==(CollectRes & g);
bool operator==(AbstractGoal & g) override;
};
class DLL_EXPORT GatherTroops : public CGoal<GatherTroops>
@ -477,10 +477,7 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool operator==(GetObj & g)
{
return g.objid == objid;
}
bool operator==(AbstractGoal & g) override;
bool fulfillsMe(TSubgoal goal) override;
std::string completeMessage() const override;
};
@ -530,10 +527,7 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool operator==(VisitHero & g)
{
return g.goalType == goalType && g.objid == objid;
}
bool operator==(AbstractGoal & g) override;
bool fulfillsMe(TSubgoal goal) override;
std::string completeMessage() const override;
};
@ -572,10 +566,7 @@ public:
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool operator==(VisitTile & g)
{
return g.goalType == goalType && g.tile == tile;
}
bool operator==(AbstractGoal & g) override;
std::string completeMessage() const override;
};
@ -601,10 +592,7 @@ public:
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool operator==(ClearWayTo & g)
{
return g.goalType == goalType && g.tile == tile;
}
bool operator==(AbstractGoal & g) override;
bool fulfillsMe(TSubgoal goal) override;
};
@ -627,10 +615,7 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool operator==(DigAtTile & g)
{
return g.goalType == goalType && g.tile == tile;
}
bool operator==(AbstractGoal & g) override;
};
class DLL_EXPORT CIssueCommand : public CGoal<CIssueCommand>

View File

@ -217,8 +217,8 @@ bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
});
if (it != queue.end()) //removed at least one
{
logAi->debug("Removing goal %s from ResourceManager.", it->goal->name());
queue.erase(queue.s_handle_from_iterator(it));
logAi->debug("Removed goal %s from ResourceManager.", it->goal->name());
removedGoal = true;
}
else //found nothing more to remove
@ -288,18 +288,6 @@ TResources ResourceManager::reservedResources() const
TResources ResourceManager::freeResources() const
{
TResources myRes = cb->getResourceAmount();
auto towns = cb->getTownsInfo();
if (towns.size()) //we don't save for Capitol if there are no towns
{
if (std::none_of(towns.begin(), towns.end(), [](const CGTownInstance * x) -> bool
{
return x->builtBuildings.find(BuildingID::CAPITOL) != x->builtBuildings.end();
}))
{
myRes[Res::GOLD] -= GOLD_RESERVE;
//what if capitol is blocked from building in all possessed towns (set in map editor)?
}
}
myRes -= reservedResources(); //substract the value of reserved goals
for (auto & val : myRes)

View File

@ -329,7 +329,7 @@ int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
}
}
else
else //tiles are in same sector
{
return findFirstVisitableTile(h, dst);
}

View File

@ -270,6 +270,10 @@ void VCAI::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * vi
unreserveObject(visitor, visitedObj);
completeGoal(sptr(Goals::GetObj(visitedObj->id.getNum()).sethero(visitor))); //we don't need to visit it anymore
//TODO: what if we visited one-time visitable object that was reserved by another hero (shouldn't, but..)
if (visitedObj->ID == Obj::HERO)
{
visitedHeroes[visitor].insert(HeroPtr(dynamic_cast<const CGHeroInstance *>(visitedObj)));
}
}
status.heroVisit(visitedObj, start);
@ -780,125 +784,33 @@ void VCAI::makeTurn()
}
}
markHeroAbleToExplore(primaryHero());
makeTurnInternal();
}
/*This method defines core of AI behavior. It is not consistent system, just "a bit of everything for everyone" done in some example order.
It is not supposed to work this way in final version of VCAI. It consists of few actions/loops done in particular order, hard parts are explained below with focus on explaining hero management logic*/
void VCAI::makeTurnInternal()
{
//it looks messy here, but it's better to have armed heroes before attempting realizing goals
for(const CGTownInstance * t : cb->getTownsInfo())
moveCreaturesToHero(t);
visitedHeroes.clear();
try
{
/*Below loop causes heroes with objects locked to them to keep trying to realize previous goal. By design when object is locked another heroes do not attempt to visit it.
Object lock happens on turn when some hero gets assigned visit tile with appropiate map object. So basically all heroes that had VisitTile goal with object assigned and not completed
will be in the loop. Sometimes heroes get assigned more important objectives, but still keep reserved objects for later. There is a problem with that - they have
reserved objects in the list, so they fall to this loop at start of turn and visiting object isn't delayed. Comments for that function are supposed to help newer VCAI maintainers*/
//it looks messy here, but it's better to have armed heroes before attempting realizing goals
for (const CGTownInstance * t : cb->getTownsInfo())
moveCreaturesToHero(t);
//Pick objects reserved in previous turn - we expect only nearby objects there
auto reservedHeroesCopy = reservedHeroesMap; //work on copy => the map may be changed while iterating (eg because hero died when attempting a goal)
for(auto hero : reservedHeroesCopy)
{
if(reservedHeroesMap.count(hero.first))
continue; //hero might have been removed while we were in this loop
if(!hero.first.validAndSet())
{
logAi->error("Hero %s present on reserved map. Shouldn't be.", hero.first.name);
continue;
}
std::vector<const CGObjectInstance *> vec(hero.second.begin(), hero.second.end());
boost::sort(vec, CDistanceSorter(hero.first.get()));
for(auto obj : vec)
{
if(!obj || !cb->getObj(obj->id))
{
logAi->error("Error: there is wrong object on list for hero %s", hero.first->name);
continue;
}
striveToGoal(sptr(Goals::VisitTile(obj->visitablePos()).sethero(hero.first)));
}
}
//now try to win
/*below line performs goal decomposition, result of the function is ONE goal for ONE hero to realize.*/
striveToGoal(sptr(Goals::Win()));
//TODO: add ResourceManager goals to the pool and process them all at once
if (ah->hasTasksLeft())
striveToGoal(ah->whatToDo());
/*Explanation of below loop: At the time of writing this - goals get decomposited either to GatherArmy or Visit Tile.
Visit tile that is about visiting object gets processed at beginning of MakeTurnInternal without re-evaluation.
Rest of goals that got started via striveToGoal(sptr(Goals::Win())); in previous turns and not finished get continued here.
Also they are subject for re-evaluation to see if there is better goal to start (still talking only about heroes that got goals started by via striveToGoal(sptr(Goals::Win())); in previous turns.*/
//finally, continue our abstract long-term goals
int oldMovement = 0;
int newMovement = 0;
while(true)
{
oldMovement = newMovement; //remember old value
newMovement = 0;
std::vector<std::pair<HeroPtr, Goals::TSubgoal>> safeCopy;
for(auto mission : lockedHeroes)
{
fh->setPriority(mission.second); //re-evaluate
if(canAct(mission.first))
{
newMovement += mission.first->movement;
safeCopy.push_back(mission);
}
}
if(newMovement == oldMovement) //means our heroes didn't move or didn't re-assign their goals
{
logAi->warn("Our heroes don't move anymore, exhaustive decomposition failed");
break;
}
if(safeCopy.empty())
{
break; //all heroes exhausted their locked goals
}
else
{
typedef std::pair<HeroPtr, Goals::TSubgoal> TItrType;
auto lockedHeroesSorter = [](TItrType m1, TItrType m2) -> bool
{
return m1.second->priority < m2.second->priority;
};
striveToGoal(boost::max_element(safeCopy, lockedHeroesSorter)->second);
}
}
auto quests = myCb->getMyQuests();
for(auto quest : quests)
{
striveToQuest(quest);
}
//TODO: striveToGoal
striveToGoal(sptr(Goals::Build())); //TODO: smarter building management
mainLoop();
/*Below function is also responsible for hero movement via internal wander function. By design it is separate logic for heroes that have nothing to do.
Heroes that were not picked by striveToGoal(sptr(Goals::Win())); recently (so they do not have new goals and cannot continue/reevaluate previously locked goals) will do logic in wander().*/
performTypicalActions();
//for debug purpose
for(auto h : cb->getHeroesInfo())
for (auto h : cb->getHeroesInfo())
{
if(h->movement)
if (h->movement)
logAi->warn("Hero %s has %d MP left", h->name, h->movement);
}
}
catch(boost::thread_interrupted & e)
catch (boost::thread_interrupted & e)
{
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());
}
@ -906,6 +818,168 @@ void VCAI::makeTurnInternal()
endTurn();
}
void VCAI::mainLoop()
{
std::vector<Goals::TSubgoal> elementarGoals; //no duplicates allowed (operator ==)
basicGoals.clear();
//get all potential and saved goals
//TODO: not lose
basicGoals.push_back(sptr(Goals::Win()));
for (auto goalPair : lockedHeroes)
{
fh->setPriority(goalPair.second); //re-evaluate, as heroes moved in the meantime
basicGoals.push_back(goalPair.second);
}
if (ah->hasTasksLeft())
basicGoals.push_back(ah->whatToDo());
for (auto quest : myCb->getMyQuests())
{
basicGoals.push_back(questToGoal(quest));
}
basicGoals.push_back(sptr(Goals::Build()));
invalidPathHeroes.clear();
while (basicGoals.size())
{
vstd::removeDuplicates(basicGoals); //TODO: container which does this automagically without has would be nice
goalsToAdd.clear();
goalsToRemove.clear();
elementarGoals.clear();
ultimateGoalsFromBasic.clear();
for (auto basicGoal : basicGoals)
{
auto goalToDecompose = basicGoal;
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
int maxAbstractGoals = 10;
while (!elementarGoal->isElementar && maxAbstractGoals)
{
try
{
elementarGoal = decomposeGoal(goalToDecompose);
}
catch (goalFulfilledException & e)
{
//it is impossible to continue some goals (like exploration, for example)
//complete abstract goal for now, but maybe main goal finds another path
logAi->debug("Goal %s decomposition failed: goal was completed as much as possible", e.goal->name());
completeGoal(e.goal); //put in goalsToRemove
break;
}
catch (std::exception & e) //decomposition failed, which means we can't decompose entire tree
{
goalsToRemove.push_back(basicGoal);
logAi->debug("Goal %s decomposition failed: %s", basicGoal->name(), e.what());
break;
}
if (elementarGoal->isAbstract) //we can decompose it further
{
goalsToAdd.push_back(elementarGoal);
//decompose further now - this is necesssary if we can't add over 10 goals in the pool
goalToDecompose = elementarGoal;
//there is a risk of infinite abstract goal loop, though it indicates failed logic
maxAbstractGoals--;
}
else if (elementarGoal->isElementar) //should be
{
logAi->debug("Found elementar goal %s", elementarGoal->name());
elementarGoals.push_back(elementarGoal);
ultimateGoalsFromBasic[elementarGoal].push_back(goalToDecompose); //TODO: how about indirect basicGoal?
break;
}
else //should never be here
throw cannotFulfillGoalException("Goal %s is neither abstract nor elementar!" + basicGoal->name());
}
}
//now choose one elementar goal to realize
Goals::TGoalVec possibleGoals(elementarGoals.begin(), elementarGoals.end()); //copy to vector
Goals::TSubgoal goalToRealize = sptr(Goals::Invalid());
while (possibleGoals.size())
{
//allow assign goals to heroes with 0 movement, but don't realize them
//maybe there are beter ones left
auto bestGoal = fh->chooseSolution(possibleGoals);
if (bestGoal->hero) //lock this hero to fulfill goal
{
setGoal(bestGoal->hero, bestGoal);
if (!bestGoal->hero->movement || vstd::contains(invalidPathHeroes, bestGoal->hero))
{
if (!vstd::erase_if_present(possibleGoals, bestGoal))
{
logAi->error("erase_if_preset failed? Something very wrong!");
break;
}
continue; //chose next from the list
}
}
goalToRealize = bestGoal; //we found our goal to execute
break;
}
//realize best goal
if (!goalToRealize->invalid())
{
logAi->debug("Trying to realize %s (value %2.3f)", goalToRealize->name(), goalToRealize->priority);
try
{
boost::this_thread::interruption_point();
goalToRealize->accept(this); //visitor pattern
boost::this_thread::interruption_point();
}
catch (boost::thread_interrupted & e)
{
logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread
}
catch (goalFulfilledException & e)
{
//the sub-goal was completed successfully
completeGoal(e.goal);
//local goal was also completed?
completeGoal(goalToRealize);
}
catch (std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s, I will stop.", goalToRealize->name());
logAi->debug("The error message was: %s", e.what());
//erase base goal if we failed to execute decomposed goal
for (auto basicGoal : ultimateGoalsFromBasic[goalToRealize])
goalsToRemove.push_back(basicGoal);
//we failed to realize best goal, but maybe others are still possible?
}
//remove goals we couldn't decompose
for (auto goal : goalsToRemove)
vstd::erase_if_present(basicGoals, goal);
//add abstract goals
boost::sort(goalsToAdd, [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
{
return lhs->priority > rhs->priority; //highest priority at the beginning
});
//max number of goals = 10
int i = 0;
while (basicGoals.size() < 10 && goalsToAdd.size() > i)
{
if (!vstd::contains(basicGoals, goalsToAdd[i])) //don't add duplicates
basicGoals.push_back(goalsToAdd[i]);
i++;
}
}
else //no elementar goals possible
{
logAi->debug("Goal decomposition exhausted");
break;
}
}
}
bool VCAI::goVisitObj(const CGObjectInstance * obj, HeroPtr h)
{
int3 dst = obj->visitablePos();
@ -1349,7 +1423,7 @@ void VCAI::wander(HeroPtr h)
const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements);
logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->name, t->name, t->visitablePos().toString());
int3 pos1 = h->pos;
striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h)));
striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop
//if out hero is stuck, we may need to request another hero to clear the way we see
if(pos1 == h->pos && h == primaryHero()) //hero can't move
@ -1435,8 +1509,22 @@ void VCAI::evaluateGoal(HeroPtr h)
void VCAI::completeGoal(Goals::TSubgoal goal)
{
if (goal->goalType == Goals::WIN) //we can never complete this goal - unless we already won
return;
logAi->trace("Completing goal: %s", goal->name());
//notify Managers
ah->notifyGoalCompleted(goal);
//notify mainLoop()
goalsToRemove.push_back(goal); //will be removed from mainLoop() goals
for (auto basicGoal : basicGoals) //we could luckily fulfill any of our goals
{
if (basicGoal->fulfillsMe(goal))
goalsToRemove.push_back(basicGoal);
}
//unreserve heroes
if(const CGHeroInstance * h = goal->hero.get(true))
{
auto it = lockedHeroes.find(h);
@ -1872,6 +1960,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
if(startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
{
vstd::erase_if_present(lockedHeroes, h); //hero seemingly is confused or has only 95mp which is not enough to move
invalidPathHeroes.insert(h);
throw cannotFulfillGoalException("Invalid path found!");
}
evaluateGoal(h); //new hero position means new game situation
@ -1996,8 +2085,9 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, toGet, g.resID, obj->getObjectName());
accquiredResources += toGet; //FIXME: this is incorrect, always equal to 1
}
if (accquiredResources >= g.value) //we traded all we needed
throw goalFulfilledException(sptr(g));
//if (accquiredResources >= g.value)
if (ah->freeResources()[g.resID] >= g.value)
throw goalFulfilledException(sptr(g)); //we traded all we needed
}
throw cannotFulfillGoalException("I cannot get needed resources by trade!");
@ -2013,46 +2103,6 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
}
}
void VCAI::tryRealize(Goals::Build & g)
{
bool didWeBuildSomething = false;
for(const CGTownInstance * t : cb->getTownsInfo())
{
logAi->debug("Looking into %s", t->name);
//start fresh with every town
ah->getBuildingOptions(t);
auto ib = ah->immediateBuilding();
if (ib.is_initialized())
{
buildStructure(t, ib.get().bid); //do it right now
didWeBuildSomething = true;
}
else
{
auto eb = ah->expensiveBuilding();
if (eb.is_initialized())
{
auto pb = eb.get(); //gather resources for any we can't afford
auto goal = ah->whatToDo(pb.price, sptr(Goals::BuildThis(pb.bid, t)));
if (goal->goalType == Goals::BUILD_STRUCTURE)
{
logAi->error("We were supposed to NOT afford any building");
buildStructure(t, pb.bid); //do it right now
didWeBuildSomething = true;
}
else
{
//TODO: right now we do that for every town in order. Consider comparison of all potential goals.
striveToGoal(goal); //gather resources, or something else?
}
}
}
}
if (!didWeBuildSomething)
throw cannotFulfillGoalException("BUILD has been realized as much as possible."); //who catches it and what for?
}
void VCAI::tryRealize(Goals::BuyArmy & g)
{
auto t = g.town;
@ -2181,171 +2231,172 @@ void VCAI::endTurn()
logGlobal->info("Player %d (%s) ended turn", playerID, playerID.getStr());
}
void VCAI::striveToGoal(Goals::TSubgoal ultimateGoal)
void VCAI::striveToGoal(Goals::TSubgoal basicGoal)
{
if(ultimateGoal->invalid())
return;
//TODO: this function is deprecated and should be dropped altogether
//we are looking for abstract goals
auto abstractGoal = striveToGoalInternal(ultimateGoal, false);
auto goalToDecompose = basicGoal;
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
int maxAbstractGoals = 10;
while (!elementarGoal->isElementar && maxAbstractGoals)
{
try
{
elementarGoal = decomposeGoal(goalToDecompose);
}
catch (goalFulfilledException & e)
{
//it is impossible to continue some goals (like exploration, for example)
completeGoal(e.goal); //put in goalsToRemove
logAi->debug("Goal %s decomposition failed: goal was completed as much as possible", e.goal->name());
return;
}
catch (std::exception & e)
{
goalsToRemove.push_back(basicGoal);
logAi->debug("Goal %s decomposition failed: %s", basicGoal->name(), e.what());
return;
}
if (elementarGoal->isAbstract) //we can decompose it further
{
goalsToAdd.push_back(elementarGoal);
//decompose further now - this is necesssary if we can't add over 10 goals in the pool
goalToDecompose = elementarGoal;
//there is a risk of infinite abstract goal loop, though it indicates failed logic
maxAbstractGoals--;
}
else if (elementarGoal->isElementar) //should be
{
logAi->debug("Found elementar goal %s", elementarGoal->name());
ultimateGoalsFromBasic[elementarGoal].push_back(goalToDecompose); //TODO: how about indirect basicGoal?
break;
}
else //should never be here
throw cannotFulfillGoalException("Goal %s is neither abstract nor elementar!" + basicGoal->name());
}
if(abstractGoal->invalid())
return;
//realize best goal
if (!elementarGoal->invalid())
{
logAi->debug("Trying to realize %s (value %2.3f)", elementarGoal->name(), elementarGoal->priority);
//we received abstract goal, need to find concrete goals
striveToGoalInternal(abstractGoal, true);
try
{
boost::this_thread::interruption_point();
elementarGoal->accept(this); //visitor pattern
boost::this_thread::interruption_point();
}
catch (boost::thread_interrupted & e)
{
logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread
}
catch (goalFulfilledException & e)
{
//the sub-goal was completed successfully
completeGoal(e.goal);
//local goal was also completed
completeGoal(elementarGoal);
}
catch (std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s, I will stop.", elementarGoal->name());
logAi->debug("The error message was: %s", e.what());
//TODO: save abstract goals not related to hero
//erase base goal if we failed to execute decomposed goal
for (auto basicGoal : ultimateGoalsFromBasic[elementarGoal])
goalsToRemove.push_back(basicGoal);
}
}
}
Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool onlyAbstract)
Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
{
const int searchDepth = 30;
const int searchDepth2 = searchDepth - 2;
Goals::TSubgoal abstractGoal = sptr(Goals::Invalid());
while(1)
Goals::TSubgoal goal = ultimateGoal;
logAi->debug("Decomposing goal %s", ultimateGoal->name());
int maxGoals = searchDepth; //preventing deadlock for mutually dependent goals
while (maxGoals)
{
Goals::TSubgoal goal = ultimateGoal;
logAi->debug("Striving to goal of type %s", ultimateGoal->name());
int maxGoals = searchDepth; //preventing deadlock for mutually dependent goals
while(!goal->isElementar && maxGoals && (onlyAbstract || !goal->isAbstract))
{
logAi->debug("Considering goal %s", goal->name());
try
{
boost::this_thread::interruption_point();
goal = goal->whatToDoToAchieve();
--maxGoals;
if(goal == ultimateGoal) //compare objects by value
throw cannotFulfillGoalException((boost::format("Goal dependency loop detected for %s!") % ultimateGoal->name()).str());
}
catch(goalFulfilledException & e)
{
//it is impossible to continue some goals (like exploration, for example)
completeGoal(goal);
logAi->debug("Goal %s decomposition failed: goal was completed as much as possible", goal->name());
return sptr(Goals::Invalid());
}
catch(std::exception & e)
{
logAi->debug("Goal %s decomposition failed: %s", goal->name(), e.what());
return sptr(Goals::Invalid());
}
}
try
{
boost::this_thread::interruption_point();
if(!maxGoals) //we counted down to 0 and found no solution
{
if(ultimateGoal->hero) // we seemingly don't know what to do with hero, free him
vstd::erase_if_present(lockedHeroes, ultimateGoal->hero);
throw (std::runtime_error("Too many subgoals, don't know what to do"));
}
else //we found elementar goal and can proceed
{
if(goal->hero) //lock this hero to fulfill ultimate goal
{
setGoal(goal->hero, goal);
}
}
if(goal->isAbstract)
{
abstractGoal = goal; //allow only one abstract goal per call
logAi->debug("Choosing abstract goal %s", goal->name());
break;
}
else //try realize
{
logAi->debug("Trying to realize %s (value %2.3f)", goal->name(), goal->priority);
goal->accept(this);
}
boost::this_thread::interruption_point();
}
catch(boost::thread_interrupted & e)
{
logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread
}
catch(goalFulfilledException & e)
{
//the sub-goal was completed successfully
completeGoal(e.goal);
//local goal was also completed... TODO: or not?
completeGoal(goal);
//completed goal was main goal //TODO: find better condition
if(ultimateGoal->fulfillsMe(goal) || maxGoals > searchDepth2)
return sptr(Goals::Invalid());
}
catch(std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s (greater goal type was %s), I will stop.", goal->name(), ultimateGoal->name());
logAi->debug("The error message was: %s", e.what());
break;
}
boost::this_thread::interruption_point();
goal = goal->whatToDoToAchieve(); //may throw if decomposition fails
--maxGoals;
if (goal == ultimateGoal) //compare objects by value
if (goal->isElementar == ultimateGoal->isElementar)
throw cannotFulfillGoalException((boost::format("Goal dependency loop detected for %s!")
% ultimateGoal->name()).str());
if (goal->isAbstract || goal->isElementar)
return goal;
else
logAi->debug("Considering: %s", goal->name());
}
if (maxGoals <= 0)
{
throw cannotFulfillGoalException("Too many subgoals, don't know what to do");
}
else
{
return goal;
}
return abstractGoal;
}
void VCAI::striveToQuest(const QuestInfo & q)
Goals::TSubgoal VCAI::questToGoal(const QuestInfo & q)
{
if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
if (q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
{
MetaString ms;
q.quest->getRolloverText(ms, false);
logAi->debug("Trying to realize quest: %s", ms.toString());
auto heroes = cb->getHeroesInfo();
auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
switch(q.quest->missionType)
switch (q.quest->missionType)
{
case CQuest::MISSION_ART:
{
for(auto hero : heroes) //TODO: remove duplicated code?
for (auto hero : heroes)
{
if(q.quest->checkQuest(hero))
if (q.quest->checkQuest(hero))
{
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)));
return;
return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero));
}
}
for(auto art : q.quest->m5arts)
for (auto art : q.quest->m5arts)
{
striveToGoal(sptr(Goals::GetArtOfType(art))); //TODO: transport?
return sptr(Goals::GetArtOfType(art)); //TODO: transport?
}
break;
}
case CQuest::MISSION_HERO:
{
//striveToGoal (CGoal(RECRUIT_HERO));
for(auto hero : heroes)
for (auto hero : heroes)
{
if(q.quest->checkQuest(hero))
if (q.quest->checkQuest(hero))
{
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)));
return;
return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero));
}
}
striveToGoal(sptr(Goals::FindObj(Obj::PRISON))); //rule of a thumb - quest heroes usually are locked in prisons
//BNLOG ("Don't know how to recruit hero with id %d\n", q.quest->m13489val);
return sptr(Goals::FindObj(Obj::PRISON)); //rule of a thumb - quest heroes usually are locked in prisons
//BNLOG ("Don't know how to recruit hero with id %d\n", q.quest->m13489val);
break;
}
case CQuest::MISSION_ARMY:
{
for(auto hero : heroes)
for (auto hero : heroes)
{
if(q.quest->checkQuest(hero)) //very bad info - stacks can be split between multiple heroes :(
if (q.quest->checkQuest(hero)) //very bad info - stacks can be split between multiple heroes :(
{
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)));
return;
return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero));
}
}
for(auto creature : q.quest->m6creatures)
for (auto creature : q.quest->m6creatures)
{
striveToGoal(sptr(Goals::GatherTroops(creature.type->idNumber, creature.count)));
return sptr(Goals::GatherTroops(creature.type->idNumber, creature.count));
}
//TODO: exchange armies... oh my
//BNLOG ("Don't know how to recruit %d of %s\n", (int)(creature.count) % creature.type->namePl);
@ -2353,47 +2404,46 @@ void VCAI::striveToQuest(const QuestInfo & q)
}
case CQuest::MISSION_RESOURCES:
{
if(heroes.size())
if (heroes.size())
{
if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
if (q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum())));
return sptr(Goals::GetObj(q.obj->id.getNum()));
}
else
{
for(int i = 0; i < q.quest->m7resources.size(); ++i)
for (int i = 0; i < q.quest->m7resources.size(); ++i)
{
if(q.quest->m7resources[i])
striveToGoal(sptr(Goals::CollectRes(i, q.quest->m7resources[i])));
if (q.quest->m7resources[i])
return sptr(Goals::CollectRes(i, q.quest->m7resources[i]));
}
}
}
else
striveToGoal(sptr(Goals::RecruitHero())); //FIXME: checkQuest requires any hero belonging to player :(
return sptr(Goals::RecruitHero()); //FIXME: checkQuest requires any hero belonging to player :(
break;
}
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
{
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
if(obj)
striveToGoal(sptr(Goals::GetObj(obj->id.getNum())));
if (obj)
return sptr(Goals::GetObj(obj->id.getNum()));
else
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum()))); //visit seer hut
return sptr(Goals::GetObj(q.obj->id.getNum())); //visit seer hut
break;
}
case CQuest::MISSION_PRIMARY_STAT:
{
auto heroes = cb->getHeroesInfo();
for(auto hero : heroes)
for (auto hero : heroes)
{
if(q.quest->checkQuest(hero))
if (q.quest->checkQuest(hero))
{
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)));
return;
return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero));
}
}
for(int i = 0; i < q.quest->m2stats.size(); ++i)
for (int i = 0; i < q.quest->m2stats.size(); ++i)
{
logAi->debug("Don't know how to increase primary stat %d", i);
}
@ -2402,12 +2452,11 @@ void VCAI::striveToQuest(const QuestInfo & q)
case CQuest::MISSION_LEVEL:
{
auto heroes = cb->getHeroesInfo();
for(auto hero : heroes)
for (auto hero : heroes)
{
if(q.quest->checkQuest(hero))
if (q.quest->checkQuest(hero))
{
striveToGoal(sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero))); //TODO: causes infinite loop :/
return;
return sptr(Goals::GetObj(q.obj->id.getNum()).sethero(hero)); //TODO: causes infinite loop :/
}
}
logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
@ -2415,17 +2464,18 @@ void VCAI::striveToQuest(const QuestInfo & q)
}
case CQuest::MISSION_PLAYER:
{
if(playerID.getNum() != q.quest->m13489val)
if (playerID.getNum() != q.quest->m13489val)
logAi->debug("Can't be player of color %d", q.quest->m13489val);
break;
}
case CQuest::MISSION_KEYMASTER:
{
striveToGoal(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)));
return sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID));
break;
}
}
} //end of switch
}
return sptr(Goals::Invalid());
}
void VCAI::performTypicalActions()
@ -2533,10 +2583,12 @@ int3 VCAI::explorationNewPoint(HeroPtr h)
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
{
auto obj = cb->getTopObj(tile);
if (obj)
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on that object
continue;
if(isSafeToVisit(h, tile))
{
if(isBlockVisitObj(tile)) //we can't stand on that object
continue;
bestTile = tile;
bestValue = ourValue;
}
@ -2580,17 +2632,19 @@ int3 VCAI::explorationDesperate(HeroPtr h)
auto t = sm->firstTileToGet(h, tile);
if(t.valid())
{
auto obj = cb->getTopObj(t);
if (obj)
if (obj->blockVisit && !isObjectRemovable(obj)) //we can't stand on object or remove it
continue;
ui64 ourDanger = evaluateDanger(t, h.h);
if(ourDanger < lowestDanger)
{
if(!isBlockVisitObj(t))
{
if(!ourDanger) //at least one safe place found
return t;
if(!ourDanger) //at least one safe place found
return t;
bestTile = t;
lowestDanger = ourDanger;
}
bestTile = t;
lowestDanger = ourDanger;
}
}
}
@ -2662,6 +2716,11 @@ void VCAI::lostHero(HeroPtr h)
}
vstd::erase_if_present(reservedHeroesMap, h);
vstd::erase_if_present(cachedSectorMaps, h);
vstd::erase_if_present(visitedHeroes, h);
for (auto heroVec : visitedHeroes)
{
vstd::erase_if_present(heroVec.second, h);
}
}
void VCAI::answerQuery(QueryID queryID, int selection)

View File

@ -87,6 +87,13 @@ public:
//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
std::map<HeroPtr, std::set<const CGTownInstance *>> townVisitsThisWeek;
//part of mainLoop, but accessible from outisde
std::vector<Goals::TSubgoal> basicGoals;
Goals::TGoalVec goalsToRemove;
Goals::TGoalVec goalsToAdd;
std::map<Goals::TSubgoal, Goals::TGoalVec> ultimateGoalsFromBasic; //theoreticlaly same goal can fulfill multiple basic goals
std::set<HeroPtr> invalidPathHeroes; //FIXME, just a workaround
std::map<HeroPtr, Goals::TSubgoal> lockedHeroes; //TODO: allow non-elementar objectives
std::map<HeroPtr, std::set<const CGObjectInstance *>> reservedHeroesMap; //objects reserved by specific heroes
std::set<HeroPtr> heroesUnableToExplore; //these heroes will not be polled for exploration in current state of game
@ -95,6 +102,7 @@ public:
std::set<const CGObjectInstance *> visitableObjs;
std::set<const CGObjectInstance *> alreadyVisited;
std::set<const CGObjectInstance *> reservedObjs; //to be visited by specific hero
std::map<HeroPtr, std::set<HeroPtr>> visitedHeroes; //visited this turn //FIXME: this is just bug workaround
//TODO: move to separate PathHandler class?
std::map<HeroPtr, std::shared_ptr<SectorMap>> cachedSectorMaps; //TODO: serialize? not necessary
@ -117,7 +125,6 @@ public:
void tryRealize(Goals::BuildThis & g);
void tryRealize(Goals::DigAtTile & g);
void tryRealize(Goals::Trade & g);
void tryRealize(Goals::Build & g);
void tryRealize(Goals::BuyArmy & g);
void tryRealize(Goals::Invalid & g);
void tryRealize(Goals::AbstractGoal & g);
@ -190,18 +197,18 @@ public:
void battleEnd(const BattleResult * br) override;
void makeTurn();
void makeTurnInternal();
void mainLoop();
void performTypicalActions();
void buildArmyIn(const CGTownInstance * t);
void striveToGoal(Goals::TSubgoal ultimateGoal);
Goals::TSubgoal striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool onlyAbstract);
Goals::TSubgoal decomposeGoal(Goals::TSubgoal ultimateGoal);
void endTurn();
void wander(HeroPtr h);
void setGoal(HeroPtr h, Goals::TSubgoal goal);
void evaluateGoal(HeroPtr h); //evaluates goal assigned to hero, if any
void completeGoal(Goals::TSubgoal goal); //safely removes goal from reserved hero
void striveToQuest(const QuestInfo & q);
Goals::TSubgoal questToGoal(const QuestInfo & q);
void recruitHero(const CGTownInstance * t, bool throwing = false);
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm);