1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-17 20:58:07 +02:00

Merge pull request #5304 from IvanSavenko/oneway_ai

Enable one-way monoliths for AI
This commit is contained in:
Ivan Savenko 2025-01-25 13:42:31 +02:00 committed by GitHub
commit 5ea4014589
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 249 additions and 129 deletions

View File

@ -1296,7 +1296,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
else
{
CGPath path;
cb->getPathsInfo(h.get())->getPath(path, dst);
nullkiller->getPathsInfo(h.get())->getPath(path, dst);
if(path.nodes.empty())
{
logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());
@ -1808,4 +1808,9 @@ bool AIStatus::channelProbing()
return ongoingChannelProbing;
}
void AIGateway::invalidatePaths()
{
nullkiller->invalidatePaths();
}
}

View File

@ -159,6 +159,8 @@ public:
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, BattleSide side, bool replayAllowed) override;
void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
void invalidatePaths() override;
void makeTurn();
void buildArmyIn(const CGTownInstance * t);

View File

@ -24,6 +24,8 @@
#include "../Goals/Composition.h"
#include "../../../lib/CPlayerState.h"
#include "../../lib/StartInfo.h"
#include "../../lib/pathfinder/PathfinderCache.h"
#include "../../lib/pathfinder/PathfinderOptions.h"
namespace NKAI
{
@ -43,6 +45,8 @@ Nullkiller::Nullkiller()
}
Nullkiller::~Nullkiller() = default;
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
{
if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed)
@ -73,6 +77,14 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, AIGateway * gateway)
settings = std::make_unique<Settings>(cb->getStartInfo()->difficulty);
PathfinderOptions pathfinderOptions(cb.get());
pathfinderOptions.useTeleportTwoWay = true;
pathfinderOptions.useTeleportOneWay = settings->isOneWayMonolithUsageAllowed();
pathfinderOptions.useTeleportOneWayRandom = settings->isOneWayMonolithUsageAllowed();
pathfinderCache = std::make_unique<PathfinderCache>(cb.get(), pathfinderOptions);
if(canUseOpenMap(cb, playerID))
{
useObjectGraph = settings->isObjectGraphAllowed();
@ -721,4 +733,14 @@ bool Nullkiller::handleTrading()
return haveTraded;
}
std::shared_ptr<const CPathsInfo> Nullkiller::getPathsInfo(const CGHeroInstance * h) const
{
return pathfinderCache->getPathsInfo(h);
}
void Nullkiller::invalidatePaths()
{
pathfinderCache->invalidatePaths();
}
}

View File

@ -21,6 +21,12 @@
#include "../Analyzers/ObjectClusterizer.h"
#include "../Helpers/ArmyFormation.h"
VCMI_LIB_NAMESPACE_BEGIN
class PathfinderCache;
VCMI_LIB_NAMESPACE_END
namespace NKAI
{
@ -72,6 +78,7 @@ private:
int3 targetTile;
ObjectInstanceID targetObject;
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
std::unique_ptr<PathfinderCache> pathfinderCache;
ScanDepth scanDepth;
TResources lockedResources;
bool useHeroChain;
@ -101,6 +108,7 @@ public:
std::mutex aiStateMutex;
Nullkiller();
~Nullkiller();
void init(std::shared_ptr<CCallback> cb, AIGateway * gateway);
void makeTurn();
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
@ -124,6 +132,9 @@ public:
bool handleTrading();
void invalidatePathfinderData();
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h) const;
void invalidatePaths();
private:
void resetAiState();
void updateAiState(int pass, bool fast = false);

View File

@ -38,6 +38,7 @@ namespace NKAI
pathfinderBucketsCount(1),
pathfinderBucketSize(32),
allowObjectGraph(true),
useOneWayMonoliths(false),
useTroopsFromGarrisons(false),
updateHitmapOnTileReveal(false),
openMap(true),
@ -64,5 +65,6 @@ namespace NKAI
openMap = node["openMap"].Bool();
useFuzzy = node["useFuzzy"].Bool();
useTroopsFromGarrisons = node["useTroopsFromGarrisons"].Bool();
useOneWayMonoliths = node["useOneWayMonoliths"].Bool();
}
}

