|
@@ -9,13 +9,17 @@
|
|
|
*/
|
|
|
#include "StdInc.h"
|
|
|
|
|
|
-#include "../../lib/ArtifactUtils.h"
|
|
|
+#include "../../lib/AsyncRunner.h"
|
|
|
#include "../../lib/UnlockGuard.h"
|
|
|
#include "../../lib/StartInfo.h"
|
|
|
+#include "../../lib/battle/CPlayerBattleCallback.h"
|
|
|
+#include "../../lib/entities/artifact/ArtifactUtils.h"
|
|
|
+#include "../../lib/entities/artifact/CArtifact.h"
|
|
|
#include "../../lib/entities/building/CBuilding.h"
|
|
|
#include "../../lib/mapObjects/MapObjects.h"
|
|
|
#include "../../lib/mapObjects/ObjectTemplate.h"
|
|
|
#include "../../lib/mapObjects/CGHeroInstance.h"
|
|
|
+#include "../../lib/mapping/TerrainTile.h"
|
|
|
#include "../../lib/CConfigHandler.h"
|
|
|
#include "../../lib/IGameSettings.h"
|
|
|
#include "../../lib/gameState/CGameState.h"
|
|
@@ -32,8 +36,6 @@
|
|
|
#include "AIGateway.h"
|
|
|
#include "Goals/Goals.h"
|
|
|
|
|
|
-static tbb::task_arena executeActionAsyncArena;
|
|
|
-
|
|
|
namespace NKAI
|
|
|
{
|
|
|
|
|
@@ -68,12 +70,13 @@ struct SetGlobalState
|
|
|
#define MAKING_TURN SET_GLOBAL_STATE(this)
|
|
|
|
|
|
AIGateway::AIGateway()
|
|
|
+ :status(this)
|
|
|
{
|
|
|
LOG_TRACE(logAi);
|
|
|
destinationTeleport = ObjectInstanceID();
|
|
|
destinationTeleportPos = int3(-1);
|
|
|
nullkiller.reset(new Nullkiller());
|
|
|
- asyncTasks = std::make_unique<tbb::task_group>();
|
|
|
+ asyncTasks = std::make_unique<AsyncRunner>();
|
|
|
}
|
|
|
|
|
|
AIGateway::~AIGateway()
|
|
@@ -125,7 +128,7 @@ void AIGateway::heroMoved(const TryMoveHero & details, bool verbose)
|
|
|
else if(details.result == TryMoveHero::EMBARK && hero)
|
|
|
{
|
|
|
//make sure AI not attempt to visit used boat
|
|
|
- validateObject(hero->boat);
|
|
|
+ validateObject(hero->getBoat());
|
|
|
}
|
|
|
else if(details.result == TryMoveHero::DISEMBARK && o1)
|
|
|
{
|
|
@@ -266,7 +269,7 @@ void AIGateway::heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance
|
|
|
NET_EVENT_HANDLER;
|
|
|
}
|
|
|
|
|
|
-void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
|
|
|
+void AIGateway::tileHidden(const FowTilesType & pos)
|
|
|
{
|
|
|
LOG_TRACE(logAi);
|
|
|
NET_EVENT_HANDLER;
|
|
@@ -274,7 +277,7 @@ void AIGateway::tileHidden(const std::unordered_set<int3> & pos)
|
|
|
nullkiller->memory->removeInvisibleObjects(myCb.get());
|
|
|
}
|
|
|
|
|
|
-void AIGateway::tileRevealed(const std::unordered_set<int3> & pos)
|
|
|
+void AIGateway::tileRevealed(const FowTilesType & pos)
|
|
|
{
|
|
|
LOG_TRACE(logAi);
|
|
|
NET_EVENT_HANDLER;
|
|
@@ -324,9 +327,15 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+void AIGateway::heroExperienceChanged(const CGHeroInstance * hero, si64 val)
|
|
|
+{
|
|
|
+ LOG_TRACE_PARAMS(logAi, "val '%i'", val);
|
|
|
+ NET_EVENT_HANDLER;
|
|
|
+}
|
|
|
+
|
|
|
void AIGateway::heroPrimarySkillChanged(const CGHeroInstance * hero, PrimarySkill which, si64 val)
|
|
|
{
|
|
|
- LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", static_cast<int>(which) % val);
|
|
|
+ LOG_TRACE_PARAMS(logAi, "which '%i', val '%i'", which.getNum() % val);
|
|
|
NET_EVENT_HANDLER;
|
|
|
}
|
|
|
|
|
@@ -575,7 +584,6 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
|
|
|
NET_EVENT_HANDLER;
|
|
|
playerID = *myCb->getPlayerID();
|
|
|
myCb->waitTillRealize = true;
|
|
|
- myCb->unlockGsWhenWaiting = true;
|
|
|
|
|
|
nullkiller->init(CB, this);
|
|
|
|
|
@@ -593,11 +601,11 @@ void AIGateway::yourTurn(QueryID queryID)
|
|
|
|
|
|
nullkiller->makingTurnInterrupption.reset();
|
|
|
|
|
|
- executeActionAsyncArena.enqueue(asyncTasks->defer([this]()
|
|
|
+ asyncTasks->run([this]()
|
|
|
{
|
|
|
ScopedThreadName guard("NKAI::makingTurn");
|
|
|
makeTurn();
|
|
|
- }));
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
|
|
@@ -629,7 +637,7 @@ void AIGateway::commanderGotLevel(const CCommanderInstance * commander, std::vec
|
|
|
{
|
|
|
LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
|
|
|
NET_EVENT_HANDLER;
|
|
|
- status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->armyObj->nodeName() % (int)commander->level));
|
|
|
+ status.addQuery(queryID, boost::str(boost::format("Commander %s of %s got level %d") % commander->name % commander->getArmy()->nodeName() % (int)commander->level));
|
|
|
executeActionAsync("commanderGotLevel", [this, queryID](){ answerQuery(queryID, 0); });
|
|
|
}
|
|
|
|
|
@@ -774,7 +782,7 @@ void AIGateway::showGarrisonDialog(const CArmedInstance * up, const CGHeroInstan
|
|
|
//you can't request action from action-response thread
|
|
|
executeActionAsync("showGarrisonDialog", [this, up, down, removableUnits, queryID]()
|
|
|
{
|
|
|
- if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->isRestorationOfErathiaCampaign())
|
|
|
+ if(removableUnits && up->tempOwner == down->tempOwner && nullkiller->settings->isGarrisonTroopsUsageAllowed() && !cb->getStartInfo()->restrictedGarrisonsForAI())
|
|
|
{
|
|
|
pickBestCreatures(down, up);
|
|
|
}
|
|
@@ -817,11 +825,11 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
|
|
|
int oldValue = s->getCreature()->getAIValue();
|
|
|
int newValue = upgID.toCreature()->getAIValue();
|
|
|
|
|
|
- if(newValue > oldValue && nullkiller->getFreeResources().canAfford(upgradeInfo.getUpgradeCostsFor(upgID) * s->count))
|
|
|
+ if(newValue > oldValue && nullkiller->getFreeResources().canAfford(upgradeInfo.getUpgradeCostsFor(upgID) * s->getCount()))
|
|
|
{
|
|
|
myCb->upgradeCreature(obj, SlotID(i), upgID);
|
|
|
upgraded = true;
|
|
|
- logAi->debug("Upgraded %d %s to %s", s->count, upgradeInfo.oldID.toCreature()->getNamePluralTranslated(),
|
|
|
+ logAi->debug("Upgraded %d %s to %s", s->getCount(), upgradeInfo.oldID.toCreature()->getNamePluralTranslated(),
|
|
|
upgradeInfo.getUpgrade().toCreature()->getNamePluralTranslated());
|
|
|
}
|
|
|
else
|
|
@@ -905,19 +913,19 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
|
|
|
switch(obj->ID)
|
|
|
{
|
|
|
case Obj::TOWN:
|
|
|
- if(h->visitedTown) //we are inside, not just attacking
|
|
|
+ if(h->getVisitedTown()) //we are inside, not just attacking
|
|
|
{
|
|
|
makePossibleUpgrades(h.get());
|
|
|
|
|
|
std::unique_lock lockGuard(nullkiller->aiStateMutex);
|
|
|
|
|
|
- if(!h->visitedTown->garrisonHero || !nullkiller->isHeroLocked(h->visitedTown->garrisonHero))
|
|
|
- moveCreaturesToHero(h->visitedTown);
|
|
|
+ if(!h->getVisitedTown()->getGarrisonHero() || !nullkiller->isHeroLocked(h->getVisitedTown()->getGarrisonHero()))
|
|
|
+ moveCreaturesToHero(h->getVisitedTown());
|
|
|
|
|
|
if(nullkiller->heroManager->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook()
|
|
|
&& nullkiller->getFreeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
|
|
|
{
|
|
|
- if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
|
|
|
+ if(h->getVisitedTown()->hasBuilt(BuildingID::MAGES_GUILD_1))
|
|
|
cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
|
|
|
}
|
|
|
}
|
|
@@ -930,9 +938,9 @@ void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h
|
|
|
|
|
|
void AIGateway::moveCreaturesToHero(const CGTownInstance * t)
|
|
|
{
|
|
|
- if(t->visitingHero && t->armedGarrison() && t->visitingHero->tempOwner == t->tempOwner)
|
|
|
+ if(t->getVisitingHero() && t->armedGarrison() && t->getVisitingHero()->tempOwner == t->tempOwner)
|
|
|
{
|
|
|
- pickBestCreatures(t->visitingHero, t->getUpperArmy());
|
|
|
+ pickBestCreatures(t->getVisitingHero(), t->getUpperArmy());
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1040,6 +1048,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
|
|
auto equipBest = [](const CGHeroInstance * h, const CGHeroInstance * otherh, bool giveStuffToFirstHero) -> void
|
|
|
{
|
|
|
bool changeMade = false;
|
|
|
+ std::set<std::pair<ArtifactInstanceID, ArtifactInstanceID> > swappedSet;
|
|
|
do
|
|
|
{
|
|
|
changeMade = false;
|
|
@@ -1050,22 +1059,22 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
|
|
{
|
|
|
for(auto p : h->artifactsWorn)
|
|
|
{
|
|
|
- if(p.second.artifact)
|
|
|
+ if(p.second.getArt())
|
|
|
allArtifacts.push_back(ArtifactLocation(h->id, p.first));
|
|
|
}
|
|
|
}
|
|
|
for(auto slot : h->artifactsInBackpack)
|
|
|
- allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
|
|
|
+ allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.getArt())));
|
|
|
|
|
|
if(otherh)
|
|
|
{
|
|
|
for(auto p : otherh->artifactsWorn)
|
|
|
{
|
|
|
- if(p.second.artifact)
|
|
|
+ if(p.second.getArt())
|
|
|
allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
|
|
|
}
|
|
|
for(auto slot : otherh->artifactsInBackpack)
|
|
|
- allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
|
|
|
+ allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.getArt())));
|
|
|
}
|
|
|
//we give stuff to one hero or another, depending on giveStuffToFirstHero
|
|
|
|
|
@@ -1087,7 +1096,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
|
|
auto s = artHolder->getSlot(location.slot);
|
|
|
if(!s || s->locked) //we can't move locks
|
|
|
continue;
|
|
|
- auto artifact = s->artifact;
|
|
|
+ auto artifact = s->getArt();
|
|
|
if(!artifact)
|
|
|
continue;
|
|
|
//FIXME: why are the above possible to be null?
|
|
@@ -1111,21 +1120,30 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
|
|
for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
|
|
|
{
|
|
|
auto otherSlot = target->getSlot(slot);
|
|
|
- if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
|
|
|
+ if(otherSlot && otherSlot->getArt()) //we need to exchange artifact for better one
|
|
|
{
|
|
|
- int64_t otherArtifactScore = getArtifactScoreForHero(target, otherSlot->artifact);
|
|
|
- logAi->trace( "Comparing artifacts of %s: %s vs %s. Score: %d vs %d", target->getHeroTypeName(), artifact->getType()->getJsonKey(), otherSlot->artifact->getType()->getJsonKey(), artifactScore, otherArtifactScore);
|
|
|
+ int64_t otherArtifactScore = getArtifactScoreForHero(target, otherSlot->getArt());
|
|
|
+ logAi->trace( "Comparing artifacts of %s: %s vs %s. Score: %d vs %d", target->getHeroTypeName(), artifact->getType()->getJsonKey(), otherSlot->getArt()->getType()->getJsonKey(), artifactScore, otherArtifactScore);
|
|
|
|
|
|
//if that artifact is better than what we have, pick it
|
|
|
//combined artifacts are not always allowed to move
|
|
|
if(artifactScore > otherArtifactScore && artifact->canBePutAt(target, slot, true))
|
|
|
{
|
|
|
+ std::pair swapPair = std::minmax<ArtifactInstanceID>({artifact->getId(), otherSlot->artifactID});
|
|
|
+ if (swappedSet.find(swapPair) != swappedSet.end())
|
|
|
+ {
|
|
|
+ logAi->warn(
|
|
|
+ "Artifacts % s < -> % s have already swapped before, ignored.",
|
|
|
+ artifact->getType()->getJsonKey(),
|
|
|
+ otherSlot->getArt()->getType()->getJsonKey());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
logAi->trace(
|
|
|
"Exchange artifacts %s <-> %s",
|
|
|
artifact->getType()->getJsonKey(),
|
|
|
- otherSlot->artifact->getType()->getJsonKey());
|
|
|
+ otherSlot->getArt()->getType()->getJsonKey());
|
|
|
|
|
|
- if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true))
|
|
|
+ if(!otherSlot->getArt()->canBePutAt(artHolder, location.slot, true))
|
|
|
{
|
|
|
ArtifactLocation destLocation(target->id, slot);
|
|
|
ArtifactLocation backpack(artHolder->id, ArtifactPosition::BACKPACK_START);
|
|
@@ -1135,10 +1153,11 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
|
|
|
+ cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->getArt())));
|
|
|
}
|
|
|
|
|
|
changeMade = true;
|
|
|
+ swappedSet.insert(swapPair);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
@@ -1170,7 +1189,7 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
|
|
|
|
|
if(!recruiter->getSlotFor(creID).validSlot())
|
|
|
{
|
|
|
- for(auto stack : recruiter->Slots())
|
|
|
+ for(const auto & stack : recruiter->Slots())
|
|
|
{
|
|
|
if(!stack.second->getType())
|
|
|
continue;
|
|
@@ -1272,10 +1291,10 @@ void AIGateway::addVisitableObj(const CGObjectInstance * obj)
|
|
|
|
|
|
bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
|
|
{
|
|
|
- if(h->inTownGarrison && h->visitedTown)
|
|
|
+ if(h->isGarrisoned() && h->getVisitedTown())
|
|
|
{
|
|
|
- cb->swapGarrisonHero(h->visitedTown);
|
|
|
- moveCreaturesToHero(h->visitedTown);
|
|
|
+ cb->swapGarrisonHero(h->getVisitedTown());
|
|
|
+ moveCreaturesToHero(h->getVisitedTown());
|
|
|
}
|
|
|
|
|
|
//TODO: consider if blockVisit objects change something in our checks: AIUtility::isBlockVisitObj()
|
|
@@ -1322,8 +1341,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
|
|
|
{
|
|
|
auto tile = cb->getTile(coord, false);
|
|
|
assert(tile);
|
|
|
- return tile->topVisitableObj(ignoreHero);
|
|
|
- //return cb->getTile(coord,false)->topVisitableObj(ignoreHero);
|
|
|
+ return cb->getObj(tile->topVisitableObj(ignoreHero), false);
|
|
|
};
|
|
|
|
|
|
auto isTeleportAction = [&](EPathNodeAction action) -> bool
|
|
@@ -1533,7 +1551,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
|
|
|
|
|
int toGive;
|
|
|
int toGet;
|
|
|
- m->getOffer(res, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
|
|
+ m->getOffer(res.getNum(), g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
|
|
|
toGive = static_cast<int>(toGive * (it->resVal / toGive)); //round down
|
|
|
//TODO trade only as much as needed
|
|
|
if (toGive) //don't try to sell 0 resources
|
|
@@ -1586,7 +1604,7 @@ void AIGateway::endTurn()
|
|
|
|
|
|
void AIGateway::buildArmyIn(const CGTownInstance * t)
|
|
|
{
|
|
|
- makePossibleUpgrades(t->visitingHero);
|
|
|
+ makePossibleUpgrades(t->getVisitingHero());
|
|
|
makePossibleUpgrades(t);
|
|
|
recruitCreatures(t, t->getUpperArmy());
|
|
|
moveCreaturesToHero(t);
|
|
@@ -1608,13 +1626,13 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f
|
|
|
if (!asyncTasks)
|
|
|
throw std::runtime_error("Attempt to execute task on shut down AI state!");
|
|
|
|
|
|
- executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]()
|
|
|
+ asyncTasks->run([this, description, whatToDo]()
|
|
|
{
|
|
|
ScopedThreadName guard("NKAI::" + description);
|
|
|
SET_GLOBAL_STATE(this);
|
|
|
std::shared_lock gsLock(CGameState::mutex);
|
|
|
whatToDo();
|
|
|
- }));
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
void AIGateway::lostHero(HeroPtr h)
|
|
@@ -1667,7 +1685,8 @@ void AIGateway::validateObject(ObjectIdRef obj)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-AIStatus::AIStatus()
|
|
|
+AIStatus::AIStatus(AIGateway * gateway)
|
|
|
+ : gateway(gateway)
|
|
|
{
|
|
|
battle = NO_BATTLE;
|
|
|
havingTurn = false;
|
|
@@ -1749,7 +1768,10 @@ void AIStatus::waitTillFree()
|
|
|
{
|
|
|
std::unique_lock<std::mutex> lock(mx);
|
|
|
while(battle != NO_BATTLE || !remainingQueries.empty() || !objectsBeingVisited.empty() || ongoingHeroMovement)
|
|
|
+ {
|
|
|
cv.wait_for(lock, std::chrono::milliseconds(10));
|
|
|
+ gateway->nullkiller->makingTurnInterrupption.interruptionPoint();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
bool AIStatus::haveTurn()
|