1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

NKAI: improve boat handling by object graph, a set of fixes

This commit is contained in:
Andrii Danylchenko 2024-03-28 13:39:15 +02:00
parent adfcb650e2
commit 30d9daf62c
16 changed files with 234 additions and 51 deletions

View File

@ -329,14 +329,24 @@ void ObjectClusterizer::clusterizeObject(
if(ai->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT) if(ai->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT)
{ {
if(path.movementCost() > 2.0f) if(path.movementCost() > 2.0f)
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Path is too far %f", path.movementCost());
#endif
continue; continue;
}
} }
else if(path.movementCost() > 4.0f) else if(path.movementCost() > 4.0f && obj->ID != Obj::TOWN)
{ {
auto strategicalValue = valueEvaluator.getStrategicalValue(obj); auto strategicalValue = valueEvaluator.getStrategicalValue(obj);
if(strategicalValue < 0.5f) if(strategicalValue < 0.3f)
{
#if NKAI_TRACE_LEVEL >= 2
logAi->trace("Object value is too low %f", strategicalValue);
#endif
continue; continue;
}
} }
if(!shouldVisit(ai, path.targetHero, obj)) if(!shouldVisit(ai, path.targetHero, obj))

View File

@ -49,6 +49,9 @@ Goals::TGoalVec StayAtTownBehavior::decompose() const
if(town->visitingHero && town->visitingHero.get() != path.targetHero) if(town->visitingHero && town->visitingHero.get() != path.targetHero)
continue; continue;
if(!path.targetHero->hasSpellbook() || path.targetHero->mana >= 0.75f * path.targetHero->manaLimit())
continue;
if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1) if(path.turn() == 0 && !path.getFirstBlockedAction() && path.exchangeCount <= 1)
{ {
if(path.targetHero->mana == path.targetHero->manaLimit()) if(path.targetHero->mana == path.targetHero->manaLimit())

View File

@ -67,40 +67,40 @@ void ExecuteHeroChain::accept(AIGateway * ai)
for(int i = chainPath.nodes.size() - 1; i >= 0; i--) for(int i = chainPath.nodes.size() - 1; i >= 0; i--)
{ {
auto & node = chainPath.nodes[i]; auto * node = &chainPath.nodes[i];
const CGHeroInstance * hero = node.targetHero; const CGHeroInstance * hero = node->targetHero;
HeroPtr heroPtr = hero; HeroPtr heroPtr = hero;
if(node.parentIndex >= i) if(node->parentIndex >= i)
{ {
logAi->error("Invalid parentIndex while executing node " + node.coord.toString()); logAi->error("Invalid parentIndex while executing node " + node->coord.toString());
} }
if(vstd::contains(blockedIndexes, i)) if(vstd::contains(blockedIndexes, i))
{ {
blockedIndexes.insert(node.parentIndex); blockedIndexes.insert(node->parentIndex);
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN); ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
continue; continue;
} }
logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node.coord.toString()); logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node->coord.toString());
try try
{ {
if(hero->movementPointsRemaining() > 0) if(hero->movementPointsRemaining() > 0)
{ {
ai->nullkiller->setActive(hero, node.coord); ai->nullkiller->setActive(hero, node->coord);
if(node.specialAction) if(node->specialAction)
{ {
if(node.actionIsBlocked) if(node->actionIsBlocked)
{ {
throw cannotFulfillGoalException("Path is nondeterministic."); throw cannotFulfillGoalException("Path is nondeterministic.");
} }
node.specialAction->execute(hero); node->specialAction->execute(hero);
if(!heroPtr.validAndSet()) if(!heroPtr.validAndSet())
{ {
@ -109,10 +109,34 @@ void ExecuteHeroChain::accept(AIGateway * ai)
return; return;
} }
} }
else if(i > 0 && ai->nullkiller->settings->isObjectGraphAllowed())
if(node.turns == 0 && node.coord != hero->visitablePos())
{ {
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord); auto chainMask = i < chainPath.nodes.size() - 1 ? chainPath.nodes[i + 1].chainMask : node->chainMask;
for(auto j = i - 1; j >= 0; j--)
{
auto & nextNode = chainPath.nodes[j];
if(nextNode.specialAction || nextNode.chainMask != chainMask)
break;
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(nextNode.coord);
if(!targetNode->reachable()
|| targetNode->getCost() > nextNode.cost)
break;
i = j;
node = &nextNode;
if(targetNode->action == EPathNodeAction::BATTLE || targetNode->action == EPathNodeAction::TELEPORT_BATTLE)
break;
}
}
if(node->turns == 0 && node->coord != hero->visitablePos())
{
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node->coord);
if(targetNode->accessible == EPathAccessibility::NOT_SET if(targetNode->accessible == EPathAccessibility::NOT_SET
|| targetNode->accessible == EPathAccessibility::BLOCKED || targetNode->accessible == EPathAccessibility::BLOCKED
@ -122,7 +146,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
logAi->error( logAi->error(
"Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this", "Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this",
hero->getNameTranslated(), hero->getNameTranslated(),
node.coord.toString()); node->coord.toString());
return; return;
} }
@ -132,7 +156,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
{ {
try try
{ {
if(moveHeroToTile(hero, node.coord)) if(moveHeroToTile(hero, node->coord))
{ {
continue; continue;
} }
@ -149,11 +173,11 @@ void ExecuteHeroChain::accept(AIGateway * ai)
if(hero->movementPointsRemaining() > 0) if(hero->movementPointsRemaining() > 0)
{ {
CGPath path; CGPath path;
bool isOk = cb->getPathsInfo(hero)->getPath(path, node.coord); bool isOk = cb->getPathsInfo(hero)->getPath(path, node->coord);
if(isOk && path.nodes.back().turns > 0) if(isOk && path.nodes.back().turns > 0)
{ {
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movementPointsRemaining(), node.coord.toString()); logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movementPointsRemaining(), node->coord.toString());
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN); ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
return; return;
@ -165,15 +189,15 @@ void ExecuteHeroChain::accept(AIGateway * ai)
} }
} }
if(node.coord == hero->visitablePos()) if(node->coord == hero->visitablePos())
continue; continue;
if(node.turns == 0) if(node->turns == 0)
{ {
logAi->error( logAi->error(
"Unable to complete chain. Expected hero %s to arive to %s but he is at %s", "Unable to complete chain. Expected hero %s to arive to %s but he is at %s",
hero->getNameTranslated(), hero->getNameTranslated(),
node.coord.toString(), node->coord.toString(),
hero->visitablePos().toString()); hero->visitablePos().toString());
return; return;
@ -181,13 +205,13 @@ void ExecuteHeroChain::accept(AIGateway * ai)
// no exception means we were not able to reach the tile // no exception means we were not able to reach the tile
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN); ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
blockedIndexes.insert(node.parentIndex); blockedIndexes.insert(node->parentIndex);
} }
catch(const goalFulfilledException &) catch(const goalFulfilledException &)
{ {
if(!heroPtr.validAndSet()) if(!heroPtr.validAndSet())
{ {
logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name, node.coord.toString()); logAi->debug("Hero %s was killed while attempting to reach %s", heroPtr.name, node->coord.toString());
return; return;
} }

View File

@ -529,7 +529,7 @@ bool AINodeStorage::calculateHeroChain()
tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r) tbb::parallel_for(tbb::blocked_range<size_t>(0, data.size()), [&](const tbb::blocked_range<size_t>& r)
{ {
//auto r = blocked_range<size_t>(0, data.size()); //auto r = blocked_range<size_t>(0, data.size());
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn); HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
task.execute(r); task.execute(r);
@ -543,7 +543,7 @@ bool AINodeStorage::calculateHeroChain()
else else
{ {
auto r = tbb::blocked_range<size_t>(0, data.size()); auto r = tbb::blocked_range<size_t>(0, data.size());
HeroChainCalculationTask task(*this, nodes, data, chainMask, heroChainTurn); HeroChainCalculationTask task(*this, data, chainMask, heroChainTurn);
task.execute(r); task.execute(r);
task.flushResult(heroChain); task.flushResult(heroChain);

View File

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

View File

@ -37,10 +37,8 @@ namespace AIPathfinding
return Goals::sptr(Goals::Invalid()); return Goals::sptr(Goals::Invalid());
} }
bool BuildBoatAction::canAct(const AIPathNode * source) const bool BuildBoatAction::canAct(const CGHeroInstance * hero, const TResources & reservedResources) const
{ {
auto hero = source->actor->hero;
if(cb->getPlayerRelations(hero->tempOwner, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES) if(cb->getPlayerRelations(hero->tempOwner, shipyard->getObject()->getOwner()) == PlayerRelations::ENEMIES)
{ {
#if NKAI_TRACE_LEVEL > 1 #if NKAI_TRACE_LEVEL > 1
@ -53,7 +51,7 @@ namespace AIPathfinding
shipyard->getBoatCost(boatCost); shipyard->getBoatCost(boatCost);
if(!cb->getResourceAmount().canAfford(source->actor->armyCost + boatCost)) if(!cb->getResourceAmount().canAfford(reservedResources + boatCost))
{ {
#if NKAI_TRACE_LEVEL > 1 #if NKAI_TRACE_LEVEL > 1
logAi->trace("Can not build a boat. Not enough resources."); logAi->trace("Can not build a boat. Not enough resources.");
@ -65,6 +63,18 @@ namespace AIPathfinding
return true; return true;
} }
bool BuildBoatAction::canAct(const AIPathNode * source) const
{
return canAct(source->actor->hero, source->actor->armyCost);
}
bool BuildBoatAction::canAct(const AIPathNodeInfo & source) const
{
TResources res;
return canAct(source.targetHero, res);
}
const CGObjectInstance * BuildBoatAction::targetObject() const const CGObjectInstance * BuildBoatAction::targetObject() const
{ {
return dynamic_cast<const CGObjectInstance*>(shipyard); return dynamic_cast<const CGObjectInstance*>(shipyard);
@ -75,6 +85,11 @@ namespace AIPathfinding
return sourceActor->resourceActor; return sourceActor->resourceActor;
} }
std::shared_ptr<SpecialAction> BuildBoatActionFactory::create(const Nullkiller * ai)
{
return std::make_shared<BuildBoatAction>(ai->cb.get(), dynamic_cast<const IShipyard * >(ai->cb->getObj(shipyard)));
}
void SummonBoatAction::execute(const CGHeroInstance * hero) const void SummonBoatAction::execute(const CGHeroInstance * hero) const
{ {
Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai); Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT).accept(ai);

View File

@ -57,6 +57,8 @@ namespace AIPathfinding
} }
bool canAct(const AIPathNode * source) const override; bool canAct(const AIPathNode * source) const override;
bool canAct(const AIPathNodeInfo & source) const override;
bool canAct(const CGHeroInstance * hero, const TResources & reservedResources) const;
void execute(const CGHeroInstance * hero) const override; void execute(const CGHeroInstance * hero) const override;
@ -68,6 +70,19 @@ namespace AIPathfinding
const CGObjectInstance * targetObject() const override; const CGObjectInstance * targetObject() const override;
}; };
class BuildBoatActionFactory : public ISpecialActionFactory
{
ObjectInstanceID shipyard;
public:
BuildBoatActionFactory(ObjectInstanceID shipyard)
:shipyard(shipyard)
{
}
std::shared_ptr<SpecialAction> create(const Nullkiller * ai) override;
};
} }
} }

View File

@ -23,6 +23,11 @@ namespace AIPathfinding
return canAct(node->actor->hero); return canAct(node->actor->hero);
} }
bool QuestAction::canAct(const AIPathNodeInfo & node) const
{
return canAct(node.targetHero);
}
bool QuestAction::canAct(const CGHeroInstance * hero) const bool QuestAction::canAct(const CGHeroInstance * hero) const
{ {
if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD) if(questInfo.obj->ID == Obj::BORDER_GATE || questInfo.obj->ID == Obj::BORDERGUARD)

View File

@ -29,7 +29,7 @@ namespace AIPathfinding
} }
bool canAct(const AIPathNode * node) const override; bool canAct(const AIPathNode * node) const override;
bool canAct(const AIPathNodeInfo & node) const override;
bool canAct(const CGHeroInstance * hero) const; bool canAct(const CGHeroInstance * hero) const;
Goals::TSubgoal decompose(const CGHeroInstance * hero) const override; Goals::TSubgoal decompose(const CGHeroInstance * hero) const override;

View File

@ -22,6 +22,7 @@ namespace NKAI
{ {
struct AIPathNode; struct AIPathNode;
struct AIPathNodeInfo;
class ChainActor; class ChainActor;
class SpecialAction class SpecialAction
@ -34,6 +35,11 @@ public:
return true; return true;
} }
virtual bool canAct(const AIPathNodeInfo & source) const
{
return true;
}
virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const; virtual Goals::TSubgoal decompose(const CGHeroInstance * hero) const;
virtual void execute(const CGHeroInstance * hero) const; virtual void execute(const CGHeroInstance * hero) const;
@ -89,4 +95,10 @@ public:
const AIPathNode * srcNode) const override; const AIPathNode * srcNode) const override;
}; };
class ISpecialActionFactory
{
public:
virtual std::shared_ptr<SpecialAction> create(const Nullkiller * ai) = 0;
};
} }

