|
@@ -120,7 +120,11 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(
|
|
|
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
|
|
{
|
|
|
if(heroChainPass)
|
|
|
+ {
|
|
|
+ calculateTownPortalTeleportations(heroChain);
|
|
|
+
|
|
|
return heroChain;
|
|
|
+ }
|
|
|
|
|
|
std::vector<CGPathNode *> initialNodes;
|
|
|
|
|
@@ -147,6 +151,8 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ calculateTownPortalTeleportations(initialNodes);
|
|
|
+
|
|
|
return initialNodes;
|
|
|
}
|
|
|
|
|
@@ -185,7 +191,7 @@ void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInf
|
|
|
dstNode->specialAction->applyOnDestination(dstNode->actor->hero, destination, source, dstNode, srcNode);
|
|
|
}
|
|
|
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace(
|
|
|
"Commited %s -> %s, cost: %f, hero: %s, mask: %x, army: %i",
|
|
|
source.coord.toString(),
|
|
@@ -310,7 +316,7 @@ void AINodeStorage::calculateHeroChain(
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace(
|
|
|
"Thy exchange %s[%i] -> %s[%i] at %s",
|
|
|
node->actor->toString(),
|
|
@@ -335,7 +341,7 @@ void AINodeStorage::calculateHeroChain(
|
|
|
&& other->armyLoss < other->actor->armyValue
|
|
|
&& carrier->actor->canExchange(other->actor))
|
|
|
{
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace(
|
|
|
"Exchange allowed %s[%i] -> %s[%i] at %s",
|
|
|
other->actor->toString(),
|
|
@@ -352,7 +358,7 @@ void AINodeStorage::calculateHeroChain(
|
|
|
|
|
|
if(hasLessMp && hasLessExperience)
|
|
|
{
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace("Exchange at %s is ineficient. Blocked.", carrier->coord.toString());
|
|
|
#endif
|
|
|
return;
|
|
@@ -376,7 +382,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
|
|
|
|
|
if(!chainNodeOptional)
|
|
|
{
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace("Exchange at %s can not allocate node. Blocked.", carrier->coord.toString());
|
|
|
#endif
|
|
|
continue;
|
|
@@ -386,7 +392,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
|
|
|
|
|
if(exchangeNode->action != CGPathNode::ENodeAction::UNKNOWN)
|
|
|
{
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace("Exchange at %s node is already in use. Blocked.", carrier->coord.toString());
|
|
|
#endif
|
|
|
continue;
|
|
@@ -394,7 +400,7 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
|
|
|
|
|
if(exchangeNode->turns != 0xFF && exchangeNode->cost < chainInfo.cost)
|
|
|
{
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace(
|
|
|
"Exchange at %s is is not effective enough. %f < %f",
|
|
|
exchangeNode->coord.toString(),
|
|
@@ -406,10 +412,16 @@ void AINodeStorage::addHeroChain(const std::vector<ExchangeCandidate> & result)
|
|
|
|
|
|
commit(exchangeNode, carrier, carrier->action, chainInfo.turns, chainInfo.moveRemains, chainInfo.cost);
|
|
|
|
|
|
+ if(carrier->specialAction || carrier->chainOther)
|
|
|
+ {
|
|
|
+ // there is some action on source tile which should be performed before we can bypass it
|
|
|
+ exchangeNode->theNodeBefore = carrier;
|
|
|
+ }
|
|
|
+
|
|
|
exchangeNode->chainOther = other;
|
|
|
exchangeNode->armyLoss = chainInfo.armyLoss;
|
|
|
|
|
|
-#if VCMI_TRACE_PATHFINDER >= 2
|
|
|
+#if AI_TRACE_LEVEL >= 2
|
|
|
logAi->trace(
|
|
|
"Chain accepted at %s %s -> %s, mask %x, cost %f, army %i",
|
|
|
exchangeNode->coord.toString(),
|
|
@@ -566,85 +578,120 @@ std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if(source.isInitialPosition)
|
|
|
- {
|
|
|
- calculateTownPortalTeleportations(source, neighbours, pathfinderHelper);
|
|
|
- }
|
|
|
-
|
|
|
return neighbours;
|
|
|
}
|
|
|
|
|
|
-void AINodeStorage::calculateTownPortalTeleportations(
|
|
|
- const PathNodeInfo & source,
|
|
|
- std::vector<CGPathNode *> & neighbours,
|
|
|
- const CPathfinderHelper * pathfinderHelper)
|
|
|
+void AINodeStorage::getBestInitialNodeForTownPortal()
|
|
|
+{
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+void AINodeStorage::calculateTownPortalTeleportations(std::vector<CGPathNode *> & initialNodes)
|
|
|
{
|
|
|
SpellID spellID = SpellID::TOWN_PORTAL;
|
|
|
const CSpell * townPortal = spellID.toSpell();
|
|
|
- auto srcNode = getAINode(source.node);
|
|
|
- auto hero = srcNode->actor->hero;
|
|
|
|
|
|
- if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
|
|
|
- {
|
|
|
- auto towns = cb->getTownsInfo(false);
|
|
|
+ std::set<const ChainActor *> actorsOfInitial;
|
|
|
|
|
|
- vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
|
|
|
- {
|
|
|
- return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
|
|
|
- });
|
|
|
+ for(const CGPathNode * node : initialNodes)
|
|
|
+ {
|
|
|
+ auto aiNode = getAINode(node);
|
|
|
|
|
|
- if(!towns.size())
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
+ actorsOfInitial.insert(aiNode->actor->baseActor);
|
|
|
+ }
|
|
|
|
|
|
- // TODO: Copy/Paste from TownPortalMechanics
|
|
|
- auto skillLevel = hero->getSpellSchoolLevel(townPortal);
|
|
|
- auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
|
|
|
- float movementCost = (float)movementNeeded / (float)pathfinderHelper->getMaxMovePoints(EPathfindingLayer::LAND);
|
|
|
+ for(const ChainActor * actor : actorsOfInitial)
|
|
|
+ {
|
|
|
+ if(!actor->hero)
|
|
|
+ continue;
|
|
|
|
|
|
- movementCost += source.node->cost;
|
|
|
+ auto hero = actor->hero;
|
|
|
|
|
|
- if(source.node->moveRemains < movementNeeded)
|
|
|
+ if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
|
|
|
{
|
|
|
- return;
|
|
|
- }
|
|
|
+ auto towns = cb->getTownsInfo(false);
|
|
|
|
|
|
- if(skillLevel < SecSkillLevel::ADVANCED)
|
|
|
- {
|
|
|
- const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
|
|
|
+ vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
|
|
|
{
|
|
|
- return source.coord.dist2dSQ(t->visitablePos());
|
|
|
+ return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
|
|
|
});
|
|
|
|
|
|
- towns = std::vector<const CGTownInstance *>{ nearestTown };
|
|
|
- }
|
|
|
-
|
|
|
- for(const CGTownInstance * targetTown : towns)
|
|
|
- {
|
|
|
- // TODO: allow to hide visiting hero in garrison
|
|
|
- if(targetTown->visitingHero)
|
|
|
- continue;
|
|
|
+ if(!towns.size())
|
|
|
+ {
|
|
|
+ return; // no towns no need to run loop further
|
|
|
+ }
|
|
|
|
|
|
- auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->actor->castActor);
|
|
|
+ // TODO: Copy/Paste from TownPortalMechanics
|
|
|
+ auto skillLevel = hero->getSpellSchoolLevel(townPortal);
|
|
|
+ auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
|
|
|
|
|
|
- if(nodeOptional)
|
|
|
+ for(const CGTownInstance * targetTown : towns)
|
|
|
{
|
|
|
-#ifdef VCMI_TRACE_PATHFINDER
|
|
|
- logAi->trace("Adding town portal node at %s", targetTown->name);
|
|
|
-#endif
|
|
|
+ CGPathNode * bestNode = nullptr;
|
|
|
|
|
|
- AIPathNode * node = nodeOptional.get();
|
|
|
+ for(CGPathNode * node : initialNodes)
|
|
|
+ {
|
|
|
+ auto aiNode = getAINode(node);
|
|
|
+
|
|
|
+ if(aiNode->actor->baseActor != actor
|
|
|
+ || node->layer != EPathfindingLayer::LAND
|
|
|
+ || node->moveRemains < movementNeeded)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(skillLevel < SecSkillLevel::ADVANCED)
|
|
|
+ {
|
|
|
+ const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
|
|
|
+ {
|
|
|
+ return node->coord.dist2dSQ(t->visitablePos());
|
|
|
+ });
|
|
|
+
|
|
|
+ if(targetTown != nearestTown)
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!bestNode || bestNode->cost > node->cost)
|
|
|
+ bestNode = node;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!bestNode)
|
|
|
+ continue;
|
|
|
|
|
|
- if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
|
|
|
+ // TODO: allow to hide visiting hero in garrison
|
|
|
+ if(targetTown->visitingHero && targetTown->visitingHero != hero)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, actor->castActor);
|
|
|
+
|
|
|
+ if(nodeOptional)
|
|
|
{
|
|
|
- node->theNodeBefore = source.node;
|
|
|
- node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
|
|
|
- node->moveRemains = source.node->moveRemains + movementNeeded;
|
|
|
- node->cost = movementCost;
|
|
|
+ float movementCost = (float)movementNeeded / (float)hero->maxMovePoints(EPathfindingLayer::LAND);
|
|
|
+
|
|
|
+ movementCost += bestNode->cost;
|
|
|
+
|
|
|
+#ifdef AI_TRACE_LEVEL >= 1
|
|
|
+ logAi->trace("Adding town portal node at %s", targetTown->name);
|
|
|
+#endif
|
|
|
+
|
|
|
+ AIPathNode * node = nodeOptional.get();
|
|
|
+
|
|
|
+ if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost)
|
|
|
+ {
|
|
|
+ commit(
|
|
|
+ node,
|
|
|
+ getAINode(bestNode),
|
|
|
+ CGPathNode::TELEPORT_NORMAL,
|
|
|
+ bestNode->turns,
|
|
|
+ bestNode->moveRemains - movementNeeded,
|
|
|
+ movementCost);
|
|
|
+
|
|
|
+ node->theNodeBefore = bestNode;
|
|
|
+ node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
|
|
|
+ }
|
|
|
+
|
|
|
+ initialNodes.push_back(node);
|
|
|
}
|
|
|
-
|
|
|
- neighbours.push_back(node);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -679,7 +726,7 @@ bool AINodeStorage::hasBetterChain(
|
|
|
{
|
|
|
if(node.cost < candidateNode->cost)
|
|
|
{
|
|
|
-#ifdef VCMI_TRACE_PATHFINDER
|
|
|
+#ifdef AI_TRACE_LEVEL >= 1
|
|
|
logAi->trace(
|
|
|
"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
|
|
|
source->coord.toString(),
|