1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-08-13 19:54:17 +02:00

hero chain stabilisation

This commit is contained in:
Andrii Danylchenko
2021-05-15 21:04:48 +03:00
committed by Andrii Danylchenko
parent 87f1079c60
commit 774f531c4e
8 changed files with 161 additions and 105 deletions

View File

@@ -132,7 +132,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
initialNode->turns = actor->initialTurn;
initialNode->moveRemains = actor->initialMovement;
initialNode->danger = 0;
initialNode->cost = 0.0;
initialNode->cost = actor->initialTurn;
initialNode->action = CGPathNode::ENodeAction::NORMAL;
if(actor->isMovable)
{
@@ -175,6 +176,16 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
{
dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
}
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace(
"Commited %s -> %s, cost: %f, hero: %s, mask: %i",
source.coord.toString(),
destination.coord.toString(),
destination.cost,
dstNode->actor->hero->name,
dstNode->actor->chainMask);
#endif
});
}
@@ -194,6 +205,7 @@ void AINodeStorage::commit(
destination->manaCost = source->manaCost;
destination->danger = source->danger;
destination->theNodeBefore = source->theNodeBefore;
destination->chainOther = nullptr;
}
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
@@ -227,33 +239,53 @@ bool AINodeStorage::calculateHeroChain()
heroChainPass = true;
heroChain.resize(0);
std::vector<AIPathNode *> buffer;
buffer.reserve(NUM_CHAINS);
foreach_tile_pos([&](const int3 & pos) {
auto layer = EPathfindingLayer::LAND;
auto chains = nodes[pos.x][pos.y][pos.z][layer];
buffer.resize(0);
for(AIPathNode & node : chains)
{
if(node.locked && node.turns < 1)
addHeroChain(&node);
if(node.turns <= heroChainMaxTurns && node.action != CGPathNode::ENodeAction::UNKNOWN)
buffer.push_back(&node);
}
for(AIPathNode * node : buffer)
{
addHeroChain(node, buffer);
}
});
return heroChain.size();
}
void AINodeStorage::addHeroChain(AIPathNode * srcNode)
void AINodeStorage::addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *> variants)
{
auto chains = nodes[srcNode->coord.x][srcNode->coord.y][srcNode->coord.z][srcNode->layer];
for(AIPathNode & node : chains)
for(AIPathNode * node : variants)
{
if(!node.locked || !node.actor || node.action == CGPathNode::ENodeAction::UNKNOWN && node.actor->hero)
if(node == srcNode || !node->actor || node->turns > heroChainMaxTurns
|| node->action == CGPathNode::ENodeAction::UNKNOWN && node->actor->hero)
{
continue;
}
addHeroChain(srcNode, &node);
addHeroChain(&node, srcNode);
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace(
"Thy exchange %s[%i] -> %s[%i] at %s",
node->actor->hero->name,
node->actor->chainMask,
srcNode->actor->hero->name,
srcNode->actor->chainMask,
srcNode->coord.toString());
#endif
addHeroChain(srcNode, node);
//addHeroChain(&node, srcNode);
}
}
@@ -261,37 +293,65 @@ void AINodeStorage::addHeroChain(AIPathNode * carrier, AIPathNode * other)
{
if(carrier->actor->canExchange(other->actor))
{
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace(
"Exchange allowed %s[%i] -> %s[%i] at %s",
other->actor->hero->name,
other->actor->chainMask,
carrier->actor->hero->name,
carrier->actor->chainMask,
carrier->coord.toString());
#endif
bool hasLessMp = carrier->turns > other->turns || carrier->moveRemains < other->moveRemains;
bool hasLessExperience = carrier->actor->hero->exp < other->actor->hero->exp;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Check hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name);
#endif
if(hasLessMp && hasLessExperience)
{
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
#endif
return;
}
auto newActor = carrier->actor->exchange(other->actor);
auto chainNodeOptional = getOrCreateNode(carrier->coord, carrier->layer, newActor);
if(!chainNodeOptional)
{
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
#endif
return;
}
auto chainNode = chainNodeOptional.get();
if(chainNode->locked)
return;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Hero exhange at %s, %s -> %s", carrier->coord.toString(), other->actor->hero->name, carrier->actor->hero->name);
if(chainNode->action != CGPathNode::ENodeAction::UNKNOWN)
{
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
#endif
return;
}
commitExchange(chainNode, carrier, other);
heroChain.push_back(chainNode);
if(commitExchange(chainNode, carrier, other))
{
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace(
"Chain accepted at %s %s -> %s, mask %i, cost %f",
chainNode->coord.toString(),
other->actor->hero->name,
chainNode->actor->hero->name,
chainNode->actor->chainMask,
chainNode->cost);
#endif
heroChain.push_back(chainNode);
}
}
}
void AINodeStorage::commitExchange(
bool AINodeStorage::commitExchange(
AIPathNode * exchangeNode,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const
@@ -302,7 +362,7 @@ void AINodeStorage::commitExchange(
auto armyLoss = carrierParentNode->armyLoss + otherParentNode->armyLoss;
auto turns = carrierParentNode->turns;
auto cost = carrierParentNode->cost;
auto cost = carrierParentNode->cost + otherParentNode->cost / 1000.0;
auto movementLeft = carrierParentNode->moveRemains;
if(carrierParentNode->turns < otherParentNode->turns)
@@ -312,27 +372,24 @@ void AINodeStorage::commitExchange(
+ carrierParentNode->moveRemains / (float)moveRemains;
turns = otherParentNode->turns;
cost = waitingCost;
cost += waitingCost;
movementLeft = moveRemains;
}
if(exchangeNode->turns != 0xFF && exchangeNode->cost < cost)
return;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Accepted hero exhange at %s, carrier %s, mp cost %f",
exchangeNode->coord.toString(),
carrierActor->hero->name,
cost);
{
#ifdef VCMI_TRACE_PATHFINDER_EX
logAi->trace("Exchange at %s is is not effective enough. %f < %f", exchangeNode->coord.toString(), exchangeNode->cost, cost);
#endif
return false;
}
commit(exchangeNode, carrierParentNode, carrierParentNode->action, turns, movementLeft, cost);
exchangeNode->theNodeBefore = carrierParentNode;
exchangeNode->chainOther = otherParentNode;
exchangeNode->armyLoss = armyLoss;
exchangeNode->manaCost = carrierParentNode->manaCost;
return true;
}
const CGHeroInstance * AINodeStorage::getHero(const CGPathNode * node) const
@@ -538,6 +595,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
path.heroArmy = node.actor->creatureSet;
path.armyLoss = node.armyLoss;
path.targetObjectDanger = evaluateDanger(pos, path.targetHero);
path.chainMask = node.actor->chainMask;
fillChainInfo(&node, path);
@@ -551,22 +609,26 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path) const
{
while(node != nullptr)
{
if(!node->actor->hero || node->coord == node->actor->hero->visitablePos())
if(!node->actor->hero)
return;
AIPathNodeInfo pathNode;
pathNode.cost = node->cost;
pathNode.targetHero = node->actor->hero;
pathNode.turns = node->turns;
pathNode.danger = node->danger;
pathNode.coord = node->coord;
path.nodes.push_back(pathNode);
path.specialAction = node->specialAction;
if(node->chainOther)
fillChainInfo(node->chainOther, path);
if(node->actor->hero->visitablePos() != node->coord)
{
AIPathNodeInfo pathNode;
pathNode.cost = node->cost;
pathNode.targetHero = node->actor->hero;
pathNode.turns = node->turns;
pathNode.danger = node->danger;
pathNode.coord = node->coord;
path.nodes.push_back(pathNode);
}
path.specialAction = node->specialAction;
node = getAINode(node->theNodeBefore);
}
}
@@ -586,6 +648,11 @@ int3 AIPath::firstTileToGet() const
return int3(-1, -1, -1);
}
const AIPathNodeInfo & AIPath::firstNode() const
{
return nodes.back();
}
uint64_t AIPath::getPathDanger() const
{
if(nodes.size())

View File

@@ -11,6 +11,7 @@
#pragma once
#define VCMI_TRACE_PATHFINDER
#define VCMI_TRACE_PATHFINDER_EX
#include "../../../lib/CPathfinder.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
@@ -19,6 +20,7 @@
#include "../Goals/AbstractGoal.h"
#include "Actions/ISpecialAction.h"
#include "Actors.h"
#include <inttypes.h>
#define VCMI_TRACE_PATHFINDER
@@ -49,6 +51,7 @@ struct AIPath
uint64_t armyLoss;
const CGHeroInstance * targetHero;
const CCreatureSet * heroArmy;
uint64_t chainMask;
AIPath();
@@ -60,6 +63,8 @@ struct AIPath
int3 firstTileToGet() const;
const AIPathNodeInfo & firstNode() const;
float movementCost() const;
uint64_t getHeroStrength() const;
@@ -78,6 +83,7 @@ private:
std::vector<std::shared_ptr<ChainActor>> actors;
std::vector<CGPathNode *> heroChain;
bool heroChainPass; // true if we need to calculate hero chain
int heroChainMaxTurns = 0;
public:
/// more than 1 chain layer for each hero allows us to have more than 1 path to each tile so we can chose more optimal one.
@@ -123,7 +129,7 @@ public:
private:
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
void addHeroChain(AIPathNode * srcNode);
void addHeroChain(AIPathNode * srcNode, std::vector<AIPathNode *> variants);
void addHeroChain(AIPathNode * carrier, AIPathNode * other);
void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
void fillChainInfo(const AIPathNode * node, AIPath & path) const;
@@ -135,7 +141,7 @@ private:
int movementLeft,
float cost) const;
void AINodeStorage::commitExchange(
bool AINodeStorage::commitExchange(
AIPathNode * exchangeNode,
AIPathNode * carrierParentNode,
AIPathNode * otherParentNode) const;

View File

@@ -51,18 +51,20 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
}
logAi->debug("Recalculate all paths");
int pass = 0;
storage->clear();
storage->setHeroes(heroes, ai);
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
cb->calculatePaths(config);
while(useHeroChain && storage->calculateHeroChain())
{
config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, storage);
do {
logAi->trace("Recalculate paths pass %" PRIi32, pass++);
cb->calculatePaths(config);
}
logAi->trace("Recalculate chain pass %" PRIi32, pass);
useHeroChain = useHeroChain && storage->calculateHeroChain();
} while(useHeroChain);
}
void AIPathfinder::updatePaths(const HeroPtr & hero)

