1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +02:00

AI: pathfinder extensibility - add one more rule for movement to destination and some refactoring

This commit is contained in:
Andrii Danylchenko 2018-08-11 22:39:42 +03:00
parent 7150fc9f71
commit eb17313f7f
2 changed files with 170 additions and 86 deletions

View File

@ -147,51 +147,47 @@ PathfinderOptions::PathfinderOptions()
originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool(); originalMovementRules = settings["pathfinder"]["originalMovementRules"].Bool();
} }
class CMovementCostRule : public IPathfindingRule void CMovementCostRule::process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper)
{ {
public: int turnAtNextTile = destination.turn, moveAtNextTile = destination.movementLeft;
virtual void process( int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile);
CPathNodeInfo & source, int remains = moveAtNextTile - cost;
CDestinationNodeInfo & destination, if(remains < 0)
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override
{ {
int turnAtNextTile = destination.turn, moveAtNextTile = destination.movementLeft; //occurs rarely, when hero with low movepoints tries to leave the road
int cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); pathfinderHelper->updateTurnInfo(++turnAtNextTile);
int remains = moveAtNextTile - cost; moveAtNextTile = pathfinderHelper->getMaxMovePoints(destination.node->layer);
if(remains < 0) cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :(
{ remains = moveAtNextTile - cost;
//occurs rarely, when hero with low movepoints tries to leave the road
pathfinderHelper->updateTurnInfo(++turnAtNextTile);
moveAtNextTile = pathfinderHelper->getMaxMovePoints(destination.node->layer);
cost = pathfinderHelper->getMovementCost(source, destination, moveAtNextTile); //cost must be updated, movement points changed :(
remains = moveAtNextTile - cost;
}
if(destination.action == CGPathNode::EMBARK || destination.action == CGPathNode::DISEMBARK)
{
/// FREE_SHIP_BOARDING bonus only remove additional penalty
/// land <-> sail transition still cost movement points as normal movement
remains = pathfinderHelper->movementPointsAfterEmbark(moveAtNextTile, cost, destination.action - 1);
}
destination.turn = turnAtNextTile;
destination.movementLeft = remains;
if(destination.isBetterWay() &&
((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source)))
{
assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other
destination.node->moveRemains = remains;
destination.node->turns = turnAtNextTile;
destination.node->theNodeBefore = source.node;
destination.node->action = destination.action;
return;
}
destination.blocked = true;
} }
}; if(destination.action == CGPathNode::EMBARK || destination.action == CGPathNode::DISEMBARK)
{
/// FREE_SHIP_BOARDING bonus only remove additional penalty
/// land <-> sail transition still cost movement points as normal movement
remains = pathfinderHelper->movementPointsAfterEmbark(moveAtNextTile, cost, destination.action - 1);
}
destination.turn = turnAtNextTile;
destination.movementLeft = remains;
if(destination.isBetterWay() &&
((source.node->turns == turnAtNextTile && remains) || pathfinderHelper->passOneTurnLimitCheck(source)))
{
assert(destination.node != source.node->theNodeBefore); //two tiles can't point to each other
destination.node->moveRemains = remains;
destination.node->turns = turnAtNextTile;
destination.node->theNodeBefore = source.node;
destination.node->action = destination.action;
return;
}
destination.blocked = true;
}
CPathfinderConfig::CPathfinderConfig( CPathfinderConfig::CPathfinderConfig(
std::shared_ptr<CNodeStorage> nodeStorage, std::shared_ptr<CNodeStorage> nodeStorage,
@ -212,6 +208,7 @@ CPathfinder::CPathfinder(
std::make_shared<CPathfinderNodeStorage>(_out, _hero), std::make_shared<CPathfinderNodeStorage>(_out, _hero),
std::make_shared<CNeighbourFinder>(), std::make_shared<CNeighbourFinder>(),
std::vector<std::shared_ptr<IPathfindingRule>>{ std::vector<std::shared_ptr<IPathfindingRule>>{
std::make_shared<CMovementToDestinationRule>(),
std::make_shared<CMovementCostRule>(), std::make_shared<CMovementCostRule>(),
std::make_shared<CMovementAfterDestinationRule>() std::make_shared<CMovementAfterDestinationRule>()
})) }))
@ -305,9 +302,6 @@ void CPathfinder::calculatePaths()
if(destination.nodeObject) if(destination.nodeObject)
destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner); destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner);
if(!isMovementToDestPossible())
continue;
destination.action = getDestAction(); destination.action = getDestAction();
destination.turn = turn; destination.turn = turn;
destination.movementLeft = movement; destination.movementLeft = movement;
@ -320,7 +314,7 @@ void CPathfinder::calculatePaths()
break; break;
} }
if(!destination.blocked && !destination.furtherProcessingImpossible) if(!destination.blocked)
pq.push(destination.node); pq.push(destination.node);
} //neighbours loop } //neighbours loop
@ -551,66 +545,94 @@ bool CPathfinder::isLayerTransitionPossible() const
return true; return true;
} }
bool CPathfinder::isMovementToDestPossible() const CPathfinderBlockingRule::BlockingReason CMovementToDestinationRule::getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper)
{ {
if(destination.node->accessible == CGPathNode::BLOCKED) if(destination.node->accessible == CGPathNode::BLOCKED)
return false; return BlockingReason::DESTINATION_BLOCKED;
switch(destination.node->layer) switch(destination.node->layer)
{ {
case ELayer::LAND: case EPathfindingLayer::LAND:
if(!hlp->canMoveBetween(source.node->coord, destination.node->coord)) if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord))
return false; return BlockingReason::DESTINATION_BLOCKED;
if(isSourceGuarded())
if(source.guarded)
{ {
if(!(config->options.originalMovementRules && source.node->layer == ELayer::AIR) && if(!(pathfinderConfig->options.originalMovementRules && source.node->layer == EPathfindingLayer::AIR) &&
!isDestinationGuardian()) // Can step into tile of guard !destination.guarded) // Can step into tile of guard
{ {
return false; return BlockingReason::SOURCE_GUARDED;
} }
} }
break; break;
case ELayer::SAIL: case EPathfindingLayer::SAIL:
if(!hlp->canMoveBetween(source.node->coord, destination.node->coord)) if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord))
return false; return BlockingReason::DESTINATION_BLOCKED;
if(isSourceGuarded())
if(source.guarded)
{ {
// Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile // Hero embarked a boat standing on a guarded tile -> we must allow to move away from that tile
if(source.node->action != CGPathNode::EMBARK && !isDestinationGuardian()) if(source.node->action != CGPathNode::EMBARK && !destination.guarded)
return false; return BlockingReason::SOURCE_GUARDED;
} }
if(source.node->layer == ELayer::LAND) if(source.node->layer == EPathfindingLayer::LAND)
{ {
if(!destination.isNodeObjectVisitable()) if(!destination.isNodeObjectVisitable())
return false; return BlockingReason::DESTINATION_BLOCKED;
if(destination.nodeObject->ID != Obj::BOAT && destination.nodeObject->ID != Obj::HERO) if(destination.nodeObject->ID != Obj::BOAT && destination.nodeObject->ID != Obj::HERO)
return false; return BlockingReason::DESTINATION_BLOCKED;
} }
else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT) else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT)
{ {
/// Hero in boat can't visit empty boats /// Hero in boat can't visit empty boats
return false; return BlockingReason::DESTINATION_BLOCKED;
} }
break; break;
case ELayer::WATER: case EPathfindingLayer::WATER:
if(!hlp->canMoveBetween(source.node->coord, destination.node->coord) || destination.node->accessible != CGPathNode::ACCESSIBLE) if(!pathfinderHelper->canMoveBetween(source.coord, destination.coord)
return false; || destination.node->accessible != CGPathNode::ACCESSIBLE)
if(isDestinationGuarded()) {
return false; return BlockingReason::DESTINATION_BLOCKED;
}
if(destination.guarded)
return BlockingReason::DESTINATION_GUARDED;
break; break;
} }
return true; return BlockingReason::NONE;
} }
void CMovementAfterDestinationRule::process( void CMovementAfterDestinationRule::process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * config,
CPathfinderHelper * pathfinderHelper)
{
auto blocker = getBlockingReason(source, destination, config, pathfinderHelper);
if(blocker == BlockingReason::DESTINATION_GUARDED && destination.action == CGPathNode::ENodeAction::BATTLE)
{
return; // allow bypass guarded tile but only in direction of guard, a bit UI related thing
}
destination.blocked = blocker != BlockingReason::NONE;
}
CPathfinderBlockingRule::BlockingReason CMovementAfterDestinationRule::getBlockingReason(
CPathNodeInfo & source, CPathNodeInfo & source,
CDestinationNodeInfo & destination, CDestinationNodeInfo & destination,
CPathfinderConfig * config, CPathfinderConfig * config,
@ -629,43 +651,48 @@ void CMovementAfterDestinationRule::process(
{ {
/// For now we'll always allow transit over teleporters /// For now we'll always allow transit over teleporters
/// Transit over whirlpools only allowed when hero protected /// Transit over whirlpools only allowed when hero protected
return; return BlockingReason::NONE;
} }
else if(destination.nodeObject->ID == Obj::GARRISON else if(destination.nodeObject->ID == Obj::GARRISON
|| destination.nodeObject->ID == Obj::GARRISON2 || destination.nodeObject->ID == Obj::GARRISON2
|| destination.nodeObject->ID == Obj::BORDER_GATE) || destination.nodeObject->ID == Obj::BORDER_GATE)
{ {
/// Transit via unguarded garrisons is always possible /// Transit via unguarded garrisons is always possible
return; return BlockingReason::NONE;
} }
break; return BlockingReason::DESTINATION_VISIT;
} }
case CGPathNode::BLOCKING_VISIT:
return destination.guarded
? BlockingReason::DESTINATION_GUARDED
: BlockingReason::DESTINATION_BLOCKVIS;
case CGPathNode::NORMAL: case CGPathNode::NORMAL:
return; return BlockingReason::NONE;
case CGPathNode::EMBARK: case CGPathNode::EMBARK:
if(pathfinderHelper->options.useEmbarkAndDisembark) if(pathfinderHelper->options.useEmbarkAndDisembark)
return; return BlockingReason::NONE;
break; return BlockingReason::DESTINATION_BLOCKED;
case CGPathNode::DISEMBARK: case CGPathNode::DISEMBARK:
if(pathfinderHelper->options.useEmbarkAndDisembark && !destination.guarded) if(pathfinderHelper->options.useEmbarkAndDisembark && !destination.guarded)
return; return BlockingReason::NONE;
break; return BlockingReason::DESTINATION_BLOCKED;
case CGPathNode::BATTLE: case CGPathNode::BATTLE:
/// Movement after BATTLE action only possible from guarded tile to guardian tile /// Movement after BATTLE action only possible from guarded tile to guardian tile
if(destination.guarded) if(destination.guarded)
return; return BlockingReason::NONE;
break; break;
} }
destination.furtherProcessingImpossible = true; return BlockingReason::DESTINATION_BLOCKED;
} }
CGPathNode::ENodeAction CPathfinder::getDestAction() const CGPathNode::ENodeAction CPathfinder::getDestAction() const
@ -1401,7 +1428,7 @@ void CPathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObje
} }
CDestinationNodeInfo::CDestinationNodeInfo() CDestinationNodeInfo::CDestinationNodeInfo()
: CPathNodeInfo(), blocked(false), furtherProcessingImpossible(false), action(CGPathNode::ENodeAction::UNKNOWN) : CPathNodeInfo(), blocked(false), action(CGPathNode::ENodeAction::UNKNOWN)
{ {
} }
@ -1410,7 +1437,6 @@ void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool exclude
CPathNodeInfo::setNode(gs, n, excludeTopObject); CPathNodeInfo::setNode(gs, n, excludeTopObject);
blocked = false; blocked = false;
furtherProcessingImpossible = false;
action = CGPathNode::ENodeAction::UNKNOWN; action = CGPathNode::ENodeAction::UNKNOWN;
} }

