From 1096f2e9e606f0c68b2e9c6d7a24e97ff77f18fe Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Sun, 16 May 2021 14:07:54 +0300 Subject: [PATCH] Nullkiller: town portal fixes --- AI/Nullkiller/Behaviors/DefenceBehavior.cpp | 23 ++- AI/Nullkiller/Engine/Nullkiller.cpp | 13 +- AI/Nullkiller/Engine/Nullkiller.h | 13 +- AI/Nullkiller/Goals/ExecuteHeroChain.cpp | 16 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 185 ++++++++++++-------- AI/Nullkiller/Pathfinding/AINodeStorage.h | 9 +- 6 files changed, 161 insertions(+), 98 deletions(-) diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index fb00872cc..e2ceb5f44 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -62,7 +62,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(town->garrisonHero) { - if(ai->nullkiller->isActive(town->garrisonHero.get())) + if(!ai->nullkiller->isHeroLocked(town->garrisonHero.get())) { tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, nullptr).setpriority(5))); return; @@ -70,8 +70,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta logAi->debug( "Hero %s in garrison of town %s is suposed to defend the town", - town->name, - town->garrisonHero->name); + town->garrisonHero->name, + town->name); return; } @@ -107,9 +107,13 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta { for(auto & treat : treats) { - if(path.turn() < treat.turn && isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)) + if(isSafeToVisit(path.targetHero, path.heroArmy, treat.danger)) { - logAi->debug("Town %s is not in danger.", town->name); + logAi->debug( + "Hero %s can eliminate danger for town %s using path %s.", + path.targetHero->name, + town->name, + path.toString()); return; } @@ -141,15 +145,18 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta if(path.targetHero == town->visitingHero && path.exchangeCount == 1) { #if AI_TRACE_LEVEL >= 1 - logAi->trace("Put %s to garrison of town %s with priority %f", + logAi->trace("Put %s to garrison of town %s with priority %f", path.targetHero->name, town->name, priority); #endif tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get()).setpriority(priority))); + + continue; } - else if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger) + + if(path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger) { #if AI_TRACE_LEVEL >= 1 logAi->trace("Move %s to defend town %s with priority %f", @@ -159,6 +166,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta #endif tasks.push_back(Goals::sptr(Goals::ExecuteHeroChain(path, town).setpriority(priority))); + + continue; } } } diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 3220f445e..0073d2468 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -77,8 +77,8 @@ void Nullkiller::updateAiState() // TODO: move to hero manager auto activeHeroes = ai->getMyHeroes(); - vstd::erase_if(activeHeroes, [&](const HeroPtr & hero) -> bool{ - return vstd::contains(lockedHeroes, hero); + vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{ + return isHeroLocked(hero.h); }); ai->ah->updatePaths(activeHeroes, true); @@ -96,14 +96,17 @@ void Nullkiller::makeTurn() Goals::TGoalVec bestTasks = { choseBestTask(std::make_shared()), choseBestTask(std::make_shared()), - choseBestTask(std::make_shared()), - choseBestTask(std::make_shared()) + choseBestTask(std::make_shared()) }; if(cb->getDate(Date::DAY) == 1) { bestTasks.push_back(choseBestTask(std::make_shared())); } + else + { + bestTasks.push_back(choseBestTask(std::make_shared())); + } Goals::TSubgoal bestTask = choseBestTask(bestTasks); @@ -118,7 +121,7 @@ void Nullkiller::makeTurn() try { - activeHero = bestTask->hero; + activeHero = bestTask->hero.get(); bestTask->accept(ai.get()); } diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 8077338dc..89784f1ee 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -18,18 +18,19 @@ class Nullkiller { private: std::unique_ptr priorityEvaluator; - HeroPtr activeHero; - std::set lockedHeroes; + const CGHeroInstance * activeHero; + std::set lockedHeroes; public: std::unique_ptr dangerHitMap; Nullkiller(); void makeTurn(); - bool isActive(const CGHeroInstance * hero) const { return activeHero.h == hero; } - void setActive(const HeroPtr & hero) { activeHero = hero; } - void lockHero(const HeroPtr & hero) { lockedHeroes.insert(hero); } - void unlockHero(const HeroPtr & hero) { lockedHeroes.erase(hero); } + bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; } + bool isHeroLocked(const CGHeroInstance * hero) const { return vstd::contains(lockedHeroes, hero); } + void setActive(const CGHeroInstance * hero) { activeHero = hero; } + void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); } + void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); } private: void resetAiState(); diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index c731cb9d0..2c2d9990c 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -69,7 +69,7 @@ void ExecuteHeroChain::accept(VCAI * ai) if(vstd::contains(blockedIndexes, i)) { blockedIndexes.insert(node.parentIndex); - ai->nullkiller->lockHero(hero); + ai->nullkiller->lockHero(hero.get()); continue; } @@ -80,21 +80,27 @@ void ExecuteHeroChain::accept(VCAI * ai) { if(hero->movement) { - ai->nullkiller->setActive(hero); + ai->nullkiller->setActive(hero.get()); if(node.specialAction) { - auto specialGoal = node.specialAction->whatToDo(hero); + if(node.specialAction->canAct(hero.get())) + { + auto specialGoal = node.specialAction->whatToDo(hero); - if(specialGoal->isElementar) specialGoal->accept(ai); + } + else + { + //TODO: decompose + } } Goals::VisitTile(node.coord).sethero(hero).accept(ai); } // no exception means we were not able to rich the tile - ai->nullkiller->lockHero(hero); + ai->nullkiller->lockHero(hero.get()); blockedIndexes.insert(node.parentIndex); } catch(goalFulfilledException) diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 7406c8775..5d676a364 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -120,7 +120,11 @@ boost::optional AINodeStorage::getOrCreateNode( std::vector AINodeStorage::getInitialNodes() { if(heroChainPass) + { + calculateTownPortalTeleportations(heroChain); + return heroChain; + } std::vector initialNodes; @@ -147,6 +151,8 @@ std::vector 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 & 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 & 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 & 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 & 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 AINodeStorage::calculateTeleportations( } } - if(source.isInitialPosition) - { - calculateTownPortalTeleportations(source, neighbours, pathfinderHelper); - } - return neighbours; } -void AINodeStorage::calculateTownPortalTeleportations( - const PathNodeInfo & source, - std::vector & neighbours, - const CPathfinderHelper * pathfinderHelper) +void AINodeStorage::getBestInitialNodeForTownPortal() +{ + +} + +void AINodeStorage::calculateTownPortalTeleportations(std::vector & 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)) + std::set actorsOfInitial; + + for(const CGPathNode * node : initialNodes) { - auto towns = cb->getTownsInfo(false); + auto aiNode = getAINode(node); - vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool + actorsOfInitial.insert(aiNode->actor->baseActor); + } + + for(const ChainActor * actor : actorsOfInitial) + { + if(!actor->hero) + continue; + + auto hero = actor->hero; + + if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal)) { - return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES; - }); + auto towns = cb->getTownsInfo(false); - if(!towns.size()) - { - return; - } - - // 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); - - movementCost += source.node->cost; - - if(source.node->moveRemains < movementNeeded) - { - return; - } - - 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{ nearestTown }; - } - - for(const CGTownInstance * targetTown : towns) - { - // TODO: allow to hide visiting hero in garrison - if(targetTown->visitingHero) - continue; - - auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->actor->castActor); - - if(nodeOptional) + if(!towns.size()) { -#ifdef VCMI_TRACE_PATHFINDER - logAi->trace("Adding town portal node at %s", targetTown->name); + return; // no towns no need to run loop further + } + + // TODO: Copy/Paste from TownPortalMechanics + auto skillLevel = hero->getSpellSchoolLevel(townPortal); + auto movementNeeded = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3); + + for(const CGTownInstance * targetTown : towns) + { + CGPathNode * bestNode = nullptr; + + 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; + + // 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) + { + 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(); + AIPathNode * node = nodeOptional.get(); - if(node->action == CGPathNode::UNKNOWN || node->cost > movementCost) - { - node->theNodeBefore = source.node; - node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown)); - node->moveRemains = source.node->moveRemains + movementNeeded; - node->cost = movementCost; + 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(), diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 433da2699..26d1bbed7 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -10,8 +10,8 @@ #pragma once -#define VCMI_TRACE_PATHFINDER 1 -#define AI_TRACE_LEVEL 1 +#define VCMI_TRACE_PATHFINDER 2 +#define AI_TRACE_LEVEL 2 #include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" @@ -165,10 +165,7 @@ private: void cleanupInefectiveChains(std::vector & result) const; void addHeroChain(const std::vector & result); - void calculateTownPortalTeleportations( - const PathNodeInfo & source, - std::vector & neighbours, - const CPathfinderHelper * pathfinderHelper); + void calculateTownPortalTeleportations(std::vector & neighbours); void fillChainInfo(const AIPathNode * node, AIPath & path, int parentIndex) const; void commit( AIPathNode * destination,