View File

@@ -13,7 +13,6 @@
#include "Rules/AIMovementAfterDestinationRule.h"
#include "Rules/AIMovementToDestinationRule.h"
#include "Rules/AIPreviousNodeRule.h"
#include "Rules/AIMovementCostRule.h"
namespace AIPathfinding
{

View File

@@ -82,21 +82,16 @@ void ChainActor::setBaseActor(HeroActor * base)
armyValue = base->armyValue;
chainMask = base->chainMask;
creatureSet = base->creatureSet;
isMovable = base->isMovable;
}
void HeroActor::setupSpecialActors()
{
auto allActors = std::vector<ChainActor *>{ this };
for(int i = 1; i <= SPECIAL_ACTORS_COUNT; i++)
for(ChainActor & specialActor : specialActors)
{
ChainActor & specialActor = specialActors[i - 1];
specialActor.setBaseActor(this);
specialActor.allowBattle = (i & 1) > 0;
specialActor.allowSpellCast = (i & 2) > 0;
specialActor.allowUseResources = (i & 4) > 0;
allActors.push_back(&specialActor);
}
@@ -104,6 +99,9 @@ void HeroActor::setupSpecialActors()
{
ChainActor * actor = allActors[i];
actor->allowBattle = (i & 1) > 0;
actor->allowSpellCast = (i & 2) > 0;
actor->allowUseResources = (i & 4) > 0;
actor->battleActor = allActors[i | 1];
actor->castActor = allActors[i | 2];
actor->resourceActor = allActors[i | 4];
@@ -180,7 +178,7 @@ CCreatureSet * HeroActor::pickBestCreatures(const CCreatureSet * army1, const CC
{
for(auto & i : armyPtr->Slots())
{
creToPower[i.second->type] += i.second->getPower();
creToPower[i.second->type] += i.second->count;
}
}
//TODO - consider more than just power (ie morale penalty, hero specialty in certain stacks, etc)
@@ -193,7 +191,7 @@ CCreatureSet * HeroActor::pickBestCreatures(const CCreatureSet * army1, const CC
typedef const std::pair<const CCreature *, int> & CrePowerPair;
auto creIt = boost::max_element(creToPower, [](CrePowerPair lhs, CrePowerPair rhs)
{
return lhs.second < rhs.second;
return lhs.first->AIValue * lhs.second < rhs.first->AIValue * rhs.second;
});
target->addToSlot(SlotID(i), creIt->first->idNumber, TQuantity(creIt->second));

View File

@@ -125,14 +125,23 @@ Goals::TGoalVec PathfindingManager::findPaths(
for(auto path : chainInfo)
{
if(hero && hero.get() != path.targetHero)
if(hero && hero.get() != path.targetHero || path.nodes.empty())
continue;
int3 firstTileToGet = path.firstTileToGet();
const AIPathNodeInfo & firstNode = path.firstNode();
int3 firstTileToGet = firstNode.coord;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
std::stringstream str;
str << "Path found ";
for(auto node : path.nodes)
str << node.targetHero->name << "->" << node.coord.toString() << "; ";
logAi->trace(str.str());
#endif
if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
if(ai->isTileNotReserved(hero.get(), firstTileToGet))
{
danger = path.getTotalDanger(hero);
@@ -148,7 +157,7 @@ Goals::TGoalVec PathfindingManager::findPaths(
{
solution = dest == firstTileToGet
? doVisitTile(firstTileToGet)
: clearWayTo(hero, firstTileToGet);
: clearWayTo(firstNode.targetHero, firstTileToGet);
}
if(solution->invalid())
@@ -161,7 +170,13 @@ Goals::TGoalVec PathfindingManager::findPaths(
solution->evaluationContext.armyLoss += path.armyLoss;
solution->evaluationContext.heroStrength = path.getHeroStrength();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), solution->name());
logAi->trace("It's safe for %s to visit tile %s with danger %s, loss %s, army strength %s, goal %s",
hero->name,
dest.toString(),
std::to_string(danger),
std::to_string(path.armyLoss),
std::to_string(path.heroArmy->getArmyStrength()),
solution->name());
#endif
result.push_back(solution);
@@ -210,9 +225,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
{
if(topObj != hero.get(true)) //the hero we want to free
{
logAi->error("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
return sptr(Goals::Invalid());
logAi->warn("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
}
}
@@ -240,10 +253,10 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
}
void PathfindingManager::updatePaths(std::vector<HeroPtr> heroes)
void PathfindingManager::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
{
logAi->debug("AIPathfinder has been reseted.");
pathfinder->updatePaths(heroes);
pathfinder->updatePaths(heroes, useHeroChain);
}
void PathfindingManager::updatePaths(const HeroPtr & hero)

View File

@@ -20,7 +20,7 @@ public:
virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
virtual void setAI(VCAI * AI) = 0;
virtual void updatePaths(std::vector<HeroPtr> heroes) = 0;
virtual void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) = 0;
virtual void updatePaths(const HeroPtr & hero) = 0;
virtual Goals::TGoalVec howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy = true) const = 0;
virtual Goals::TGoalVec howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy = true) const = 0;
@@ -47,7 +47,7 @@ public:
Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override;
std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
void updatePaths(std::vector<HeroPtr> heroes) override;
void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) override;
void updatePaths(const HeroPtr & hero) override;
STRONG_INLINE

View File

@@ -38,39 +38,10 @@ namespace AIPathfinding
auto srcNode = nodeStorage->getAINode(source.node);
if(srcNode->specialAction)
if(srcNode->specialAction || srcNode->chainOther)
{
// there is some action on source tile which should be performed before we can bypass it
destination.node->theNodeBefore = source.node;
}
auto dstNode = nodeStorage->getAINode(destination.node);
auto srcActor = srcNode->actor;
auto dstActor = dstNode->actor;
if(srcActor == dstActor)
{
return;
}
auto carrierActor = dstActor->carrierParent;
auto otherActor = dstActor->otherParent;
nodeStorage->updateAINode(destination.node, [&](AIPathNode * dstNode) {
if(source.coord == destination.coord)
{
auto carrierNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, carrierActor).get();
auto otherNode = nodeStorage->getOrCreateNode(source.coord, source.node->layer, otherActor).get();
if(destination.coord != carrierNode->coord)
dstNode->theNodeBefore = carrierNode;
dstNode->chainOther = otherNode;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Link Hero exhange at %s, %s -> %s", dstNode->coord.toString(), otherNode->actor->hero->name, carrierNode->actor->hero->name);
#endif
}
});
}
}