View File

@ -36,6 +36,7 @@ namespace NKAI
float maxArmyLossTarget;
bool allowObjectGraph;
bool useTroopsFromGarrisons;
bool useOneWayMonoliths;
bool updateHitmapOnTileReveal;
bool openMap;
bool useFuzzy;
@ -58,6 +59,7 @@ namespace NKAI
int getPathfinderBucketSize() const { return pathfinderBucketSize; }
bool isObjectGraphAllowed() const { return allowObjectGraph; }
bool isGarrisonTroopsUsageAllowed() const { return useTroopsFromGarrisons; }
bool isOneWayMonolithUsageAllowed() const { return useOneWayMonoliths; }
bool isUpdateHitmapOnTileReveal() const { return updateHitmapOnTileReveal; }
bool isOpenMap() const { return openMap; }
bool isUseFuzzy() const { return useFuzzy; }

View File

@ -166,7 +166,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
if(nextNode.specialAction || nextNode.chainMask != chainMask)
break;
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(nextNode.coord);
auto targetNode = ai->nullkiller->getPathsInfo(hero)->getPathInfo(nextNode.coord);
if(!targetNode->reachable()
|| targetNode->getCost() > nextNode.cost)
@ -182,7 +182,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
if(node->turns == 0 && node->coord != hero->visitablePos())
{
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node->coord);
auto targetNode = ai->nullkiller->getPathsInfo(hero)->getPathInfo(node->coord);
if(targetNode->accessible == EPathAccessibility::NOT_SET
|| targetNode->accessible == EPathAccessibility::BLOCKED
@ -239,7 +239,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
if(hero->movementPointsRemaining() > 0)
{
CGPath path;
bool isOk = cb->getPathsInfo(hero)->getPath(path, node->coord);
bool isOk = ai->nullkiller->getPathsInfo(hero)->getPath(path, node->coord);
if(isOk && path.nodes.back().turns > 0)
{

View File

@ -35,7 +35,7 @@ void ExploreNeighbourTile::accept(AIGateway * ai)
int3 target = int3(-1);
foreach_neighbour(pos, [&](int3 tile)
{
auto pathInfo = ai->myCb->getPathsInfo(hero)->getPathInfo(tile);
auto pathInfo = ai->nullkiller->getPathsInfo(hero)->getPathInfo(tile);
if(pathInfo->turns > 0)
return;

View File

@ -223,7 +223,7 @@ bool ExplorationHelper::hasReachableNeighbor(const int3 & pos) const
if(cbp->isInTheMap(tile))
{
auto isAccessible = useCPathfinderAccessibility
? ai->cb->getPathsInfo(hero)->getPathInfo(tile)->reachable()
? ai->getPathsInfo(hero)->getPathInfo(tile)->reachable()
: ai->pathfinder->isTileAccessible(hero, tile);
if(isAccessible)

View File

@ -50,6 +50,8 @@ namespace AIPathfinding
options.allowLayerTransitioningAfterBattle = true;
options.useTeleportWhirlpool = true;
options.forceUseTeleportWhirlpool = true;
options.useTeleportOneWay = ai->settings->isOneWayMonolithUsageAllowed();;
options.useTeleportOneWayRandom = ai->settings->isOneWayMonolithUsageAllowed();;
}
AIPathfinderConfig::~AIPathfinderConfig() = default;

View File

@ -133,8 +133,8 @@ bool HeroPtr::operator==(const HeroPtr & rhs) const
bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const
{
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
const CGPathNode * ln = ai->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
const CGPathNode * rn = ai->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
return ln->getCost() < rn->getCost();
}

View File

@ -96,7 +96,7 @@ float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const Goals::A
}
else
{
auto pathInfo = ai->myCb->getPathsInfo(goal.hero.h)->getPathInfo(goal.tile);
auto pathInfo = ai->getPathsInfo(goal.hero.h)->getPathInfo(goal.tile);
return pathInfo->getCost();
}
}

View File

@ -31,6 +31,8 @@
#include "../../lib/networkPacks/PacksForClientBattle.h"
#include "../../lib/networkPacks/PacksForServer.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/pathfinder/PathfinderCache.h"
#include "../../lib/pathfinder/PathfinderOptions.h"
#include "AIhelper.h"
@ -621,6 +623,7 @@ void VCAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<C
playerID = *myCb->getPlayerID();
myCb->waitTillRealize = true;
myCb->unlockGsWhenWaiting = true;
pathfinderCache = std::make_unique<PathfinderCache>(myCb.get(), PathfinderOptions(myCb.get()));
if(!fh)
fh = new FuzzyHelper();
@ -628,6 +631,16 @@ void VCAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<C
retrieveVisitableObjs();
}
std::shared_ptr<const CPathsInfo> VCAI::getPathsInfo(const CGHeroInstance * h) const
{
return pathfinderCache->getPathsInfo(h);
}
void VCAI::invalidatePaths()
{
pathfinderCache->invalidatePaths();
}
void VCAI::yourTurn(QueryID queryID)
{
LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
@ -1800,7 +1813,7 @@ bool VCAI::isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies)
}
}
}
return cb->getPathsInfo(h.get())->getPathInfo(pos)->reachable();
return getPathsInfo(h.get())->getPathInfo(pos)->reachable();
}
bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
@ -1837,7 +1850,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
else
{
CGPath path;
cb->getPathsInfo(h.get())->getPath(path, dst);
getPathsInfo(h.get())->getPath(path, dst);
if(path.nodes.empty())
{
logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());

View File

@ -26,6 +26,7 @@
VCMI_LIB_NAMESPACE_BEGIN
struct QuestInfo;
class PathfinderCache;
VCMI_LIB_NAMESPACE_END
@ -80,6 +81,7 @@ public:
std::vector<ObjectInstanceID> teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored
//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
std::map<HeroPtr, std::set<const CGTownInstance *>> townVisitsThisWeek;
std::unique_ptr<PathfinderCache> pathfinderCache;
//part of mainLoop, but accessible from outside
std::vector<Goals::TSubgoal> basicGoals;
@ -254,6 +256,8 @@ public:
std::vector<HeroPtr> getMyHeroes() const;
HeroPtr primaryHero() const;
void checkHeroArmy(HeroPtr h);
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h) const;
void invalidatePaths() override;
void requestSent(const CPackForServer * pack, int requestID) override;
void answerQuery(QueryID queryID, int selection);

