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(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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto hero = path.targetHero;
|
auto hero = path.targetHero;
|
||||||
auto danger = path.getTotalDanger();
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto firstBlockedAction = path.getFirstBlockedAction();
|
auto firstBlockedAction = path.getFirstBlockedAction();
|
||||||
if(firstBlockedAction)
|
if(firstBlockedAction)
|
||||||
|
@ -27,8 +27,8 @@ namespace NKAI
|
|||||||
{
|
{
|
||||||
namespace AIPathfinding
|
namespace AIPathfinding
|
||||||
{
|
{
|
||||||
const int BUCKET_COUNT = 5;
|
const int BUCKET_COUNT = 3;
|
||||||
const int BUCKET_SIZE = 3;
|
const int BUCKET_SIZE = 5;
|
||||||
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
const int NUM_CHAINS = BUCKET_COUNT * BUCKET_SIZE;
|
||||||
const int CHAIN_MAX_DEPTH = 4;
|
const int CHAIN_MAX_DEPTH = 4;
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,18 @@ namespace AIPathfinding
|
|||||||
{
|
{
|
||||||
bool QuestAction::canAct(const AIPathNode * node) const
|
bool QuestAction::canAct(const AIPathNode * node) const
|
||||||
{
|
{
|
||||||
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)
|
return canAct(node->actor->hero);
|
||||||
{
|
|
||||||
return dynamic_cast<const IQuestObject *>(questInfo.obj)->checkQuest(node->actor->hero);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return questInfo.quest->activeForPlayers.count(node->actor->hero->getOwner())
|
bool QuestAction::canAct(const CGHeroInstance * hero) const
|
||||||
|| questInfo.quest->checkQuest(node->actor->hero);
|
{
|
||||||
|
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
|
Goals::TSubgoal QuestAction::decompose(const CGHeroInstance * hero) const
|
||||||
|
@ -30,6 +30,8 @@ namespace AIPathfinding
|
|||||||
|
|
||||||
bool canAct(const AIPathNode * node) const override;
|
bool canAct(const AIPathNode * node) const override;
|
||||||
|
|
||||||
|
bool canAct(const CGHeroInstance * hero) const;
|
||||||
|
|
||||||
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;
|
||||||
|
|
||||||
void execute(const CGHeroInstance * hero) const override;
|
void execute(const CGHeroInstance * hero) const override;
|
||||||
|
@ -15,10 +15,18 @@
|
|||||||
#include "../../../lib/mapping/CMap.h"
|
#include "../../../lib/mapping/CMap.h"
|
||||||
#include "../Engine/Nullkiller.h"
|
#include "../Engine/Nullkiller.h"
|
||||||
#include "../../../lib/logging/VisualLogger.h"
|
#include "../../../lib/logging/VisualLogger.h"
|
||||||
|
#include "Actions/QuestAction.h"
|
||||||
|
|
||||||
namespace NKAI
|
namespace NKAI
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct ConnectionCostInfo
|
||||||
|
{
|
||||||
|
float totalCost = 0;
|
||||||
|
float avg = 0;
|
||||||
|
int connectionsCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class ObjectGraphCalculator
|
class ObjectGraphCalculator
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@ -34,10 +42,14 @@ private:
|
|||||||
public:
|
public:
|
||||||
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
|
ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai)
|
||||||
:ai(ai), target(target)
|
:ai(ai), target(target)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGraphObjects()
|
||||||
{
|
{
|
||||||
for(auto obj : ai->memory->visitableObjs)
|
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);
|
addObjectActor(obj);
|
||||||
}
|
}
|
||||||
@ -47,7 +59,70 @@ public:
|
|||||||
{
|
{
|
||||||
addObjectActor(town);
|
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;
|
PathfinderSettings ps;
|
||||||
|
|
||||||
ps.mainTurnDistanceLimit = 5;
|
ps.mainTurnDistanceLimit = 5;
|
||||||
@ -59,11 +134,31 @@ public:
|
|||||||
|
|
||||||
void calculateConnections(const int3 & pos)
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto guardPos = ai->cb->getGuardingCreaturePosition(pos);
|
||||||
auto paths = ai->pathfinder->getPathInfo(pos);
|
auto paths = ai->pathfinder->getPathInfo(pos);
|
||||||
|
|
||||||
for(AIPath & path1 : paths)
|
for(AIPath & path1 : paths)
|
||||||
@ -73,25 +168,32 @@ public:
|
|||||||
if(path1.targetHero == path2.targetHero)
|
if(path1.targetHero == path2.targetHero)
|
||||||
continue;
|
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 obj1 = actorObjectMap[path1.targetHero];
|
||||||
auto obj2 = actorObjectMap[path2.targetHero];
|
auto obj2 = actorObjectMap[path2.targetHero];
|
||||||
|
|
||||||
auto tile1 = cb->getTile(obj1->visitablePos());
|
auto tile1 = cb->getTile(pos1);
|
||||||
auto tile2 = cb->getTile(obj2->visitablePos());
|
auto tile2 = cb->getTile(pos2);
|
||||||
|
|
||||||
if(tile2->isWater() && !tile1->isWater())
|
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;
|
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(
|
auto updated = target->tryAddConnection(
|
||||||
obj1->visitablePos(),
|
pos1,
|
||||||
obj2->visitablePos(),
|
pos2,
|
||||||
path1.movementCost() + path2.movementCost(),
|
path1.movementCost() + path2.movementCost(),
|
||||||
danger);
|
danger);
|
||||||
|
|
||||||
@ -99,8 +201,8 @@ public:
|
|||||||
{
|
{
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
"Connected %s[%s] -> %s[%s] through [%s], cost %2f",
|
||||||
obj1->getObjectName(), obj1->visitablePos().toString(),
|
obj1 ? obj1->getObjectName() : "J", pos1.toString(),
|
||||||
obj2->getObjectName(), obj2->visitablePos().toString(),
|
obj2 ? obj2->getObjectName() : "J", pos2.toString(),
|
||||||
pos.toString(),
|
pos.toString(),
|
||||||
path1.movementCost() + path2.movementCost());
|
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)
|
void addObjectActor(const CGObjectInstance * obj)
|
||||||
{
|
{
|
||||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(obj->cb)).get();
|
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();
|
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(objectActor->visitablePos() == visitablePos);
|
||||||
|
|
||||||
actorObjectMap[objectActor] = obj;
|
actorObjectMap[objectActor] = obj;
|
||||||
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
|
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD ? HeroRole::MAIN : HeroRole::SCOUT;
|
||||||
|
|
||||||
target->addObject(obj);
|
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(
|
bool ObjectGraph::tryAddConnection(
|
||||||
@ -142,23 +352,24 @@ bool ObjectGraph::tryAddConnection(
|
|||||||
return nodes[from].connections[to].update(cost, danger);
|
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)
|
void ObjectGraph::updateGraph(const Nullkiller * ai)
|
||||||
{
|
{
|
||||||
auto cb = ai->cb;
|
auto cb = ai->cb;
|
||||||
|
|
||||||
ObjectGraphCalculator calculator(this, ai);
|
ObjectGraphCalculator calculator(this, ai);
|
||||||
|
|
||||||
foreach_tile_pos(cb.get(), [this, &calculator](const CPlayerSpecificInfoCallback * cb, const int3 & pos)
|
calculator.setGraphObjects();
|
||||||
{
|
calculator.calculateConnections();
|
||||||
if(nodes.find(pos) != nodes.end())
|
calculator.addMinimalDistanceJunctions();
|
||||||
return;
|
calculator.calculateConnections();
|
||||||
|
|
||||||
calculator.calculateConnections(pos);
|
|
||||||
});
|
|
||||||
|
|
||||||
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
|
if(NKAI_GRAPH_TRACE_LEVEL >= 1)
|
||||||
dumpToLog("graph");
|
dumpToLog("graph");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectGraph::addObject(const CGObjectInstance * obj)
|
void ObjectGraph::addObject(const CGObjectInstance * obj)
|
||||||
@ -166,6 +377,11 @@ void ObjectGraph::addObject(const CGObjectInstance * obj)
|
|||||||
nodes[obj->visitablePos()].init(obj);
|
nodes[obj->visitablePos()].init(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectGraph::registerJunction(const int3 & pos)
|
||||||
|
{
|
||||||
|
nodes[pos].initJunction();
|
||||||
|
}
|
||||||
|
|
||||||
void ObjectGraph::removeObject(const CGObjectInstance * obj)
|
void ObjectGraph::removeObject(const CGObjectInstance * obj)
|
||||||
{
|
{
|
||||||
nodes[obj->visitablePos()].objectExists = false;
|
nodes[obj->visitablePos()].objectExists = false;
|
||||||
@ -198,6 +414,9 @@ void ObjectGraph::connectHeroes(const Nullkiller * ai)
|
|||||||
|
|
||||||
for(AIPath & path : paths)
|
for(AIPath & path : paths)
|
||||||
{
|
{
|
||||||
|
if(path.getFirstBlockedAction())
|
||||||
|
continue;
|
||||||
|
|
||||||
auto heroPos = path.targetHero->visitablePos();
|
auto heroPos = path.targetHero->visitablePos();
|
||||||
|
|
||||||
nodes[pos].connections[heroPos].update(
|
nodes[pos].connections[heroPos].update(
|
||||||
@ -259,20 +478,55 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
|
|||||||
GraphPathNodePointer pos = pq.top();
|
GraphPathNodePointer pos = pq.top();
|
||||||
pq.pop();
|
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;
|
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 targetPointer = GraphPathNodePointer(target, targetNodeType);
|
||||||
auto & targetNode = getNode(targetPointer);
|
auto & targetNode = getOrCreateNode(targetPointer);
|
||||||
|
|
||||||
if(targetNode.tryUpdate(pos, node, o))
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(targetNode.isInQueue)
|
if(targetNode.isInQueue)
|
||||||
{
|
{
|
||||||
@ -321,11 +575,6 @@ bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathN
|
|||||||
|
|
||||||
if(newCost < cost)
|
if(newCost < cost)
|
||||||
{
|
{
|
||||||
if(nodeType < pos.nodeType)
|
|
||||||
{
|
|
||||||
logAi->error("Linking error");
|
|
||||||
}
|
|
||||||
|
|
||||||
previous = pos;
|
previous = pos;
|
||||||
danger = prev.danger + link.danger;
|
danger = prev.danger + link.danger;
|
||||||
cost = newCost;
|
cost = newCost;
|
||||||
@ -348,7 +597,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
|||||||
if(!node.reachable())
|
if(!node.reachable())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::vector<int3> tilesToPass;
|
std::vector<GraphPathNodePointer> tilesToPass;
|
||||||
|
|
||||||
uint64_t danger = node.danger;
|
uint64_t danger = node.danger;
|
||||||
float cost = node.cost;
|
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(danger, currentNode.danger);
|
||||||
vstd::amax(cost, currentNode.cost);
|
vstd::amax(cost, currentNode.cost);
|
||||||
|
|
||||||
tilesToPass.push_back(current.coord);
|
tilesToPass.push_back(current);
|
||||||
|
|
||||||
if(currentNode.cost < 2.0f)
|
if(currentNode.cost < 2.0f)
|
||||||
break;
|
break;
|
||||||
@ -383,7 +632,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
|||||||
if(tilesToPass.empty())
|
if(tilesToPass.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back());
|
auto entryPaths = ai->pathfinder->getPathInfo(tilesToPass.back().coord);
|
||||||
|
|
||||||
for(auto & path : entryPaths)
|
for(auto & path : entryPaths)
|
||||||
{
|
{
|
||||||
@ -394,12 +643,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
|
|||||||
{
|
{
|
||||||
AIPathNodeInfo n;
|
AIPathNodeInfo n;
|
||||||
|
|
||||||
n.coord = *graphTile;
|
n.coord = graphTile->coord;
|
||||||
n.cost = cost;
|
n.cost = cost;
|
||||||
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
|
n.turns = static_cast<ui8>(cost) + 1; // just in case lets select worst scenario
|
||||||
n.danger = danger;
|
n.danger = danger;
|
||||||
n.targetHero = hero;
|
n.targetHero = hero;
|
||||||
n.parentIndex = 0;
|
n.parentIndex = -1;
|
||||||
|
n.specialAction = getNode(*graphTile).specialAction;
|
||||||
|
|
||||||
for(auto & node : path.nodes)
|
for(auto & node : path.nodes)
|
||||||
{
|
{
|
||||||
|
@ -50,6 +50,13 @@ struct ObjectNode
|
|||||||
objID = obj->id;
|
objID = obj->id;
|
||||||
objTypeID = obj->ID;
|
objTypeID = obj->ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initJunction()
|
||||||
|
{
|
||||||
|
objectExists = false;
|
||||||
|
objID = ObjectInstanceID();
|
||||||
|
objTypeID = Obj();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ObjectGraph
|
class ObjectGraph
|
||||||
@ -59,8 +66,11 @@ class ObjectGraph
|
|||||||
public:
|
public:
|
||||||
void updateGraph(const Nullkiller * ai);
|
void updateGraph(const Nullkiller * ai);
|
||||||
void addObject(const CGObjectInstance * obj);
|
void addObject(const CGObjectInstance * obj);
|
||||||
|
void registerJunction(const int3 & pos);
|
||||||
void connectHeroes(const Nullkiller * ai);
|
void connectHeroes(const Nullkiller * ai);
|
||||||
void removeObject(const CGObjectInstance * obj);
|
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;
|
void dumpToLog(std::string visualKey) const;
|
||||||
|
|
||||||
template<typename Func>
|
template<typename Func>
|
||||||
@ -77,7 +87,10 @@ public:
|
|||||||
return nodes.at(tile);
|
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;
|
struct GraphPathNode;
|
||||||
@ -131,6 +144,8 @@ struct GraphPathNode
|
|||||||
GraphPathNodePointer previous;
|
GraphPathNodePointer previous;
|
||||||
float cost = BAD_COST;
|
float cost = BAD_COST;
|
||||||
uint64_t danger = 0;
|
uint64_t danger = 0;
|
||||||
|
const CGObjectInstance * obj = nullptr;
|
||||||
|
std::shared_ptr<SpecialAction> specialAction;
|
||||||
|
|
||||||
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
|
using TFibHeap = boost::heap::fibonacci_heap<GraphPathNodePointer, boost::heap::compare<GraphNodeComparer>>;
|
||||||
|
|
||||||
@ -157,7 +172,7 @@ public:
|
|||||||
void dumpToLog() const;
|
void dumpToLog() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GraphPathNode & getNode(const GraphPathNodePointer & pos)
|
GraphPathNode & getOrCreateNode(const GraphPathNodePointer & pos)
|
||||||
{
|
{
|
||||||
auto & node = pathNodes[pos.coord][pos.nodeType];
|
auto & node = pathNodes[pos.coord][pos.nodeType];
|
||||||
|
|
||||||
@ -165,6 +180,13 @@ private:
|
|||||||
|
|
||||||
return node;
|
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);
|
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
|
||||||
|
|
||||||
if(blocker == BlockingReason::NONE)
|
if(blocker == BlockingReason::NONE)
|
||||||
{
|
{
|
||||||
destination.blocked = nodeStorage->isDistanceLimitReached(source, destination);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!allowBypassObjects)
|
if(!allowBypassObjects)
|
||||||
|
{
|
||||||
|
if(destination.nodeObject)
|
||||||
|
{
|
||||||
|
destination.blocked = true;
|
||||||
|
destination.node->locked = true;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
#if NKAI_PATHFINDER_TRACE_LEVEL >= 2
|
||||||
logAi->trace(
|
logAi->trace(
|
||||||
|
@ -49,7 +49,7 @@ namespace AIPathfinding
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// when actor represents moster graph node, we need to let him escape monster
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,8 @@ public:
|
|||||||
|
|
||||||
virtual void drawLine(int3 start, int3 end) override
|
virtual void drawLine(int3 start, int3 end) override
|
||||||
{
|
{
|
||||||
|
const Point offset = Point(30, 30);
|
||||||
|
|
||||||
auto level = model->getLevel();
|
auto level = model->getLevel();
|
||||||
|
|
||||||
if(start.z != level || end.z != level)
|
if(start.z != level || end.z != level)
|
||||||
@ -83,6 +85,9 @@ public:
|
|||||||
pStart.x += 3;
|
pStart.x += 3;
|
||||||
pEnd.x -= 3;
|
pEnd.x -= 3;
|
||||||
|
|
||||||
|
pStart += offset;
|
||||||
|
pEnd += offset;
|
||||||
|
|
||||||
if(viewPort.isInside(pStart) && viewPort.isInside(pEnd))
|
if(viewPort.isInside(pStart) && viewPort.isInside(pEnd))
|
||||||
{
|
{
|
||||||
target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 0, 0));
|
target.drawLine(pStart, pEnd, ColorRGBA(255, 255, 0), ColorRGBA(255, 0, 0));
|
||||||
|
Loading…
Reference in New Issue
Block a user