diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 6d204e3a4..908f6c6cd 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -224,7 +224,11 @@ void ObjectClusterizer::clusterize() ai->memory->visitableObjs.begin(), ai->memory->visitableObjs.end()); +#if NKAI_TRACE_LEVEL == 0 parallel_for(blocked_range(0, objs.size()), [&](const blocked_range & r) +#else + blocked_range r(0, objs.size()); +#endif { auto priorityEvaluator = ai->priorityEvaluators->acquire(); @@ -255,9 +259,9 @@ void ObjectClusterizer::clusterize() } std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool - { - return p1.movementCost() < p2.movementCost(); - }); + { + return p1.movementCost() < p2.movementCost(); + }); if(vstd::contains(ignoreObjects, obj->ID)) { @@ -348,7 +352,11 @@ void ObjectClusterizer::clusterize() #endif } } +#if NKAI_TRACE_LEVEL == 0 }); +#else + } +#endif logAi->trace("Near objects count: %i", nearObjects.objects.size()); logAi->trace("Far objects count: %i", farObjects.objects.size()); diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 97998d9ae..e71ed1abc 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -42,6 +42,8 @@ void Nullkiller::init(std::shared_ptr cb, PlayerColor playerID) this->cb = cb; this->playerID = playerID; + baseGraph.reset(); + priorityEvaluator.reset(new PriorityEvaluator(this)); priorityEvaluators.reset( new SharedPool( diff --git a/AI/Nullkiller/Goals/AdventureSpellCast.cpp b/AI/Nullkiller/Goals/AdventureSpellCast.cpp index 7e62e7d42..17b24c0c5 100644 --- a/AI/Nullkiller/Goals/AdventureSpellCast.cpp +++ b/AI/Nullkiller/Goals/AdventureSpellCast.cpp @@ -39,12 +39,18 @@ void AdventureSpellCast::accept(AIGateway * ai) if(hero->mana < hero->getSpellCost(spell)) throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated()); - if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero) - throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); if(town && spellID == SpellID::TOWN_PORTAL) { ai->selectedObject = town->id; + + if(town->visitingHero && town->tempOwner == ai->playerID && !town->getUpperArmy()->stacksCount()) + { + ai->myCb->swapGarrisonHero(town); + } + + if(town->visitingHero) + throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated()); } auto wait = cb->waitTillRealize; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 310f9d867..6c6f59d87 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -1123,14 +1123,14 @@ void AINodeStorage::calculateTownPortal( { for(const CGTownInstance * targetTown : towns) { - // TODO: allow to hide visiting hero in garrison - if(targetTown->visitingHero && maskMap.find(targetTown->visitingHero.get()) != maskMap.end()) + if(targetTown->visitingHero + && targetTown->getUpperArmy()->stacksCount() + && maskMap.find(targetTown->visitingHero.get()) != maskMap.end()) { auto basicMask = maskMap.at(targetTown->visitingHero.get()); - bool heroIsInChain = (actor->chainMask & basicMask) != 0; bool sameActorInTown = actor->chainMask == basicMask; - if(sameActorInTown || !heroIsInChain) + if(!sameActorInTown) continue; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index f449b146d..556402d76 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -11,7 +11,8 @@ #pragma once #define NKAI_PATHFINDER_TRACE_LEVEL 0 -#define NKAI_TRACE_LEVEL 2 +#define NKAI_GRAPH_TRACE_LEVEL 0 +#define NKAI_TRACE_LEVEL 0 #include "../../../lib/pathfinder/CGPathNode.h" #include "../../../lib/pathfinder/INodeStorage.h" diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index bc208bcdb..10d7f9644 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -48,9 +48,10 @@ std::vector AIPathfinder::getPathInfo(const int3 & tile, bool includeGra { for(auto hero : cb->getHeroesInfo()) { - auto & graph = heroGraphs.at(hero->id); + auto graph = heroGraphs.find(hero->id); - graph.addChainInfo(info, tile, hero, ai); + if(graph != heroGraphs.end()) + graph->second.addChainInfo(info, tile, hero, ai); } } @@ -140,10 +141,10 @@ void AIPathfinder::updateGraphs(std::map heroe parallel_for(blocked_range(0, heroesVector.size()), [&](const blocked_range & r) { for(auto i = r.begin(); i != r.end(); i++) - heroGraphs[heroesVector[i]->id].calculatePaths(heroesVector[i], ai); + heroGraphs.at(heroesVector[i]->id).calculatePaths(heroesVector[i], ai); }); -#if NKAI_TRACE_LEVEL >= 2 +#if NKAI_GRAPH_TRACE_LEVEL >= 1 for(auto hero : heroes) { heroGraphs[hero.first->id].dumpToLog(); diff --git a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp index ed1d4c698..afd046bc8 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinderConfig.cpp @@ -30,7 +30,7 @@ namespace AIPathfinding std::vector> rules = { std::make_shared(cb, ai, nodeStorage), std::make_shared(), - std::make_shared(nodeStorage), + std::make_shared(nodeStorage, allowBypassObjects), std::make_shared(), std::make_shared(nodeStorage), std::make_shared(cb, nodeStorage, allowBypassObjects) diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp index f15b69ee4..00d52f661 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.cpp @@ -13,6 +13,7 @@ #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../Engine/Nullkiller.h" +#include "../../../lib/logging/VisualLogger.h" namespace NKAI { @@ -64,6 +65,14 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) foreach_tile_pos(cb.get(), [&](const CPlayerSpecificInfoCallback * cb, const int3 & pos) { + if(nodes.find(pos) != nodes.end()) + return; + + auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid(); + + if(guarded) + return; + auto paths = ai->pathfinder->getPathInfo(pos); for(AIPath & path1 : paths) @@ -76,9 +85,25 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) auto obj1 = actorObjectMap[path1.targetHero]; auto obj2 = actorObjectMap[path2.targetHero]; - nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( + auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true); + + auto updated = nodes[obj1->visitablePos()].connections[obj2->visitablePos()].update( path1.movementCost() + path2.movementCost(), - path1.getPathDanger() + path2.getPathDanger()); + danger); + +#if NKAI_GRAPH_TRACE_LEVEL >= 2 + if(updated) + { + logAi->trace( + "Connected %s[%s] -> %s[%s] through [%s], cost %2f", + obj1->getObjectName(), obj1->visitablePos().toString(), + obj2->getObjectName(), obj2->visitablePos().toString(), + pos.toString(), + path1.movementCost() + path2.movementCost()); + } +#else + (void)updated; +#endif } } }); @@ -87,6 +112,10 @@ void ObjectGraph::updateGraph(const Nullkiller * ai) { delete h.first; } + +#if NKAI_GRAPH_TRACE_LEVEL >= 1 + dumpToLog("graph"); +#endif } void ObjectGraph::addObject(const CGObjectInstance * obj) @@ -104,7 +133,7 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) } } - for(auto node : nodes) + for(auto & node : nodes) { auto pos = node.first; auto paths = ai->pathfinder->getPathInfo(pos); @@ -124,6 +153,29 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai) } } +void ObjectGraph::dumpToLog(std::string visualKey) const +{ + logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder) + { + for(auto & tile : nodes) + { + for(auto & node : tile.second.connections) + { +#if NKAI_GRAPH_TRACE_LEVEL >= 2 + logAi->trace( + "%s -> %s: %f !%d", + node.first.toString(), + tile.first.toString(), + node.second.cost, + node.second.danger); +#endif + + logBuilder.addLine(node.first, tile.first); + } + } + }); +} + bool GraphNodeComparer::operator()(const GraphPathNodePointer & lhs, const GraphPathNodePointer & rhs) const { return pathNodes.at(lhs.coord)[lhs.nodeType].cost > pathNodes.at(rhs.coord)[rhs.nodeType].cost; @@ -134,6 +186,7 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil graph = *ai->baseGraph; graph.connectHeroes(ai); + visualKey = std::to_string(ai->playerID) + ":" + targetHero->getNameTranslated(); pathNodes.clear(); GraphNodeComparer cmp(pathNodes); @@ -153,11 +206,17 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o) { - auto targetPointer = GraphPathNodePointer(target, pos.nodeType); + auto graphNode = graph.getNode(target); + auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType; + + auto targetPointer = GraphPathNodePointer(target, targetNodeType); auto & targetNode = getNode(targetPointer); if(targetNode.tryUpdate(pos, node, o)) { + if(graph.getNode(target).objTypeID == Obj::HERO) + return; + if(targetNode.isInQueue) { pq.increase(targetNode.handle); @@ -174,21 +233,28 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil void GraphPaths::dumpToLog() const { - for(auto & tile : pathNodes) - { - for(auto & node : tile.second) + logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder) { - if(!node.previous.valid()) - continue; + for(auto & tile : pathNodes) + { + for(auto & node : tile.second) + { + if(!node.previous.valid()) + continue; - logAi->trace( - "%s -> %s: %f !%d", - node.previous.coord.toString(), - tile.first.toString(), - node.cost, - node.danger); - } - } +#if NKAI_GRAPH_TRACE_LEVEL >= 2 + logAi->trace( + "%s -> %s: %f !%d", + node.previous.coord.toString(), + tile.first.toString(), + node.cost, + node.danger); +#endif + + logBuilder.addLine(node.previous.coord, tile.first); + } + } + }); } bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link) @@ -197,6 +263,11 @@ bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathN if(newCost < cost) { + if(nodeType < pos.nodeType) + { + logAi->error("Linking error"); + } + previous = pos; danger = prev.danger + link.danger; cost = newCost; @@ -214,7 +285,7 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe if(nodes == pathNodes.end()) return; - for(auto & node : (*nodes).second) + for(auto & node : nodes->second) { if(!node.reachable()) continue; @@ -236,6 +307,9 @@ void GraphPaths::addChainInfo(std::vector & paths, int3 tile, const CGHe auto currentNode = currentTile->second[current.nodeType]; + if(currentNode.cost == 0) + break; + allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE; vstd::amax(danger, currentNode.danger); vstd::amax(cost, currentNode.cost); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraph.h b/AI/Nullkiller/Pathfinding/ObjectGraph.h index 1dc135a68..11b9e84e3 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraph.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraph.h @@ -23,19 +23,24 @@ struct ObjectLink float cost = 100000; // some big number uint64_t danger = 0; - void update(float newCost, uint64_t newDanger) + bool update(float newCost, uint64_t newDanger) { if(cost > newCost) { cost = newCost; danger = newDanger; + + return true; } + + return false; } }; struct ObjectNode { ObjectInstanceID objID; + MapObjectID objTypeID; bool objectExists; std::unordered_map connections; @@ -43,6 +48,7 @@ struct ObjectNode { objectExists = true; objID = obj->id; + objTypeID = obj->ID; } }; @@ -54,15 +60,21 @@ public: void updateGraph(const Nullkiller * ai); void addObject(const CGObjectInstance * obj); void connectHeroes(const Nullkiller * ai); + void dumpToLog(std::string visualKey) const; template void iterateConnections(const int3 & pos, Func fn) { - for(auto connection : nodes.at(pos).connections) + for(auto & connection : nodes.at(pos).connections) { fn(connection.first, connection.second); } } + + const ObjectNode & getNode(int3 tile) const + { + return nodes.at(tile); + } }; struct GraphPathNode; @@ -134,6 +146,7 @@ class GraphPaths { ObjectGraph graph; GraphNodeStorage pathNodes; + std::string visualKey; public: void calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai); @@ -143,7 +156,11 @@ public: private: GraphPathNode & getNode(const GraphPathNodePointer & pos) { - return pathNodes[pos.coord][pos.nodeType]; + auto & node = pathNodes[pos.coord][pos.nodeType]; + + node.nodeType = pos.nodeType; + + return node; } }; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 9119a6759..9aa5719ae 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -48,7 +48,7 @@ namespace AIPathfinding return; } - + if(!allowBypassObjects) return; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp index 8886f5233..3f0acd1b2 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.cpp @@ -14,8 +14,10 @@ namespace NKAI { namespace AIPathfinding { - AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr nodeStorage) - : nodeStorage(nodeStorage) + AIMovementToDestinationRule::AIMovementToDestinationRule( + std::shared_ptr nodeStorage, + bool allowBypassObjects) + : nodeStorage(nodeStorage), allowBypassObjects(allowBypassObjects) { } @@ -37,15 +39,30 @@ namespace AIPathfinding return; } - if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->getAINode(source.node)->actor->allowBattle) + if(blocker == BlockingReason::SOURCE_GUARDED) { + auto actor = nodeStorage->getAINode(source.node)->actor; + + if(!allowBypassObjects) + { + if (source.node->getCost() == 0) + return; + + // when actor represents moster graph node, we need to let him escape monster + if(!destination.guarded && cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition) + return; + } + + if(actor->allowBattle) + { #if NKAI_PATHFINDER_TRACE_LEVEL >= 1 - logAi->trace( - "Bypass src guard while moving from %s to %s", - source.coord.toString(), - destination.coord.toString()); + logAi->trace( + "Bypass src guard while moving from %s to %s", + source.coord.toString(), + destination.coord.toString()); #endif - return; + return; + } } destination.blocked = true; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h index d33624514..a1c3015ac 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementToDestinationRule.h @@ -24,9 +24,10 @@ namespace AIPathfinding { private: std::shared_ptr nodeStorage; + bool allowBypassObjects; public: - AIMovementToDestinationRule(std::shared_ptr nodeStorage); + AIMovementToDestinationRule(std::shared_ptr nodeStorage, bool allowBypassObjects); virtual void process( const PathNodeInfo & source, diff --git a/lib/logging/VisualLogger.cpp b/lib/logging/VisualLogger.cpp index a29fa40d4..ae0f2493b 100644 --- a/lib/logging/VisualLogger.cpp +++ b/lib/logging/VisualLogger.cpp @@ -21,7 +21,9 @@ void VisualLogger::updateWithLock(std::string channel, std::function