View File

@ -17,6 +17,7 @@
#include "../../../lib/logging/VisualLogger.h" #include "../../../lib/logging/VisualLogger.h"
#include "Actions/QuestAction.h" #include "Actions/QuestAction.h"
#include "../pforeach.h" #include "../pforeach.h"
#include "Actions/BoatActions.h"
namespace NKAI namespace NKAI
{ {
@ -121,7 +122,7 @@ public:
ConnectionCostInfo currentCost = getConnectionsCost(paths); ConnectionCostInfo currentCost = getConnectionsCost(paths);
if(currentCost.connectionsCount <= 2) if(currentCost.connectionsCount <= 1 || currentCost.connectionsCount == 2 && currentCost.totalCost < 3.0f)
return; return;
float neighborCost = getNeighborConnectionsCost(pos, paths); float neighborCost = getNeighborConnectionsCost(pos, paths);
@ -170,7 +171,7 @@ private:
auto obj = ai->cb->getTopObj(pos); auto obj = ai->cb->getTopObj(pos);
if(obj && obj->ID == Obj::BOAT) if((obj && obj->ID == Obj::BOAT) || target->isVirtualBoat(pos))
{ {
ai->pathfinder->calculatePathInfo(pathCache, pos); ai->pathfinder->calculatePathInfo(pathCache, pos);
@ -230,9 +231,9 @@ private:
if(!cb->getTile(pos)->isWater()) if(!cb->getTile(pos)->isWater())
continue; continue;
auto startingObjIsBoatOrShipyard = obj1 && (obj1->ID == Obj::BOAT || obj1->ID == Obj::SHIPYARD); auto startingObjIsBoat = (obj1 && obj1->ID == Obj::BOAT) || target->isVirtualBoat(pos1);
if(!startingObjIsBoatOrShipyard) if(!startingObjIsBoat)
continue; continue;
} }
@ -320,12 +321,22 @@ private:
assert(objectActor->visitablePos() == visitablePos); assert(objectActor->visitablePos() == visitablePos);
actorObjectMap[objectActor] = obj; actorObjectMap[objectActor] = obj;
actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::SHIPYARD || obj->ID == Obj::BOAT ? HeroRole::MAIN : HeroRole::SCOUT; actors[objectActor] = obj->ID == Obj::TOWN || obj->ID == Obj::BOAT ? HeroRole::MAIN : HeroRole::SCOUT;
target->addObject(obj); target->addObject(obj);
auto shipyard = dynamic_cast<const IShipyard *>(obj);
if(shipyard && shipyard->bestLocation().valid())
{
int3 virtualBoat = shipyard->bestLocation();
addJunctionActor(virtualBoat, true);
target->addVirtualBoat(virtualBoat, obj);
}
} }
void addJunctionActor(const int3 & visitablePos) void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false)
{ {
std::lock_guard<std::mutex> lock(syncLock); std::lock_guard<std::mutex> lock(syncLock);
@ -339,7 +350,7 @@ private:
objectActor->pos = objectActor->convertFromVisitablePos(visitablePos); objectActor->pos = objectActor->convertFromVisitablePos(visitablePos);
objectActor->initObj(rng); objectActor->initObj(rng);
if(ai->cb->getTile(visitablePos)->isWater()) if(isVirtualBoat || ai->cb->getTile(visitablePos)->isWater())
{ {
objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get(); objectActor->boat = temporaryBoats.emplace_back(std::make_unique<CGBoat>(objectActor->cb)).get();
} }
@ -347,7 +358,7 @@ private:
assert(objectActor->visitablePos() == visitablePos); assert(objectActor->visitablePos() == visitablePos);
actorObjectMap[objectActor] = nullptr; actorObjectMap[objectActor] = nullptr;
actors[objectActor] = HeroRole::SCOUT; actors[objectActor] = isVirtualBoat ? HeroRole::MAIN : HeroRole::SCOUT;
target->registerJunction(visitablePos); target->registerJunction(visitablePos);
} }
@ -397,7 +408,15 @@ bool ObjectGraph::tryAddConnection(
float cost, float cost,
uint64_t danger) uint64_t danger)
{ {
return nodes[from].connections[to].update(cost, danger); auto result = nodes[from].connections[to].update(cost, danger);
auto & connection = nodes[from].connections[to];
if(result && isVirtualBoat(to) && !connection.specialAction)
{
connection.specialAction = std::make_shared<AIPathfinding::BuildBoatActionFactory>(virtualBoats[to]);
}
return result;
} }
void ObjectGraph::removeConnection(const int3 & from, const int3 & to) void ObjectGraph::removeConnection(const int3 & from, const int3 & to)
@ -422,19 +441,30 @@ void ObjectGraph::updateGraph(const Nullkiller * ai)
void ObjectGraph::addObject(const CGObjectInstance * obj) void ObjectGraph::addObject(const CGObjectInstance * obj)
{ {
nodes[obj->visitablePos()].init(obj); if(!hasNodeAt(obj->visitablePos()))
nodes[obj->visitablePos()].init(obj);
}
void ObjectGraph::addVirtualBoat(const int3 & pos, const CGObjectInstance * shipyard)
{
if(!isVirtualBoat(pos))
{
virtualBoats[pos] = shipyard->id;
}
} }
void ObjectGraph::registerJunction(const int3 & pos) void ObjectGraph::registerJunction(const int3 & pos)
{ {
nodes[pos].initJunction(); if(!hasNodeAt(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;
if(obj->ID == Obj::BOAT) if(obj->ID == Obj::BOAT && !isVirtualBoat(obj->visitablePos()))
{ {
vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair<int3, ObjectLink> & link) -> bool vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair<int3, ObjectLink> & link) -> bool
{ {
@ -512,6 +542,27 @@ GraphPaths::GraphPaths()
{ {
} }
std::shared_ptr<SpecialAction> getCompositeAction(
const Nullkiller * ai,
std::shared_ptr<ISpecialActionFactory> linkActionFactory,
std::shared_ptr<SpecialAction> transitionAction)
{
if(!linkActionFactory)
return transitionAction;
auto linkAction = linkActionFactory->create(ai);
if(!transitionAction)
return linkAction;
std::vector<std::shared_ptr<const SpecialAction>> actionsArray = {
transitionAction,
linkAction
};
return std::make_shared<CompositeAction>(actionsArray);
}
void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai) void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkiller * ai)
{ {
graph.copyFrom(*ai->baseGraph); graph.copyFrom(*ai->baseGraph);
@ -561,15 +612,16 @@ void GraphPaths::calculatePaths(const CGHeroInstance * targetHero, const Nullkil
node.isInQueue = false; node.isInQueue = false;
graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq](int3 target, ObjectLink o) graph.iterateConnections(pos.coord, [this, ai, &pos, &node, &transitionAction, &pq](int3 target, const ObjectLink & o)
{ {
auto targetNodeType = o.danger || transitionAction ? GrapthPathNodeType::BATTLE : pos.nodeType; auto compositeAction = getCompositeAction(ai, o.specialAction, transitionAction);
auto targetNodeType = o.danger || compositeAction ? GrapthPathNodeType::BATTLE : pos.nodeType;
auto targetPointer = GraphPathNodePointer(target, targetNodeType); auto targetPointer = GraphPathNodePointer(target, targetNodeType);
auto & targetNode = getOrCreateNode(targetPointer); auto & targetNode = getOrCreateNode(targetPointer);
if(targetNode.tryUpdate(pos, node, o)) if(targetNode.tryUpdate(pos, node, o))
{ {
targetNode.specialAction = transitionAction; targetNode.specialAction = compositeAction;
auto targetGraphNode = graph.getNode(target); auto targetGraphNode = graph.getNode(target);
@ -704,6 +756,11 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
n.parentIndex = -1; n.parentIndex = -1;
n.specialAction = getNode(*graphTile).specialAction; n.specialAction = getNode(*graphTile).specialAction;
if(n.specialAction)
{
n.actionIsBlocked = !n.specialAction->canAct(n);
}
for(auto & node : path.nodes) for(auto & node : path.nodes)
{ {
node.parentIndex++; node.parentIndex++;
@ -806,7 +863,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
} }
} }
if(!path.nodes.empty()) if(path.nodes.size() > 1)
continue; continue;
for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++) for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
@ -820,6 +877,11 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
n.specialAction = node.specialAction; n.specialAction = node.specialAction;
n.parentIndex = path.nodes.size(); n.parentIndex = path.nodes.size();
if(n.specialAction)
{
n.actionIsBlocked = !n.specialAction->canAct(n);
}
auto blocker = ai->objectClusterizer->getBlocker(n); auto blocker = ai->objectClusterizer->getBlocker(n);
if(!blocker) if(!blocker)

View File

@ -22,6 +22,7 @@ struct ObjectLink
{ {
float cost = 100000; // some big number float cost = 100000; // some big number
uint64_t danger = 0; uint64_t danger = 0;
std::shared_ptr<ISpecialActionFactory> specialAction;
bool update(float newCost, uint64_t newDanger) bool update(float newCost, uint64_t newDanger)
{ {
@ -62,20 +63,33 @@ struct ObjectNode
class ObjectGraph class ObjectGraph
{ {
std::unordered_map<int3, ObjectNode> nodes; std::unordered_map<int3, ObjectNode> nodes;
std::unordered_map<int3, ObjectInstanceID> virtualBoats;
public: public:
ObjectGraph()
:nodes(), virtualBoats()
{
}
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 registerJunction(const int3 & pos);
void addVirtualBoat(const int3 & pos, const CGObjectInstance * shipyard);
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); bool tryAddConnection(const int3 & from, const int3 & to, float cost, uint64_t danger);
void removeConnection(const int3 & from, const int3 & to); void removeConnection(const int3 & from, const int3 & to);
void dumpToLog(std::string visualKey) const; void dumpToLog(std::string visualKey) const;
bool isVirtualBoat(const int3 & tile) const
{
return vstd::contains(virtualBoats, tile);
}
void copyFrom(const ObjectGraph & other) void copyFrom(const ObjectGraph & other)
{ {
nodes = other.nodes; nodes = other.nodes;
virtualBoats = other.virtualBoats;
} }
template<typename Func> template<typename Func>

View File

@ -17,8 +17,12 @@ namespace NKAI
{ {
namespace AIPathfinding namespace AIPathfinding
{ {
AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage) AILayerTransitionRule::AILayerTransitionRule(
:cb(cb), ai(ai), nodeStorage(nodeStorage) CPlayerSpecificInfoCallback * cb,
Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowEmbark)
:cb(cb), ai(ai), nodeStorage(nodeStorage), allowEmbark(allowEmbark)
{ {
setup(); setup();
} }

View File

@ -32,9 +32,14 @@ namespace AIPathfinding
std::map<const CGHeroInstance *, std::shared_ptr<const SummonBoatAction>> summonableVirtualBoats; std::map<const CGHeroInstance *, std::shared_ptr<const SummonBoatAction>> summonableVirtualBoats;
std::map<const CGHeroInstance *, std::shared_ptr<const WaterWalkingAction>> waterWalkingActions; std::map<const CGHeroInstance *, std::shared_ptr<const WaterWalkingAction>> waterWalkingActions;
std::map<const CGHeroInstance *, std::shared_ptr<const AirWalkingAction>> airWalkingActions; std::map<const CGHeroInstance *, std::shared_ptr<const AirWalkingAction>> airWalkingActions;
bool allowEmbark;
public: public:
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, Nullkiller * ai, std::shared_ptr<AINodeStorage> nodeStorage); AILayerTransitionRule(
CPlayerSpecificInfoCallback * cb,
Nullkiller * ai,
std::shared_ptr<AINodeStorage> nodeStorage,
bool allowEmbark);
virtual void process( virtual void process(
const PathNodeInfo & source, const PathNodeInfo & source,

View File

@ -40,6 +40,16 @@ namespace AIPathfinding
return; return;
} }
if(!allowBypassObjects
&& destination.action == EPathNodeAction::EMBARK
&& source.node->layer == EPathfindingLayer::LAND
&& destination.node->layer == EPathfindingLayer::SAIL)
{
destination.blocked = true;
return;
}
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper); auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);

View File

@ -396,7 +396,11 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
// In auto testing pack.mode we always close client if red pack.player won or lose // In auto testing pack.mode we always close client if red pack.player won or lose
if(!settings["session"]["testmap"].isNull() && pack.player == PlayerColor(0)) if(!settings["session"]["testmap"].isNull() && pack.player == PlayerColor(0))
{
logAi->info("Red player %s. Ending game.", pack.victoryLossCheckResult.victory() ? "won" : "lost");
handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not
}
} }
void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface & pack) void ApplyClientNetPackVisitor::visitPlayerReinitInterface(PlayerReinitInterface & pack)