1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +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 else
{ {
CGPath path; CGPath path;
cb->getPathsInfo(h.get())->getPath(path, dst); nullkiller->getPathsInfo(h.get())->getPath(path, dst);
if(path.nodes.empty()) if(path.nodes.empty())
{ {
logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString()); logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());
@ -1808,4 +1808,9 @@ bool AIStatus::channelProbing()
return ongoingChannelProbing; 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 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 battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
void invalidatePaths() override;
void makeTurn(); void makeTurn();
void buildArmyIn(const CGTownInstance * t); void buildArmyIn(const CGTownInstance * t);

View File

@ -24,6 +24,8 @@
#include "../Goals/Composition.h" #include "../Goals/Composition.h"
#include "../../../lib/CPlayerState.h" #include "../../../lib/CPlayerState.h"
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/pathfinder/PathfinderCache.h"
#include "../../lib/pathfinder/PathfinderOptions.h"
namespace NKAI namespace NKAI
{ {
@ -43,6 +45,8 @@ Nullkiller::Nullkiller()
} }
Nullkiller::~Nullkiller() = default;
bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID) bool canUseOpenMap(std::shared_ptr<CCallback> cb, PlayerColor playerID)
{ {
if(!cb->getStartInfo()->extraOptionsInfo.cheatsAllowed) 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); 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)) if(canUseOpenMap(cb, playerID))
{ {
useObjectGraph = settings->isObjectGraphAllowed(); useObjectGraph = settings->isObjectGraphAllowed();
@ -721,4 +733,14 @@ bool Nullkiller::handleTrading()
return haveTraded; 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 "../Analyzers/ObjectClusterizer.h"
#include "../Helpers/ArmyFormation.h" #include "../Helpers/ArmyFormation.h"
VCMI_LIB_NAMESPACE_BEGIN
class PathfinderCache;
VCMI_LIB_NAMESPACE_END
namespace NKAI namespace NKAI
{ {
@ -72,6 +78,7 @@ private:
int3 targetTile; int3 targetTile;
ObjectInstanceID targetObject; ObjectInstanceID targetObject;
std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes; std::map<const CGHeroInstance *, HeroLockedReason> lockedHeroes;
std::unique_ptr<PathfinderCache> pathfinderCache;
ScanDepth scanDepth; ScanDepth scanDepth;
TResources lockedResources; TResources lockedResources;
bool useHeroChain; bool useHeroChain;
@ -101,6 +108,7 @@ public:
std::mutex aiStateMutex; std::mutex aiStateMutex;
Nullkiller(); Nullkiller();
~Nullkiller();
void init(std::shared_ptr<CCallback> cb, AIGateway * gateway); void init(std::shared_ptr<CCallback> cb, AIGateway * gateway);
void makeTurn(); void makeTurn();
bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; } bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; }
@ -124,6 +132,9 @@ public:
bool handleTrading(); bool handleTrading();
void invalidatePathfinderData(); void invalidatePathfinderData();
std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h) const;
void invalidatePaths();
private: private:
void resetAiState(); void resetAiState();
void updateAiState(int pass, bool fast = false); void updateAiState(int pass, bool fast = false);

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ void ExploreNeighbourTile::accept(AIGateway * ai)
int3 target = int3(-1); int3 target = int3(-1);
foreach_neighbour(pos, [&](int3 tile) 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) if(pathInfo->turns > 0)
return; return;

View File

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

View File

@ -50,6 +50,8 @@ namespace AIPathfinding
options.allowLayerTransitioningAfterBattle = true; options.allowLayerTransitioningAfterBattle = true;
options.useTeleportWhirlpool = true; options.useTeleportWhirlpool = true;
options.forceUseTeleportWhirlpool = true; options.forceUseTeleportWhirlpool = true;
options.useTeleportOneWay = ai->settings->isOneWayMonolithUsageAllowed();;
options.useTeleportOneWayRandom = ai->settings->isOneWayMonolithUsageAllowed();;
} }
AIPathfinderConfig::~AIPathfinderConfig() = default; 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 bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const
{ {
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos()); const CGPathNode * ln = ai->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos()); const CGPathNode * rn = ai->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
return ln->getCost() < rn->getCost(); return ln->getCost() < rn->getCost();
} }

