diff --git a/AI/VCAI/FuzzyEngines.cpp b/AI/VCAI/FuzzyEngines.cpp index 4aaa7215c..3891de9c2 100644 --- a/AI/VCAI/FuzzyEngines.cpp +++ b/AI/VCAI/FuzzyEngines.cpp @@ -50,15 +50,25 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army) double shootersStrenght = 0; ui32 maxSpeed = 0; + static const CSelector selectorSHOOTER = Selector::type(Bonus::SHOOTER); + static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER); + + static const CSelector selectorFLYING = Selector::type(Bonus::FLYING); + static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING); + + static const CSelector selectorSTACKS_SPEED = Selector::type(Bonus::STACKS_SPEED); + static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED); + for(auto s : army->Slots()) { bool walker = true; - if(s.second->type->hasBonusOfType(Bonus::SHOOTER)) + const CCreature * creature = s.second->type; + if(creature->hasBonus(selectorSHOOTER, keySHOOTER)) { shootersStrenght += s.second->getPower(); walker = false; } - if(s.second->type->hasBonusOfType(Bonus::FLYING)) + if(creature->hasBonus(selectorFLYING, keyFLYING)) { flyersStrenght += s.second->getPower(); walker = false; @@ -66,7 +76,7 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army) if(walker) walkersStrenght += s.second->getPower(); - vstd::amax(maxSpeed, s.second->type->valOfBonuses(Bonus::STACKS_SPEED)); + vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED)); } armyStructure as; as.walkers = walkersStrenght / totalStrenght; @@ -79,12 +89,14 @@ armyStructure evaluateArmyStructure(const CArmedInstance * army) float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const { + const float maxMovePoints = (float)goal.hero->maxMovePoints(true); + if(goal.evaluationContext.movementCost != 0) { - return goal.evaluationContext.movementCost / (float)goal.hero->maxMovePoints(true); + return goal.evaluationContext.movementCost / maxMovePoints; } - return distanceToTile(goal.hero.h, goal.tile) / (float)goal.hero->maxMovePoints(true); + return distanceToTile(goal.hero.h, goal.tile) / maxMovePoints; } TacticalAdvantageEngine::TacticalAdvantageEngine() @@ -437,4 +449,4 @@ float VisitTileEngine::evaluate(Goals::VisitTile & goal) } assert(goal.priority >= 0); return goal.priority; -} \ No newline at end of file +} diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 9921fa211..4bb639613 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -244,7 +244,8 @@ TSubgoal Explore::explorationBestNeighbour(int3 hpos, int radius, HeroPtr h) con auto distance = hpos.dist2d(tile); // diagonal movement opens more tiles but spends more mp int tilesDiscovered = howManyTilesWillBeDiscovered(tile, radius, cbp, ts, aip, h); - dstToRevealedTiles[tile] = tilesDiscovered / distance; + if(tilesDiscovered > 0) + dstToRevealedTiles[tile] = tilesDiscovered / distance; } } } @@ -371,9 +372,9 @@ TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const int radius = h->getSightRadius(); int3 hpos = h->visitablePos(); - //look for nearby objs -> visit them if they're close enouh + //look for nearby objs -> visit them if they're close enough const int DIST_LIMIT = 3; - const int MP_LIMIT = DIST_LIMIT * 150; // aproximate cost of diagonal movement + const int MP_LIMIT = DIST_LIMIT * 150; // approximate cost of diagonal movement std::vector nearbyVisitableObjs; for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h index cc120ee35..813c48468 100644 --- a/AI/VCAI/Goals/Explore.h +++ b/AI/VCAI/Goals/Explore.h @@ -54,9 +54,9 @@ namespace Goals bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; void getVisibleNeighbours( - const std::vector & tiles, - std::vector & out, - CCallback * cbp, + const std::vector & tiles, + std::vector & out, + CCallback * cbp, const TeamState * ts) const; int howManyTilesWillBeDiscovered( diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index b6645f017..d8a5fba90 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -14,16 +14,61 @@ #include "../../../lib/mapping/CMap.h" #include "../../../lib/mapObjects/MapObjects.h" +#include "../../../lib/PathfinderUtil.h" +#include "../../../lib/CPlayerState.h" extern boost::thread_specific_ptr cb; + AINodeStorage::AINodeStorage(const int3 & Sizes) : sizes(Sizes) { nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]); } -AINodeStorage::~AINodeStorage() +AINodeStorage::~AINodeStorage() = default; + +void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) { + //TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline + + int3 pos; + const int3 sizes = gs->getMapSize(); + const auto & fow = static_cast(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap; + const PlayerColor player = hero->tempOwner; + + //make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching) + const bool useFlying = options.useFlying; + const bool useWaterWalking = options.useWaterWalking; + + for(pos.x=0; pos.x < sizes.x; ++pos.x) + { + for(pos.y=0; pos.y < sizes.y; ++pos.y) + { + for(pos.z=0; pos.z < sizes.z; ++pos.z) + { + const TerrainTile * tile = &gs->map->getTile(pos); + switch(tile->terType) + { + case ETerrainType::ROCK: + break; + + case ETerrainType::WATER: + resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useFlying) + resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useWaterWalking) + resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + break; + + default: + resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + if(useFlying) + resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility(pos, tile, fow, player, gs)); + break; + } + } + } + } } const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const @@ -97,7 +142,8 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf { const AIPathNode * srcNode = getAINode(source.node); - updateAINode(destination.node, [&](AIPathNode * dstNode) { + updateAINode(destination.node, [&](AIPathNode * dstNode) + { dstNode->moveRemains = destination.movementLeft; dstNode->turns = destination.turn; dstNode->danger = srcNode->danger; @@ -172,18 +218,18 @@ std::vector AINodeStorage::calculateTeleportations( if(source.isNodeObjectVisitable()) { - auto accessibleExits = pathfinderHelper->getTeleportExits(source); - auto srcNode = getAINode(source.node); + auto accessibleExits = pathfinderHelper->getTeleportExits(source); + auto srcNode = getAINode(source.node); - for(auto & neighbour : accessibleExits) - { - auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); + for(auto & neighbour : accessibleExits) + { + auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); - if(!node) - continue; + if(!node) + continue; - neighbours.push_back(node.get()); - } + neighbours.push_back(node.get()); + } } if(hero->getPosition(false) == source.coord) @@ -287,7 +333,6 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode destinationNode->chainMask, node.moveRemains - destinationNode->moveRemains); #endif - return true; } } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 4de24f4a8..007912c05 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -24,7 +24,7 @@ public: virtual void applyOnDestination( HeroPtr hero, - CDestinationNodeInfo & destination, + CDestinationNodeInfo & destination, const PathNodeInfo & source, AIPathNode * dstMode, const AIPathNode * srcNode) const @@ -76,6 +76,9 @@ private: boost::multi_array nodes; const CGHeroInstance * hero; + STRONG_INLINE + void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility); + public: /// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one. static const int NUM_CHAINS = 3; @@ -89,8 +92,9 @@ public: AINodeStorage(const int3 & sizes); ~AINodeStorage(); + void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override; + virtual CGPathNode * getInitialNode() override; - virtual void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) override; virtual std::vector calculateNeighbours( const PathNodeInfo & source, diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp index 932613d87..056bd9640 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp @@ -76,12 +76,14 @@ namespace AIPathfinding bool isAffordableBy(HeroPtr hero, const AIPathNode * source) const { +#ifdef VCMI_TRACE_PATHFINDER logAi->trace( "Hero %s has %d mana and needed %d and already spent %d", hero->name, hero->mana, getManaCost(hero), source->manaCost); +#endif return hero->mana >= source->manaCost + getManaCost(hero); } @@ -148,7 +150,9 @@ namespace AIPathfinding if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat)) { +#ifdef VCMI_TRACE_PATHFINDER logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString()); +#endif } } } @@ -242,15 +246,17 @@ namespace AIPathfinding } else { +#ifdef VCMI_TRACE_PATHFINDER logAi->trace( "Special transition node already allocated. Blocked moving %s -> %s", source.coord.toString(), destination.coord.toString()); +#endif } } else { - logAi->trace( + logAi->debug( "Can not allocate special transition node while moving %s -> %s", source.coord.toString(), destination.coord.toString()); @@ -330,10 +336,12 @@ namespace AIPathfinding auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size(); if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node)) { - //logAi->trace( - // "Bypass guard at destination while moving %s -> %s", - // source.coord.toString(), - // destination.coord.toString()); +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Bypass guard at destination while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif return; } @@ -346,10 +354,12 @@ namespace AIPathfinding if(!battleNodeOptional) { - //logAi->trace( - // "Can not allocate battle node while moving %s -> %s", - // source.coord.toString(), - // destination.coord.toString()); +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Can not allocate battle node while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif destination.blocked = true; @@ -360,11 +370,12 @@ namespace AIPathfinding if(battleNode->locked) { - //logAi->trace( - // "Block bypass guard at destination while moving %s -> %s", - // source.coord.toString(), - // destination.coord.toString()); - +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Block bypass guard at destination while moving %s -> %s", + source.coord.toString(), + destination.coord.toString()); +#endif destination.blocked = true; return; @@ -382,13 +393,13 @@ namespace AIPathfinding } battleNode->specialAction = std::make_shared(destination.coord); - - //logAi->trace( - // "Begin bypass guard at destination with danger %s while moving %s -> %s", - // std::to_string(danger), - // source.coord.toString(), - // destination.coord.toString()); - +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Begin bypass guard at destination with danger %s while moving %s -> %s", + std::to_string(danger), + source.coord.toString(), + destination.coord.toString()); +#endif return; } @@ -428,11 +439,12 @@ namespace AIPathfinding if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node)) { - //logAi->trace( - // "Bypass src guard while moving from %s to %s", - // source.coord.toString(), - // destination.coord.toString()); - +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Bypass src guard while moving from %s to %s", + source.coord.toString(), + destination.coord.toString()); +#endif return; } @@ -462,12 +474,12 @@ namespace AIPathfinding { // we can not directly bypass objects, we need to interact with them first destination.node->theNodeBefore = source.node; - - //logAi->trace( - // "Link src node %s to destination node %s while bypassing visitable obj", - // source.coord.toString(), - // destination.coord.toString()); - +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace( + "Link src node %s to destination node %s while bypassing visitable obj", + source.coord.toString(), + destination.coord.toString()); +#endif return; } diff --git a/AI/VCAI/Pathfinding/PathfindingManager.cpp b/AI/VCAI/Pathfinding/PathfindingManager.cpp index 06c9309fc..f39d3f0b7 100644 --- a/AI/VCAI/Pathfinding/PathfindingManager.cpp +++ b/AI/VCAI/Pathfinding/PathfindingManager.cpp @@ -124,14 +124,16 @@ Goals::TGoalVec PathfindingManager::findPath( std::vector chainInfo = pathfinder->getPathInfo(hero, dest); +#ifdef VCMI_TRACE_PATHFINDER logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString()); +#endif for(auto path : chainInfo) { int3 firstTileToGet = path.firstTileToGet(); - +#ifdef VCMI_TRACE_PATHFINDER logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString()); - +#endif if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet)) { danger = path.getTotalDanger(hero); @@ -158,9 +160,9 @@ Goals::TGoalVec PathfindingManager::findPath( solution->evaluationContext.danger = danger; solution->evaluationContext.movementCost += path.movementCost(); - +#ifdef VCMI_TRACE_PATHFINDER logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name()); - +#endif result.push_back(solution); continue; @@ -178,7 +180,9 @@ Goals::TGoalVec PathfindingManager::findPath( if(allowGatherArmy && danger > 0) { //we need to get army in order to conquer that place +#ifdef VCMI_TRACE_PATHFINDER logAi->trace("Gather army for %s, value=%s", hero->name, std::to_string(danger)); +#endif result.push_back(sptr(Goals::GatherArmy(danger * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); } diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 2effd4e3b..a1387448d 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -1313,7 +1313,7 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::option if(movementCostLimit && movementCostLimit.get() < path.movementCost()) return false; - if(ai->isGoodForVisit(obj, h, path)) + if(isGoodForVisit(obj, h, path)) return true; } @@ -1330,7 +1330,7 @@ bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath return false; if (obj->wasVisited(playerID)) return false; - if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj)) + if (cb->getPlayerRelations(playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj)) return false; // Otherwise we flag or get weekly resources / creatures if (!isSafeToVisit(h, pos)) return false; diff --git a/CCallback.cpp b/CCallback.cpp index c35b83569..b01c2f36d 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -286,7 +286,7 @@ bool CCallback::canMoveBetween(const int3 &a, const int3 &b) return gs->map->canMoveBetween(a, b); } -const CPathsInfo * CCallback::getPathsInfo(const CGHeroInstance *h) +std::shared_ptr CCallback::getPathsInfo(const CGHeroInstance * h) { return cl->getPathsInfo(h); } diff --git a/CCallback.h b/CCallback.h index 1a6c55590..739b13564 100644 --- a/CCallback.h +++ b/CCallback.h @@ -106,7 +106,7 @@ public: //client-specific functionalities (pathfinding) virtual bool canMoveBetween(const int3 &a, const int3 &b); virtual int3 getGuardingCreaturePosition(int3 tile); - virtual const CPathsInfo * getPathsInfo(const CGHeroInstance *h); + virtual std::shared_ptr getPathsInfo(const CGHeroInstance * h); virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); diff --git a/client/Client.cpp b/client/Client.cpp index 932471c31..583226bf2 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -109,7 +109,6 @@ public: CClient::CClient() { waitingRequest.clear(); - pathInfo = nullptr; applier = std::make_shared>(); registerTypesClientPacks1(*applier); registerTypesClientPacks2(*applier); @@ -316,7 +315,8 @@ void CClient::initMapHandler() CGI->mh->init(); logNetwork->trace("Initializing mapHandler (together): %d ms", CSH->th->getDiff()); } - pathInfo = make_unique(getMapSize()); + + pathCache.clear(); } void CClient::initPlayerInterfaces() @@ -612,20 +612,30 @@ void CClient::waitForMoveAndSend(PlayerColor color) void CClient::invalidatePaths() { - // turn pathfinding info into invalid. It will be regenerated later - boost::unique_lock pathLock(pathInfo->pathMx); - pathInfo->hero = nullptr; + boost::unique_lock pathLock(pathCacheMutex); + pathCache.clear(); } -const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance * h) +std::shared_ptr CClient::getPathsInfo(const CGHeroInstance * h) { assert(h); - boost::unique_lock pathLock(pathInfo->pathMx); - if(pathInfo->hero != h) + boost::unique_lock pathLock(pathCacheMutex); + + auto iter = pathCache.find(h); + + if(iter == std::end(pathCache)) { - gs->calculatePaths(h, *pathInfo.get()); + std::shared_ptr paths = std::make_shared(getMapSize(), h); + + gs->calculatePaths(h, *paths.get()); + + pathCache[h] = paths; + return paths; + } + else + { + return iter->second; } - return pathInfo.get(); } PlayerColor CClient::getLocalPlayer() const diff --git a/client/Client.h b/client/Client.h index eeff0d36b..6e0cabf00 100644 --- a/client/Client.h +++ b/client/Client.h @@ -99,7 +99,9 @@ public: class CClient : public IGameCallback { std::shared_ptr> applier; - std::unique_ptr pathInfo; + + mutable boost::mutex pathCacheMutex; + std::map> pathCache; std::map> playerActionThreads; void waitForMoveAndSend(PlayerColor color); @@ -150,7 +152,7 @@ public: void stopAllBattleActions(); void invalidatePaths(); - const CPathsInfo * getPathsInfo(const CGHeroInstance * h); + std::shared_ptr getPathsInfo(const CGHeroInstance * h); virtual PlayerColor getLocalPlayer() const override; friend class CCallback; //handling players actions diff --git a/client/VCMI_client.cbp b/client/VCMI_client.cbp index 159a08a47..dda2fc3f8 100644 --- a/client/VCMI_client.cbp +++ b/client/VCMI_client.cbp @@ -13,6 +13,7 @@