View File

@ -384,11 +384,6 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
return gs->map->canMoveBetween(a, b);
}
std::shared_ptr<const CPathsInfo> CCallback::getPathsInfo(const CGHeroInstance * h)
{
return cl->getPathsInfo(h);
}
std::optional<PlayerColor> CCallback::getPlayerID() const
{
return CBattleCallback::getPlayerID();

View File

@ -157,7 +157,6 @@ public:
//client-specific functionalities (pathfinding)
virtual bool canMoveBetween(const int3 &a, const int3 &b);
virtual int3 getGuardingCreaturePosition(int3 tile);
virtual std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
std::optional<PlayerColor> getPlayerID() const override;

View File

@ -97,6 +97,8 @@
#include "../lib/networkPacks/PacksForServer.h"
#include "../lib/pathfinder/CGPathNode.h"
#include "../lib/pathfinder/PathfinderCache.h"
#include "../lib/pathfinder/PathfinderOptions.h"
#include "../lib/serializer/CTypeList.h"
#include "../lib/serializer/ESerializationVersion.h"
@ -156,6 +158,7 @@ CPlayerInterface::~CPlayerInterface()
if (LOCPLINT == this)
LOCPLINT = nullptr;
}
void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB)
{
cb = CB;
@ -164,9 +167,20 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
CCS->musich->loadTerrainMusicThemes();
initializeHeroTownList();
pathfinderCache = std::make_unique<PathfinderCache>(cb.get(), PathfinderOptions(cb.get()));
adventureInt.reset(new AdventureMapInterface());
}
std::shared_ptr<const CPathsInfo> CPlayerInterface::getPathsInfo(const CGHeroInstance * h)
{
return pathfinderCache->getPathsInfo(h);
}
void CPlayerInterface::invalidatePaths()
{
pathfinderCache->invalidatePaths();
}
void CPlayerInterface::closeAllDialogs()
{
// remove all active dialogs that do not expect query answer
@ -467,6 +481,8 @@ void CPlayerInterface::heroSecondarySkillChanged(const CGHeroInstance * hero, in
EVENT_HANDLER_CALLED_BY_CLIENT;
for (auto cuw : GH.windows().findWindows<IMarketHolder>())
cuw->updateSecondarySkills();
localState->verifyPath(hero);
}
void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero)
@ -583,6 +599,8 @@ void CPlayerInterface::garrisonsChanged(std::vector<const CArmedInstance *> objs
if (hero)
{
localState->verifyPath(hero);
adventureInt->onHeroChanged(hero);
if(hero->inTownGarrison && hero->visitedTown != town)
adventureInt->onTownChanged(hero->visitedTown);

View File

@ -27,6 +27,7 @@ class CGObjectInstance;
class UpgradeInfo;
class ConditionalWait;
struct CPathsInfo;
class PathfinderCache;
VCMI_LIB_NAMESPACE_END
@ -64,6 +65,7 @@ class CPlayerInterface : public CGameInterface, public IUpdateable
std::list<std::shared_ptr<CInfoWindow>> dialogs; //queue of dialogs awaiting to be shown (not currently shown!)
std::unique_ptr<HeroMovementController> movementController;
std::unique_ptr<PathfinderCache> pathfinderCache;
public: // TODO: make private
std::unique_ptr<ArtifactsUIController> artifactController;
std::shared_ptr<Environment> env;
@ -198,6 +200,8 @@ public: // public interface for use by client via LOCPLINT access
void gamePause(bool pause);
void endNetwork();
void closeAllDialogs();
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
void invalidatePaths() override;
///returns true if all events are processed internally
bool capturedAllEvents();

View File

@ -222,8 +222,6 @@ void CClient::initMapHandler()
CGI->mh = std::make_shared<CMapHandler>(gs->map);
logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff());
}
pathCache.clear();
}
void CClient::initPlayerEnvironments()
@ -494,24 +492,7 @@ void CClient::startPlayerBattleAction(const BattleID & battleID, PlayerColor col
}
}
void CClient::updatePath(const ObjectInstanceID & id)
{
invalidatePaths();
auto hero = getHero(id);
updatePath(hero);
}
void CClient::updatePath(const CGHeroInstance * hero)
{
if(LOCPLINT && hero)
LOCPLINT->localState->verifyPath(hero);
}
void CClient::invalidatePaths()
{
boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
pathCache.clear();
}
vstd::RNG & CClient::getRandomGenerator()
{
@ -520,28 +501,6 @@ vstd::RNG & CClient::getRandomGenerator()
throw std::runtime_error("Illegal access to random number generator from client code!");
}
std::shared_ptr<const CPathsInfo> CClient::getPathsInfo(const CGHeroInstance * h)
{
assert(h);
boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
auto iter = pathCache.find(h);
if(iter == std::end(pathCache))
{
auto paths = std::make_shared<CPathsInfo>(getMapSize(), h);
gs->calculatePaths(h, *paths.get());
pathCache[h] = paths;
return paths;
}
else
{
return iter->second;
}
}
#if SCRIPTING_ENABLED
scripting::Pool * CClient::getGlobalContextPool() const
{

View File

@ -149,11 +149,6 @@ public:
void battleFinished(const BattleID & battleID);
void startPlayerBattleAction(const BattleID & battleID, PlayerColor color);
void invalidatePaths(); // clears this->pathCache()
void updatePath(const ObjectInstanceID & heroID); // invalidatePaths and update displayed hero path
void updatePath(const CGHeroInstance * hero);
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
friend class CCallback; //handling players actions
friend class CBattleCallback; //handling players actions
@ -235,8 +230,5 @@ private:
#endif
std::unique_ptr<events::EventBus> clientEventBus;
mutable boost::mutex pathCacheMutex;
std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
void reinitScripting();
};

