1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-12 02:28:11 +02:00

NKAI: graph add battle layer

This commit is contained in:
Andrii Danylchenko 2024-02-03 12:20:59 +02:00
parent 047e076d05
commit b236384356
13 changed files with 176 additions and 47 deletions

View File

@ -224,7 +224,11 @@ void ObjectClusterizer::clusterize()
ai->memory->visitableObjs.begin(),
ai->memory->visitableObjs.end());
#if NKAI_TRACE_LEVEL == 0
parallel_for(blocked_range<size_t>(0, objs.size()), [&](const blocked_range<size_t> & r)
#else
blocked_range<size_t> 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());

View File

@ -42,6 +42,8 @@ void Nullkiller::init(std::shared_ptr<CCallback> cb, PlayerColor playerID)
this->cb = cb;
this->playerID = playerID;
baseGraph.reset();
priorityEvaluator.reset(new PriorityEvaluator(this));
priorityEvaluators.reset(
new SharedPool<PriorityEvaluator>(

View File

@ -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;

View File

@ -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;
}

View File

@ -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"

View File

@ -48,9 +48,10 @@ std::vector<AIPath> 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<const CGHeroInstance *, HeroRole> heroe
parallel_for(blocked_range<size_t>(0, heroesVector.size()), [&](const blocked_range<size_t> & 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();

View File

@ -30,7 +30,7 @@ namespace AIPathfinding
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
std::make_shared<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage),
std::make_shared<AIMovementToDestinationRule>(nodeStorage, allowBypassObjects),
std::make_shared<MovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage, allowBypassObjects)

View File

@ -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<AIPath> & 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<AIPath> & 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);

View File

@ -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<int3, ObjectLink> 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<typename Func>
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;
}
};

View File

@ -14,8 +14,10 @@ namespace NKAI
{
namespace AIPathfinding
{
AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage)
: nodeStorage(nodeStorage)
AIMovementToDestinationRule::AIMovementToDestinationRule(
std::shared_ptr<AINodeStorage> 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;

View File

@ -24,9 +24,10 @@ namespace AIPathfinding
{
private:
std::shared_ptr<AINodeStorage> nodeStorage;
bool allowBypassObjects;
public:
AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage);
AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage, bool allowBypassObjects);
virtual void process(
const PathNodeInfo & source,

View File

@ -21,7 +21,9 @@ void VisualLogger::updateWithLock(std::string channel, std::function<void(IVisua
mapLines[channel].clear();
func(VisualLogBuilder(mapLines[channel]));
VisualLogBuilder builder(mapLines[channel]);
func(builder);
}
void VisualLogger::visualize(ILogVisualizer & visulizer)