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:
parent
adfcb650e2
commit
30d9daf62c
@ -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))
|
||||||
|
@ -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())
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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>(),
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user