View File

@ -96,7 +96,7 @@ float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const Goals::A
} }
else 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(); return pathInfo->getCost();
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -222,8 +222,6 @@ void CClient::initMapHandler()
CGI->mh = std::make_shared<CMapHandler>(gs->map); CGI->mh = std::make_shared<CMapHandler>(gs->map);
logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff()); logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff());
} }
pathCache.clear();
} }
void CClient::initPlayerEnvironments() 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() 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!"); 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 #if SCRIPTING_ENABLED
scripting::Pool * CClient::getGlobalContextPool() const scripting::Pool * CClient::getGlobalContextPool() const
{ {

View File

@ -149,11 +149,6 @@ public:
void battleFinished(const BattleID & battleID); void battleFinished(const BattleID & battleID);
void startPlayerBattleAction(const BattleID & battleID, PlayerColor color); 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 CCallback; //handling players actions
friend class CBattleCallback; //handling players actions friend class CBattleCallback; //handling players actions
@ -235,8 +230,5 @@ private:
#endif #endif
std::unique_ptr<events::EventBus> clientEventBus; std::unique_ptr<events::EventBus> clientEventBus;
mutable boost::mutex pathCacheMutex;
std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
void reinitScripting(); void reinitScripting();
}; };

View File

@ -168,7 +168,6 @@ void ApplyClientNetPackVisitor::visitSetMana(SetMana & pack)
void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack) void ApplyClientNetPackVisitor::visitSetMovePoints(SetMovePoints & pack)
{ {
const CGHeroInstance *h = cl.getHero(pack.hid); const CGHeroInstance *h = cl.getHero(pack.hid);
cl.updatePath(h);
callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h); callInterfaceIfPresent(cl, h->tempOwner, &IGameEventsReceiver::heroMovePointsChanged, h);
} }
@ -194,7 +193,7 @@ void ApplyClientNetPackVisitor::visitFoWChange(FoWChange & pack)
i.second->tileHidden(pack.tiles); i.second->tileHidden(pack.tiles);
} }
} }
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
} }
static void dispatchGarrisonChange(CClient & cl, ObjectInstanceID army1, ObjectInstanceID army2) static void dispatchGarrisonChange(CClient & cl, ObjectInstanceID army1, ObjectInstanceID army2)
@ -235,33 +234,21 @@ void ApplyClientNetPackVisitor::visitSetStackType(SetStackType & pack)
void ApplyClientNetPackVisitor::visitEraseStack(EraseStack & pack) void ApplyClientNetPackVisitor::visitEraseStack(EraseStack & pack)
{ {
dispatchGarrisonChange(cl, pack.army, ObjectInstanceID()); 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) void ApplyClientNetPackVisitor::visitSwapStacks(SwapStacks & pack)
{ {
dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy); 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) void ApplyClientNetPackVisitor::visitInsertNewStack(InsertNewStack & pack)
{ {
dispatchGarrisonChange(cl, pack.army, ObjectInstanceID()); 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) void ApplyClientNetPackVisitor::visitRebalanceStacks(RebalanceStacks & pack)
{ {
dispatchGarrisonChange(cl, pack.srcArmy, pack.dstArmy); 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) void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
@ -272,12 +259,6 @@ void ApplyClientNetPackVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & p
? ObjectInstanceID() ? ObjectInstanceID()
: pack.moves[0].dstArmy; : pack.moves[0].dstArmy;
dispatchGarrisonChange(cl, pack.moves[0].srcArmy, destArmy); 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) void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
{ {
cl.updatePath(pack.artHolder);
for(const auto & slotErase : pack.posPack) for(const auto & slotErase : pack.posPack)
callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase)); 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); callInterfaceIfPresent(cl, pack.interfaceOwner, &IGameEventsReceiver::askToAssembleArtifact, dstLoc);
if(pack.interfaceOwner != dstOwner) if(pack.interfaceOwner != dstOwner)
callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::artifactMoved, srcLoc, dstLoc); 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) void ApplyClientNetPackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
{ {
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactAssembled, pack.al); 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) void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
{ {
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactDisassembled, pack.al); 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) void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
@ -374,7 +347,7 @@ void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack) void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
{ {
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
if(pack.newWeekNotification) if(pack.newWeekNotification)
{ {
@ -387,7 +360,8 @@ void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack) void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
{ {
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
switch(pack.who) switch(pack.who)
{ {
case GiveBonus::ETarget::OBJECT: case GiveBonus::ETarget::OBJECT:
@ -423,7 +397,7 @@ void ApplyClientNetPackVisitor::visitChangeObjPos(ChangeObjPos & pack)
CGI->mh->onObjectFadeIn(obj, pack.initiator); CGI->mh->onObjectFadeIn(obj, pack.initiator);
CGI->mh->waitForOngoingAnimations(); CGI->mh->waitForOngoingAnimations();
} }
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
} }
void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack) void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
@ -490,7 +464,6 @@ void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface
void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack) void ApplyClientNetPackVisitor::visitRemoveBonus(RemoveBonus & pack)
{ {
cl.invalidatePaths();
switch(pack.who) switch(pack.who)
{ {
case GiveBonus::ETarget::OBJECT: case GiveBonus::ETarget::OBJECT:
@ -531,7 +504,8 @@ void ApplyFirstClientNetPackVisitor::visitRemoveObject(RemoveObject & pack)
void ApplyClientNetPackVisitor::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++) for(auto i=cl.playerint.begin(); i!=cl.playerint.end(); i++)
i->second->objectRemovedAfter(); i->second->objectRemovedAfter();
} }
@ -561,7 +535,7 @@ void ApplyFirstClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack) void ApplyClientNetPackVisitor::visitTryMoveHero(TryMoveHero & pack)
{ {
const CGHeroInstance *h = cl.getHero(pack.id); const CGHeroInstance *h = cl.getHero(pack.id);
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
if(CGI->mh) if(CGI->mh)
{ {
@ -976,7 +950,8 @@ void ApplyClientNetPackVisitor::visitPlayerMessageClient(PlayerMessageClient & p
void ApplyClientNetPackVisitor::visitAdvmapSpellCast(AdvmapSpellCast & pack) void ApplyClientNetPackVisitor::visitAdvmapSpellCast(AdvmapSpellCast & pack)
{ {
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
auto caster = cl.getHero(pack.casterID); auto caster = cl.getHero(pack.casterID);
if(caster) if(caster)
//consider notifying other interfaces that see hero? //consider notifying other interfaces that see hero?
@ -1068,7 +1043,7 @@ void ApplyClientNetPackVisitor::visitCenterView(CenterView & pack)
void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack) void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
{ {
cl.invalidatePaths(); callAllInterfaces(cl, &CGameInterface::invalidatePaths);
const CGObjectInstance *obj = pack.newObject; const CGObjectInstance *obj = pack.newObject;
if(CGI->mh) if(CGI->mh)
@ -1101,5 +1076,5 @@ void ApplyClientNetPackVisitor::visitSetAvailableArtifacts(SetAvailableArtifacts
void ApplyClientNetPackVisitor::visitEntitiesChanged(EntitiesChanged & pack) 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) bool PlayerLocalState::setPath(const CGHeroInstance * h, const int3 & destination)
{ {
CGPath path; 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) paths.erase(h); //invalidate previously possible path if selected (before other hero blocked only path / fly spell expired)
syncronizeState(); syncronizeState();

View File

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

View File

@ -107,6 +107,9 @@ void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h) void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
{ {
if (h)
LOCPLINT->localState->verifyPath(h);
widget->getHeroList()->updateElement(h); widget->getHeroList()->updateElement(h);
if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents()) if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
@ -546,7 +549,7 @@ void AdventureMapInterface::onTileLeftClicked(const int3 &targetPosition)
{ {
isHero = true; 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 if(currentHero == topBlocking) //clicked selected hero
{ {
LOCPLINT->openHeroWindow(currentHero); 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> 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, }; 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); assert(pathNode);
if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info 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, "maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true, "updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true, "useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true, "openMap": true,
"allowObjectGraph": false, "allowObjectGraph": false,
"pathfinderBucketsCount" : 3, "pathfinderBucketsCount" : 3,
@ -63,6 +64,7 @@
"maxGoldPressure" : 0.3, "maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true, "updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true, "useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true, "openMap": true,
"allowObjectGraph": false, "allowObjectGraph": false,
"pathfinderBucketsCount" : 3, "pathfinderBucketsCount" : 3,
@ -84,6 +86,7 @@
"maxGoldPressure" : 0.3, "maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true, "updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true, "useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true, "openMap": true,
"allowObjectGraph": false, "allowObjectGraph": false,
"pathfinderBucketsCount" : 3, "pathfinderBucketsCount" : 3,
@ -105,6 +108,7 @@
"maxGoldPressure" : 0.3, "maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true, "updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true, "useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true, "openMap": true,
"allowObjectGraph": false, "allowObjectGraph": false,
"pathfinderBucketsCount" : 3, "pathfinderBucketsCount" : 3,
@ -126,6 +130,7 @@
"maxGoldPressure" : 0.3, "maxGoldPressure" : 0.3,
"updateHitmapOnTileReveal" : true, "updateHitmapOnTileReveal" : true,
"useTroopsFromGarrisons" : true, "useTroopsFromGarrisons" : true,
"useOneWayMonoliths" : false,
"openMap": true, "openMap": true,
"allowObjectGraph": false, "allowObjectGraph": false,
"pathfinderBucketsCount" : 3, "pathfinderBucketsCount" : 3,

View File

@ -473,7 +473,7 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getVisitableObjs(int3
for(const CGObjectInstance * obj : t->visitableObjects) 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); 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); 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); gs->calculatePaths(config);
} }
void CGameInfoCallback::calculatePaths( const CGHeroInstance *hero, CPathsInfo &out)
{
gs->calculatePaths(hero, out);
}
const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
{ {
return gs->map->artInstances.at(aid.num); 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 std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
virtual bool isInTheMap(const int3 &pos) 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 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 std::shared_ptr<PathfinderConfig> & config) const;
virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out);
virtual EDiggingStatus getTileDigStatus(int3 tile, bool verbose = true) const; virtual EDiggingStatus getTileDigStatus(int3 tile, bool verbose = true) const;
//town //town

View File

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

View File

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

View File

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

View File

@ -96,8 +96,7 @@ public:
void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override; void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2) 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 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) const override;
void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) override;
int3 guardingCreaturePosition (int3 pos) const override; int3 guardingCreaturePosition (int3 pos) const override;
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const; 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_) : sizes(Sizes), hero(hero_)
{ {
nodes.resize(boost::extents[ELayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y]); nodes.resize(boost::extents[ELayer::NUM_LAYERS][sizes.z][sizes.x][sizes.y]);
heroBonusTreeVersion = hero->getTreeVersion();
} }
CPathsInfo::~CPathsInfo() = default; CPathsInfo::~CPathsInfo() = default;

View File

@ -188,6 +188,8 @@ struct DLL_LINKAGE CPathsInfo
const CGHeroInstance * hero; const CGHeroInstance * hero;
int3 hpos; int3 hpos;
int3 sizes; 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] boost::multi_array<CGPathNode, 4> nodes; //[layer][level][w][h]
CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); 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() = 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()) : 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) CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
{ {
if (!pathfinderHelper)
pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, hero, options);
return pathfinderHelper.get(); return pathfinderHelper.get();
} }

View File

@ -108,9 +108,10 @@ class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig
{ {
private: private:
std::unique_ptr<CPathfinderHelper> pathfinderHelper; std::unique_ptr<CPathfinderHelper> pathfinderHelper;
const CGHeroInstance * hero;
public: public:
SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero); SingleHeroPathfinderConfig(CPathsInfo & out, const CGameInfoCallback * gs, const CGHeroInstance * hero);
virtual ~SingleHeroPathfinderConfig(); virtual ~SingleHeroPathfinderConfig();
CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override; 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); static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT);
const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector); const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector);
roughTerrainDiscountValue = bonuses->getFirst(daySelector) != nullptr; roughTerrainDiscountValue = bonuses->valOfBonuses(daySelector);
} }
{ {