View File

@ -168,7 +168,6 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack)
void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack)
{
const CGHeroInstance *h = cl.getHero(pack.hid);
cl.updatePath(h);
callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h);
}
@ -194,7 +193,7 @@ void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack)
i.second->tileHidden(pack.tiles);
}
}
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
}
static void dispatchGarrisonChange(CClient & cl, ObjectInstanceID army1, ObjectInstanceID army2)
@ -235,33 +234,21 @@ void ApplyClientNetPackVisitor::visitSetStackType(SetStackType & pack)
void ApplyClientNetPackVisitor::visitEraseStack(EraseStack & pack)
{
dispatchGarrisonChange(cl, pack.army, ObjectInstanceID());
cl.updatePath(pack.army); //it is possible to remove last non-native unit for current terrain and lose movement penalty
}
void ApplyClientNetPackVisitor::visitSwapStacks(SwapStacks & pack)
{
dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy);
if(pack.srcArmy != pack.dstArmy)
cl.updatePath(pack.dstArmy); // adding/removing units may change terrain type penalty based on creature native terrains
}
void ApplyClientNetPackVisitor::visitInsertNewStack(InsertNewStack & pack)
{
dispatchGarrisonChange(cl, pack.army, ObjectInstanceID());
cl.updatePath(pack.army); // adding/removing units may change terrain type penalty based on creature native terrains
}
void ApplyClientNetPackVisitor::visitRebalanceStacks(RebalanceStacks & pack)
{
dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy);
if(pack.srcArmy != pack.dstArmy)
{
cl.updatePath(pack.srcArmy); // adding/removing units may change terrain type penalty based on creature native terrains
cl.updatePath(pack.dstArmy);
}
}
void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
@ -272,12 +259,6 @@ void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & p
? ObjectInstanceID()
: pack.moves[0].dstArmy;
dispatchGarrisonChange(cl, pack.moves[0].srcArmy, destArmy);
if(pack.moves[0].srcArmy != destArmy)
{
cl.updatePath(destArmy); // adding/removing units may change terrain type penalty based on creature native terrains
cl.updatePath(pack.moves[0].srcArmy);
}
}
}
@ -303,7 +284,6 @@ void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
{
cl.updatePath(pack.artHolder);
for(const auto & slotErase : pack.posPack)
callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase));
}
@ -323,9 +303,6 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::askToAssembleArtifact, dstLoc);
if(pack.interfaceOwner != dstOwner)
callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc);
cl.updatePath(pack.srcArtHolder); // hero might have equipped/unequipped Angel Wings
cl.updatePath(pack.dstArtHolder);
}
};
@ -354,15 +331,11 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
void ApplyClientNetPackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
{
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactAssembled, pack.al);
cl.updatePath(pack.al.artHolder); // hero might have equipped/unequipped Angel Wings
}
void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
{
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactDisassembled, pack.al);
cl.updatePath(pack.al.artHolder); // hero might have equipped/unequipped Angel Wings
}
void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
@ -374,7 +347,7 @@ void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
{
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
if(pack.newWeekNotification)
{
@ -387,7 +360,8 @@ void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
{
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
switch(pack.who)
{
case GiveBonus::ETarget::OBJECT:
@ -423,7 +397,7 @@ void ApplyClientNetPackVisitor::visitChangeObjPos(ChangeObjPos & pack)
CGI->mh->onObjectFadeIn(obj, pack.initiator);
CGI->mh->waitForOngoingAnimations();
}
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
}
void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
@ -490,7 +464,6 @@ void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface
void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack)
{
cl.invalidatePaths();
switch(pack.who)
{
case GiveBonus::ETarget::OBJECT:
@ -531,7 +504,8 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
void ApplyClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
{
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
for(auto i=cl.playerint.begin(); i!=cl.playerint.end(); i++)
i->second->objectRemovedAfter();
}
@ -561,7 +535,7 @@ void ApplyFirstClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
{
const CGHeroInstance *h = cl.getHero(pack.id);
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
if(CGI->mh)
{
@ -976,7 +950,8 @@ void ApplyClientNetPackVisitor::visitPlayerMessageClient(PlayerMessageClient & p
void ApplyClientNetPackVisitor::visitAdvmapSpellCast(AdvmapSpellCast & pack)
{
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
auto caster = cl.getHero(pack.casterID);
if(caster)
//consider notifying other interfaces that see hero?
@ -1068,7 +1043,7 @@ void ApplyClientNetPackVisitor::visitCenterView(CenterView & pack)
void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
{
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
const CGObjectInstance *obj = pack.newObject;
if(CGI->mh)
@ -1101,5 +1076,5 @@ void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts
void ApplyClientNetPackVisitor::visitEntitiesChanged(EntitiesChanged & pack)
{
cl.invalidatePaths();
callAllInterfaces(cl, &CGameInterface::invalidatePaths);
}

View File

@ -54,7 +54,7 @@ bool PlayerLocalState::hasPath(const CGHeroInstance * h) const
bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destination)
{
CGPath path;
if(!owner.cb->getPathsInfo(h)->getPath(path, destination))
if(!owner.getPathsInfo(h)->getPath(path, destination))
{
paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired)
syncronizeState();

View File

@ -17,6 +17,7 @@ class CArmedInstance;
class JsonNode;
struct CGPath;
class int3;
struct CPathsInfo;
VCMI_LIB_NAMESPACE_END

View File

@ -107,6 +107,9 @@ void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
{
if (h)
LOCPLINT->localState->verifyPath(h);
widget->getHeroList()->updateElement(h);
if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
@ -546,7 +549,7 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
{
isHero = true;
const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(targetPosition);
const CGPathNode *pn = LOCPLINT->getPathsInfo(currentHero)->getPathInfo(targetPosition);
if(currentHero == topBlocking) //clicked selected hero
{
LOCPLINT->openHeroWindow(currentHero);
@ -685,7 +688,7 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
std::array<Cursor::Map, 4> cursorVisit = { Cursor::Map::T1_VISIT, Cursor::Map::T2_VISIT, Cursor::Map::T3_VISIT, Cursor::Map::T4_VISIT, };
std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(targetPosition);
const CGPathNode * pathNode = LOCPLINT->getPathsInfo(hero)->getPathInfo(targetPosition);
assert(pathNode);
if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info

View File

@ -42,6 +42,7 @@
"maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true,
"allowObjectGraph": false,
"pathfinderBucketsCount" : 3,
@ -63,6 +64,7 @@
"maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true,
"allowObjectGraph": false,
"pathfinderBucketsCount" : 3,
@ -84,6 +86,7 @@
"maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true,
"allowObjectGraph": false,
"pathfinderBucketsCount" : 3,
@ -105,6 +108,7 @@
"maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true,
"allowObjectGraph": false,
"pathfinderBucketsCount" : 3,
@ -126,6 +130,7 @@
"maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true,
"allowObjectGraph": false,
"pathfinderBucketsCount" : 3,

View File

@ -473,7 +473,7 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getVisitableObjs(int3
for(const CGObjectInstance * obj : t->visitableObjects)
{
if(getPlayerID() || obj->ID != Obj::EVENT) //hide events from players
if(!getPlayerID().has_value() || obj->ID != Obj::EVENT) //hide events from players
ret.push_back(obj);
}
@ -945,16 +945,11 @@ void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3> &tiles,
gs->getTilesInRange(tiles, pos, radious, ETileVisibility::REVEALED, *getPlayerID(), distanceFormula);
}
void CGameInfoCallback::calculatePaths(const std::shared_ptr<PathfinderConfig> & config)
void CGameInfoCallback::calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const
{
gs->calculatePaths(config);
}
void CGameInfoCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out)
{
gs->calculatePaths(hero, out);
}
const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
{
return gs->map->artInstances.at(aid.num);

View File

@ -207,8 +207,7 @@ public:
virtual std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
virtual bool isInTheMap(const int3 &pos) const;
virtual void getVisibleTilesInRange(std::unordered_set<int3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
virtual void calculatePaths(const std::shared_ptr<PathfinderConfig> & config);
virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);
virtual void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const;
virtual EDiggingStatus getTileDigStatus(int3 tile, bool verbose = true) const;
//town

View File

@ -56,6 +56,7 @@ class CSaveFile;
class BattleStateInfo;
struct ArtifactLocation;
class BattleStateInfoForRetreat;
struct CPathsInfo;
#if SCRIPTING_ENABLED
namespace scripting
@ -108,6 +109,9 @@ public:
virtual void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain){};
virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) = 0;
/// Invalidates and destroys all paths for all heroes
virtual void invalidatePaths(){};
};
class DLL_LINKAGE CDynLibHandler

View File

@ -172,6 +172,7 @@ set(lib_MAIN_SRCS
pathfinder/CGPathNode.cpp
pathfinder/CPathfinder.cpp
pathfinder/NodeStorage.cpp
pathfinder/PathfinderCache.cpp
pathfinder/PathfinderOptions.cpp
pathfinder/PathfindingRules.cpp
pathfinder/TurnInfo.cpp
@ -584,6 +585,7 @@ set(lib_MAIN_HEADERS
pathfinder/CGPathNode.h
pathfinder/CPathfinder.h
pathfinder/NodeStorage.h
pathfinder/PathfinderCache.h
pathfinder/PathfinderOptions.h
pathfinder/PathfinderUtil.h
pathfinder/PathfindingRules.h

View File

@ -1144,15 +1144,9 @@ void CGameState::apply(CPackForClient & pack)
pack.applyGs(this);
}
void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
void CGameState::calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const
{
calculatePaths(std::make_shared<SingleHeroPathfinderConfig>(out, this, hero));
}
void CGameState::calculatePaths(const std::shared_ptr<PathfinderConfig> & config)
{
//FIXME: creating pathfinder is costly, maybe reset / clear is enough?
CPathfinder pathfinder(this, config);
CPathfinder pathfinder(const_cast<CGameState*>(this), config);
pathfinder.calculatePaths();
}

View File

@ -96,8 +96,7 @@ public:
void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2) const override;
bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out) override; //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) override;
void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const override;
int3 guardingCreaturePosition (int3 pos) const override;
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;

View File

@ -56,6 +56,7 @@ CPathsInfo::CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_)
: sizes(Sizes), hero(hero_)
{
nodes.resize(boost::extents[ELayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y]);
heroBonusTreeVersion = hero->getTreeVersion();
}
CPathsInfo::~CPathsInfo() = default;

View File

@ -188,6 +188,8 @@ struct DLL_LINKAGE CPathsInfo
const CGHeroInstance * hero;
int3 hpos;
int3 sizes;
/// Bonus tree version for which this information can be considered to be valid
int heroBonusTreeVersion = 0;
boost::multi_array<CGPathNode, 4> nodes; //[layer][level][w][h]
CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_);

View File

@ -0,0 +1,66 @@
/*
* PathfinderCache.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 "PathfinderCache.h"
#include "CGPathNode.h"
#include "PathfinderOptions.h"
#include "../CGameInfoCallback.h"
#include "../mapObjects/CGHeroInstance.h"
VCMI_LIB_NAMESPACE_BEGIN
std::shared_ptr<PathfinderConfig> PathfinderCache::createConfig(const CGHeroInstance * h, CPathsInfo & out)
{
auto config = std::make_shared<SingleHeroPathfinderConfig>(out, cb, h);
config->options = options;
return config;
}
std::shared_ptr<CPathsInfo> PathfinderCache::buildPaths(const CGHeroInstance * h)
{
std::shared_ptr<CPathsInfo> result = std::make_shared<CPathsInfo>(cb->getMapSize(), h);
auto config = createConfig(h, *result);
cb->calculatePaths(config);
return result;
}
PathfinderCache::PathfinderCache(const CGameInfoCallback * cb, const PathfinderOptions & options)
: cb(cb)
, options(options)
{
}
void PathfinderCache::invalidatePaths()
{
std::lock_guard lock(pathCacheMutex);
pathCache.clear();
}
std::shared_ptr<const CPathsInfo> PathfinderCache::getPathsInfo(const CGHeroInstance * h)
{
std::lock_guard lock(pathCacheMutex);
auto iter = pathCache.find(h);
if(iter == std::end(pathCache) || iter->second->heroBonusTreeVersion != h->getTreeVersion())
{
auto result = buildPaths(h);
pathCache[h] = result;
return result;
}
else
return iter->second;
}
VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,40 @@
/*
* PathfinderCache.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 "PathfinderOptions.h"
VCMI_LIB_NAMESPACE_BEGIN
class CGameInfoCallback;
class CGHeroInstance;
class PathfinderConfig;
struct CPathsInfo;
class DLL_LINKAGE PathfinderCache
{
const CGameInfoCallback * cb;
std::mutex pathCacheMutex;
std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
PathfinderOptions options;
std::shared_ptr<PathfinderConfig> createConfig(const CGHeroInstance *h, CPathsInfo &out);
std::shared_ptr<CPathsInfo> buildPaths(const CGHeroInstance *h);
public:
PathfinderCache(const CGameInfoCallback * cb, const PathfinderOptions & options);
/// Invalidates and erases all existing paths from the cache
void invalidatePaths();
/// Returns compute path information for requested hero
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
};
VCMI_LIB_NAMESPACE_END

View File

@ -59,14 +59,17 @@ std::vector<std::shared_ptr<IPathfindingRule>> SingleHeroPathfinderConfig::build
SingleHeroPathfinderConfig::~SingleHeroPathfinderConfig() = default;
SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero)
SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, const CGameInfoCallback * gs, const CGHeroInstance * hero)
: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), gs, buildRuleSet())
, hero(hero)
{
pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, hero, options);
}
CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
{
if (!pathfinderHelper)
pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, hero, options);
return pathfinderHelper.get();
}

View File

@ -108,9 +108,10 @@ class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig
{
private:
std::unique_ptr<CPathfinderHelper> pathfinderHelper;
const CGHeroInstance * hero;
public:
SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero);
SingleHeroPathfinderConfig(CPathsInfo & out, const CGameInfoCallback * gs, const CGHeroInstance * hero);
virtual ~SingleHeroPathfinderConfig();
CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;

View File

@ -120,7 +120,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i
{
static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT);
const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector);
roughTerrainDiscountValue = bonuses->getFirst(daySelector) != nullptr;
roughTerrainDiscountValue = bonuses->valOfBonuses(daySelector);
}
{