mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-20 20:23:03 +02:00
Merge pull request #3664 from vcmi/object-graph
NKAI: object graph improved and optimized
This commit is contained in:
commit
6381a6f601
@ -73,13 +73,25 @@ Goals::TGoalVec CaptureObjectsBehavior::getVisitGoals(const std::vector<AIPath>
|
||||
}
|
||||
|
||||
if(objToVisit && !shouldVisit(ai->nullkiller.get(), path.targetHero, objToVisit))
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Hero %s should not visit obj %s", path.targetHero->getNameTranslated(), objToVisit->getObjectName());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
auto hero = path.targetHero;
|
||||
auto danger = path.getTotalDanger();
|
||||
|
||||
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT && path.exchangeCount > 1)
|
||||
if(ai->nullkiller->heroManager->getHeroRole(hero) == HeroRole::SCOUT
|
||||
&& (path.getTotalDanger() == 0 || path.turn() > 0)
|
||||
&& path.exchangeCount > 1)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 2
|
||||
logAi->trace("Ignore path. Hero %s is SCOUT, chain used and no danger", path.targetHero->getNameTranslated());
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||
if(firstBlockedAction)
|
||||
|
@ -27,8 +27,8 @@ namespace NKAI
|
||||
{
|
||||
namespace AIPathfinding
|
||||
{
|
||||
const int BUCKET_COUNT = 5;
|
||||
const int BUCKET_SIZE = 3;
|
||||
const int BUCKET_COUNT = 3;
|
||||
const int BUCKET_SIZE = 5;
|
||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||
const int CHAIN_MAX_DEPTH = 4;
|
||||
}
|
||||
|
@ -20,13 +20,18 @@ namespace AIPathfinding
|
||||
{
|
||||
bool QuestAction::canAct(const AIPathNode * node) const
|
||||
{
|
||||
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
|
||||
{
|
||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
|
||||
return canAct(node->actor->hero);
|
||||
}
|
||||
|
||||
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
|
||||
|| questInfo.quest->checkQuest(node->actor->hero);
|
||||
bool QuestAction::canAct(const CGHeroInstance * hero) const
|
||||
{
|
||||
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
|
||||
{
|
||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(hero);
|
||||
}
|
||||
|
||||
return questInfo.quest->activeForPlayers.count(hero->getOwner())
|
||||
|| questInfo.quest->checkQuest(hero);
|
||||
}
|
||||
|
||||
Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const
|
||||
|
@ -30,6 +30,8 @@ namespace AIPathfinding
|
||||
|
||||
bool canAct(const AIPathNode * node) const override;
|
||||
|
||||
bool canAct(const CGHeroInstance * hero) const;
|
||||
|
||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||
|
||||
void execute(const CGHeroInstance * hero) const override;
|
||||
|
@ -15,10 +15,18 @@
|
||||
#include "../../../lib/mapping/CMap.h"
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/logging/VisualLogger.h"
|
||||
#include "Actions/QuestAction.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
struct ConnectionCostInfo
|
||||
{
|
||||
float totalCost = 0;
|
||||
float avg = 0;
|
||||
int connectionsCount = 0;
|
||||
};
|
||||
|
||||
class ObjectGraphCalculator
|
||||
{
|
||||
private:
|
||||
@ -34,10 +42,14 @@ private:
|
||||
public:
|
||||
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
|
||||
:ai(ai), target(target)
|
||||
{
|
||||
}
|
||||
|
||||
void setGraphObjects()
|
||||
{
|
||||
for(auto obj : ai->memory->visitableObjs)
|
||||
{
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO)
|
||||
if(obj && obj->isVisitable() && obj->ID != Obj::HERO && obj->ID != Obj::EVENT)
|
||||
{
|
||||
addObjectActor(obj);
|
||||
}
|
||||
@ -47,7 +59,70 @@ public:
|
||||
{
|
||||
addObjectActor(town);
|
||||
}
|
||||
}
|
||||
|
||||
void calculateConnections()
|
||||
{
|
||||
updatePaths();
|
||||
|
||||
foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
calculateConnections(pos);
|
||||
});
|
||||
|
||||
removeExtraConnections();
|
||||
}
|
||||
|
||||
void addMinimalDistanceJunctions()
|
||||
{
|
||||
foreach_tile_pos(ai->cb.get(), [this](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
if(target->hasNodeAt(pos))
|
||||
return;
|
||||
|
||||
if(ai->cb->getGuardingCreaturePosition(pos).valid())
|
||||
return;
|
||||
|
||||
ConnectionCostInfo currentCost = getConnectionsCost(pos);
|
||||
|
||||
if(currentCost.connectionsCount <= 2)
|
||||
return;
|
||||
|
||||
float neighborCost = currentCost.avg + 0.001f;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Checking junction %s", pos.toString());
|
||||
}
|
||||
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &neighborCost](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
auto costTotal = this->getConnectionsCost(neighbor);
|
||||
|
||||
if(costTotal.avg < neighborCost)
|
||||
{
|
||||
neighborCost = costTotal.avg;
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Better node found at %s", neighbor.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(currentCost.avg < neighborCost)
|
||||
{
|
||||
addJunctionActor(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void updatePaths()
|
||||
{
|
||||
PathfinderSettings ps;
|
||||
|
||||
ps.mainTurnDistanceLimit = 5;
|
||||
@ -59,11 +134,31 @@ public:
|
||||
|
||||
void calculateConnections(const int3 & pos)
|
||||
{
|
||||
auto guarded = ai->cb->getGuardingCreaturePosition(pos).valid();
|
||||
if(target->hasNodeAt(pos))
|
||||
{
|
||||
foreach_neighbour(
|
||||
ai->cb.get(),
|
||||
pos,
|
||||
[this, &pos](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor)
|
||||
{
|
||||
if(target->hasNodeAt(neighbor))
|
||||
{
|
||||
auto paths = ai->pathfinder->getPathInfo(neighbor);
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
if(pos == path.targetHero->visitablePos())
|
||||
{
|
||||
target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(guarded)
|
||||
return;
|
||||
}
|
||||
|
||||
auto guardPos = ai->cb->getGuardingCreaturePosition(pos);
|
||||
auto paths = ai->pathfinder->getPathInfo(pos);
|
||||
|
||||
for(AIPath & path1 : paths)
|
||||
@ -73,25 +168,32 @@ public:
|
||||
if(path1.targetHero == path2.targetHero)
|
||||
continue;
|
||||
|
||||
auto pos1 = path1.targetHero->visitablePos();
|
||||
auto pos2 = path2.targetHero->visitablePos();
|
||||
|
||||
if(guardPos.valid() && guardPos != pos1 && guardPos != pos2)
|
||||
continue;
|
||||
|
||||
auto obj1 = actorObjectMap[path1.targetHero];
|
||||
auto obj2 = actorObjectMap[path2.targetHero];
|
||||
|
||||
auto tile1 = cb->getTile(obj1->visitablePos());
|
||||
auto tile2 = cb->getTile(obj2->visitablePos());
|
||||
auto tile1 = cb->getTile(pos1);
|
||||
auto tile2 = cb->getTile(pos2);
|
||||
|
||||
if(tile2->isWater() && !tile1->isWater())
|
||||
{
|
||||
auto linkTile = cb->getTile(pos);
|
||||
if(!cb->getTile(pos)->isWater())
|
||||
continue;
|
||||
|
||||
if(!linkTile->isWater() || obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD)
|
||||
if(obj1 && (obj1->ID != Obj::BOAT || obj1->ID != Obj::SHIPYARD))
|
||||
continue;
|
||||
}
|
||||
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(obj2->visitablePos(), path1.targetHero, true);
|
||||
auto danger = ai->pathfinder->getStorage()->evaluateDanger(pos2, path1.targetHero, true);
|
||||
|
||||
auto updated = target->tryAddConnection(
|
||||
obj1->visitablePos(),
|
||||
obj2->visitablePos(),
|
||||
pos1,
|
||||
pos2,
|
||||
path1.movementCost() + path2.movementCost(),
|
||||
danger);
|
||||
|
||||
@ -99,8 +201,8 @@ public:
|
||||
{
|
||||
logAi->trace(
|
||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||
obj1->getObjectName(), obj1->visitablePos().toString(),
|
||||
obj2->getObjectName(), obj2->visitablePos().toString(),
|
||||
obj1 ? obj1->getObjectName() : "J", pos1.toString(),
|
||||
obj2 ? obj2->getObjectName() : "J", pos2.toString(),
|
||||
pos.toString(),
|
||||
path1.movementCost() + path2.movementCost());
|
||||
}
|
||||
@ -108,7 +210,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool isExtraConnection(float direct, float side1, float side2) const
|
||||
{
|
||||
float sideRatio = (side1 + side2) / direct;
|
||||
|
||||
return sideRatio < 1.25f && direct > side1 && direct > side2;
|
||||
}
|
||||
|
||||
void removeExtraConnections()
|
||||
{
|
||||
std::vector<std::pair<int3, int3>> connectionsToRemove;
|
||||
|
||||
for(auto & actor : temporaryActorHeroes)
|
||||
{
|
||||
auto pos = actor->visitablePos();
|
||||
auto & currentNode = target->getNode(pos);
|
||||
|
||||
target->iterateConnections(pos, [this, &pos, &connectionsToRemove, ¤tNode](int3 n1, ObjectLink o1)
|
||||
{
|
||||
target->iterateConnections(n1, [&pos, &o1, ¤tNode, &connectionsToRemove, this](int3 n2, ObjectLink o2)
|
||||
{
|
||||
auto direct = currentNode.connections.find(n2);
|
||||
|
||||
if(direct != currentNode.connections.end() && isExtraConnection(direct->second.cost, o1.cost, o2.cost))
|
||||
{
|
||||
connectionsToRemove.push_back({pos, n2});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(connectionsToRemove);
|
||||
|
||||
for(auto & c : connectionsToRemove)
|
||||
{
|
||||
target->removeConnection(c.first, c.second);
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 2)
|
||||
{
|
||||
logAi->trace("Remove ineffective connection %s->%s", c.first.toString(), c.second.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addObjectActor(const CGObjectInstance * obj)
|
||||
{
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
|
||||
@ -126,11 +270,77 @@ private:
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = obj;
|
||||
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||
|
||||
target->addObject(obj);
|
||||
};
|
||||
}
|
||||
|
||||
void addJunctionActor(const int3 & visitablePos)
|
||||
{
|
||||
auto internalCb = temporaryActorHeroes.front()->cb;
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
|
||||
|
||||
CRandomGenerator rng;
|
||||
|
||||
objectActor->setOwner(ai->playerID); // lets avoid having multiple colors
|
||||
objectActor->initHero(rng, static_cast<HeroTypeID>(0));
|
||||
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
|
||||
objectActor->initObj(rng);
|
||||
|
||||
if(cb->getTile(visitablePos)->isWater())
|
||||
{
|
||||
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||
}
|
||||
|
||||
assert(objectActor->visitablePos() == visitablePos);
|
||||
|
||||
actorObjectMap[objectActor] = nullptr;
|
||||
actors[objectActor] = HeroRole::SCOUT;
|
||||
|
||||
target->registerJunction(visitablePos);
|
||||
}
|
||||
|
||||
ConnectionCostInfo getConnectionsCost(const int3 & pos) const
|
||||
{
|
||||
auto paths = ai->pathfinder->getPathInfo(pos);
|
||||
std::map<int3, float> costs;
|
||||
|
||||
for(auto & path : paths)
|
||||
{
|
||||
auto fromPos = path.targetHero->visitablePos();
|
||||
auto cost = costs.find(fromPos);
|
||||
|
||||
if(cost == costs.end())
|
||||
{
|
||||
costs.emplace(fromPos, path.movementCost());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(path.movementCost() < cost->second)
|
||||
{
|
||||
costs[fromPos] = path.movementCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionCostInfo result;
|
||||
|
||||
for(auto & cost : costs)
|
||||
{
|
||||
result.totalCost += cost.second;
|
||||
result.connectionsCount++;
|
||||
}
|
||||
|
||||
if(result.connectionsCount)
|
||||
{
|
||||
result.avg = result.totalCost / result.connectionsCount;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
bool ObjectGraph::tryAddConnection(
|
||||
@ -142,23 +352,24 @@ bool ObjectGraph::tryAddConnection(
|
||||
return nodes[from].connections[to].update(cost, danger);
|
||||
}
|
||||
|
||||
void ObjectGraph::removeConnection(const int3 & from, const int3 & to)
|
||||
{
|
||||
nodes[from].connections.erase(to);
|
||||
}
|
||||
|
||||
void ObjectGraph::updateGraph(const Nullkiller * ai)
|
||||
{
|
||||
auto cb = ai->cb;
|
||||
|
||||
ObjectGraphCalculator calculator(this, ai);
|
||||
|
||||
foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
||||
{
|
||||
if(nodes.find(pos) != nodes.end())
|
||||
return;
|
||||
|
||||
calculator.calculateConnections(pos);
|
||||
});
|
||||
calculator.setGraphObjects();
|
||||
calculator.calculateConnections();
|
||||
calculator.addMinimalDistanceJunctions();
|
||||
calculator.calculateConnections();
|
||||
|
||||
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
|
||||
dumpToLog("graph");
|
||||
|
||||
}
|
||||
|
||||
void ObjectGraph::addObject(const CGObjectInstance * obj)
|
||||
@ -166,6 +377,11 @@ void ObjectGraph::addObject(const CGObjectInstance * obj)
|
||||
nodes[obj->visitablePos()].init(obj);
|
||||
}
|
||||
|
||||
void ObjectGraph::registerJunction(const int3 & pos)
|
||||
{
|
||||
nodes[pos].initJunction();
|
||||
}
|
||||
|
||||
void ObjectGraph::removeObject(const CGObjectInstance * obj)
|
||||
{
|
||||
nodes[obj->visitablePos()].objectExists = false;
|
||||
@ -198,6 +414,9 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai)
|
||||
|
||||
for(AIPath & path : paths)
|
||||
{
|
||||
if(path.getFirstBlockedAction())
|
||||
continue;
|
||||
|
||||
auto heroPos = path.targetHero->visitablePos();
|
||||
|
||||
nodes[pos].connections[heroPos].update(
|
||||
@ -259,20 +478,55 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
|
||||
GraphPathNodePointer pos = pq.top();
|
||||
pq.pop();
|
||||
|
||||
auto & node = getNode(pos);
|
||||
auto & node = getOrCreateNode(pos);
|
||||
std::shared_ptr<SpecialAction> transitionAction;
|
||||
|
||||
if(node.obj)
|
||||
{
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
|| node.obj->ID == Obj::BORDERGUARD
|
||||
|| node.obj->ID == Obj::BORDER_GATE)
|
||||
{
|
||||
auto questObj = dynamic_cast<const IQuestObject *>(node.obj);
|
||||
auto questInfo = QuestInfo(questObj->quest, node.obj, pos.coord);
|
||||
|
||||
if(node.obj->ID == Obj::QUEST_GUARD
|
||||
&& questObj->quest->mission == Rewardable::Limiter{}
|
||||
&& questObj->quest->killTarget == ObjectInstanceID::NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto questAction = std::make_shared<AIPathfinding::QuestAction>(questInfo);
|
||||
|
||||
if(!questAction->canAct(targetHero))
|
||||
{
|
||||
transitionAction = questAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.isInQueue = false;
|
||||
|
||||
graph.iterateConnections(pos.coord, [&](int3 target, ObjectLink o)
|
||||
graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq](int3 target, ObjectLink o)
|
||||
{
|
||||
auto targetNodeType = o.danger ? GrapthPathNodeType::BATTLE : pos.nodeType;
|
||||
auto targetNodeType = o.danger || transitionAction ? GrapthPathNodeType::BATTLE : pos.nodeType;
|
||||
auto targetPointer = GraphPathNodePointer(target, targetNodeType);
|
||||
auto & targetNode = getNode(targetPointer);
|
||||
auto & targetNode = getOrCreateNode(targetPointer);
|
||||
|
||||
if(targetNode.tryUpdate(pos, node, o))
|
||||
{
|
||||
if(graph.getNode(target).objTypeID == Obj::HERO)
|
||||
targetNode.specialAction = transitionAction;
|
||||
|
||||
auto targetGraphNode = graph.getNode(target);
|
||||
|
||||
if(targetGraphNode.objID.hasValue())
|
||||
{
|
||||
targetNode.obj = ai->cb->getObj(targetGraphNode.objID, false);
|
||||
|
||||
if(targetNode.obj && targetNode.obj->ID == Obj::HERO)
|
||||
return;
|
||||
}
|
||||
|
||||
if(targetNode.isInQueue)
|
||||
{
|
||||
@ -321,11 +575,6 @@ 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;
|
||||
@ -348,7 +597,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
if(!node.reachable())
|
||||
continue;
|
||||
|
||||
std::vector<int3> tilesToPass;
|
||||
std::vector<GraphPathNodePointer> tilesToPass;
|
||||
|
||||
uint64_t danger = node.danger;
|
||||
float cost = node.cost;
|
||||
@ -372,7 +621,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
vstd::amax(danger, currentNode.danger);
|
||||
vstd::amax(cost, currentNode.cost);
|
||||
|
||||
tilesToPass.push_back(current.coord);
|
||||
tilesToPass.push_back(current);
|
||||
|
||||
if(currentNode.cost < 2.0f)
|
||||
break;
|
||||
@ -383,7 +632,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
if(tilesToPass.empty())
|
||||
continue;
|
||||
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back());
|
||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||
|
||||
for(auto & path : entryPaths)
|
||||
{
|
||||
@ -394,12 +643,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
||||
{
|
||||
AIPathNodeInfo n;
|
||||
|
||||
n.coord = *graphTile;
|
||||
n.coord = graphTile->coord;
|
||||
n.cost = cost;
|
||||
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
|
||||
n.danger = danger;
|
||||
n.targetHero = hero;
|
||||
n.parentIndex = 0;
|
||||
n.parentIndex = -1;
|
||||
n.specialAction = getNode(*graphTile).specialAction;
|
||||
|
||||
for(auto & node : path.nodes)
|
||||
{
|
||||
|
@ -50,6 +50,13 @@ struct ObjectNode
|
||||
objID = obj->id;
|
||||
objTypeID = obj->ID;
|
||||
}
|
||||
|
||||
void initJunction()
|
||||
{
|
||||
objectExists = false;
|
||||
objID = ObjectInstanceID();
|
||||
objTypeID = Obj();
|
||||
}
|
||||
};
|
||||
|
||||
class ObjectGraph
|
||||
@ -59,8 +66,11 @@ class ObjectGraph
|
||||
public:
|
||||
void updateGraph(const Nullkiller * ai);
|
||||
void addObject(const CGObjectInstance * obj);
|
||||
void registerJunction(const int3 & pos);
|
||||
void connectHeroes(const Nullkiller * ai);
|
||||
void removeObject(const CGObjectInstance * obj);
|
||||
bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
|
||||
void removeConnection(const int3 & from, const int3 & to);
|
||||
void dumpToLog(std::string visualKey) const;
|
||||
|
||||
template<typename Func>
|
||||
@ -77,7 +87,10 @@ public:
|
||||
return nodes.at(tile);
|
||||
}
|
||||
|
||||
bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
|
||||
bool hasNodeAt(const int3 & tile) const
|
||||
{
|
||||
return vstd::contains(nodes, tile);
|
||||
}
|
||||
};
|
||||
|
||||
struct GraphPathNode;
|
||||
@ -131,6 +144,8 @@ struct GraphPathNode
|
||||
GraphPathNodePointer previous;
|
||||
float cost = BAD_COST;
|
||||
uint64_t danger = 0;
|
||||
const CGObjectInstance * obj = nullptr;
|
||||
std::shared_ptr<SpecialAction> specialAction;
|
||||
|
||||
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
|
||||
|
||||
@ -157,7 +172,7 @@ public:
|
||||
void dumpToLog() const;
|
||||
|
||||
private:
|
||||
GraphPathNode & getNode(const GraphPathNodePointer & pos)
|
||||
GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos)
|
||||
{
|
||||
auto & node = pathNodes[pos.coord][pos.nodeType];
|
||||
|
||||
@ -165,6 +180,13 @@ private:
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
const GraphPathNode & getNode(const GraphPathNodePointer & pos) const
|
||||
{
|
||||
auto & node = pathNodes.at(pos.coord)[pos.nodeType];
|
||||
|
||||
return node;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -42,15 +42,34 @@ namespace AIPathfinding
|
||||
}
|
||||
|
||||
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||
|
||||
if(blocker == BlockingReason::NONE)
|
||||
{
|
||||
destination.blocked = nodeStorage->isDistanceLimitReached(source, destination);
|
||||
|
||||
if(destination.nodeObject
|
||||
&& !destination.blocked
|
||||
&& !allowBypassObjects
|
||||
&& !dynamic_cast<const CGTeleport *>(destination.nodeObject)
|
||||
&& destination.nodeObject->ID != Obj::EVENT)
|
||||
{
|
||||
destination.blocked = true;
|
||||
destination.node->locked = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!allowBypassObjects)
|
||||
{
|
||||
if(destination.nodeObject)
|
||||
{
|
||||
destination.blocked = true;
|
||||
destination.node->locked = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||
logAi->trace(
|
||||
|
@ -49,7 +49,7 @@ namespace AIPathfinding
|
||||
return;
|
||||
|
||||
// when actor represents moster graph node, we need to let him escape monster
|
||||
if(!destination.guarded && cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition)
|
||||
if(cb->getGuardingCreaturePosition(source.coord) == actor->initialPosition)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,8 @@ public:
|
||||
|
||||
virtual void drawLine(int3 start, int3 end) override
|
||||
{
|
||||
const Point offset = Point(30, 30);
|
||||
|
||||
auto level = model->getLevel();
|
||||
|
||||
if(start.z != level || end.z != level)
|
||||
@ -83,6 +85,9 @@ public:
|
||||
pStart.x += 3;
|
||||
pEnd.x -= 3;
|
||||
|
||||
pStart += offset;
|
||||
pEnd += offset;
|
||||
|
||||
if(viewPort.isInside(pStart) && viewPort.isInside(pEnd))
|
||||
{
|
||||
target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 0, 0));
|
||||
|
Loading…
Reference in New Issue
Block a user