View File

@ -117,7 +117,6 @@ struct DLL_LINKAGE CDestinationNodeInfo : public CPathNodeInfo
CGPathNode::ENodeAction action; CGPathNode::ENodeAction action;
int turn; int turn;
int movementLeft; int movementLeft;
bool furtherProcessingImpossible;
bool blocked; bool blocked;
CDestinationNodeInfo(); CDestinationNodeInfo();
@ -149,7 +148,7 @@ public:
CPathfinderHelper * pathfinderHelper) = 0; CPathfinderHelper * pathfinderHelper) = 0;
}; };
class CMovementAfterDestinationRule : public IPathfindingRule class CMovementCostRule : public IPathfindingRule
{ {
public: public:
virtual void process( virtual void process(
@ -159,6 +158,66 @@ public:
CPathfinderHelper * pathfinderHelper) override; CPathfinderHelper * pathfinderHelper) override;
}; };
class CPathfinderBlockingRule : public IPathfindingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override
{
auto blockingReason = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
destination.blocked = blockingReason != BlockingReason::NONE;
}
protected:
enum class BlockingReason
{
NONE = 0,
SOURCE_GUARDED = 1,
DESTINATION_GUARDED = 2,
SOURCE_BLOCKED = 3,
DESTINATION_BLOCKED = 4,
DESTINATION_BLOCKVIS = 5,
DESTINATION_VISIT = 6
};
virtual BlockingReason getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) = 0;
};
class CMovementAfterDestinationRule : public CPathfinderBlockingRule
{
public:
virtual void process(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
protected:
virtual BlockingReason getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
};
class CMovementToDestinationRule : public CPathfinderBlockingRule
{
protected:
virtual BlockingReason getBlockingReason(
CPathNodeInfo & source,
CDestinationNodeInfo & destination,
CPathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) override;
};
class CNeighbourFinder class CNeighbourFinder
{ {
public: public:
@ -299,7 +358,6 @@ private:
bool isLayerTransitionPossible(const ELayer dstLayer) const; bool isLayerTransitionPossible(const ELayer dstLayer) const;
bool isLayerTransitionPossible() const; bool isLayerTransitionPossible() const;
bool isMovementToDestPossible() const;
CGPathNode::ENodeAction getDestAction() const; CGPathNode::ENodeAction getDestAction() const;
CGPathNode::ENodeAction getTeleportDestAction() const; CGPathNode::ENodeAction getTeleportDestAction() const;