diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 4f64cc5c7..9f8d7407a 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -28,6 +28,17 @@ std::string CaptureObjectsBehavior::toString() const return "Capture objects"; } +std::shared_ptr getFirstBlockedAction(const AIPath & path) +{ + for(auto node : path.nodes) + { + if(node.specialAction && !node.specialAction->canAct(node.targetHero)) + return node.specialAction; + } + + return std::shared_ptr(); +} + Goals::TGoalVec CaptureObjectsBehavior::getTasks() { Goals::TGoalVec tasks; @@ -65,6 +76,15 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() logAi->trace("Path found %s", path.toString()); #endif + if(getFirstBlockedAction(path)) + { +#ifdef VCMI_TRACE_PATHFINDER + // TODO: decomposition? + logAi->trace("Ignore path. Action is blocked."); +#endif + continue; + } + if(ai->nullkiller->dangerHitMap->enemyCanKillOurHeroesAlongThePath(path)) { #ifdef VCMI_TRACE_PATHFINDER @@ -78,6 +98,10 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() auto hero = path.targetHero; auto danger = path.getTotalDanger(hero); + + if(danger == 0 && path.exchangeCount > 1) + continue; + auto isSafe = isSafeToVisit(hero, path.heroArmy, danger); #ifdef VCMI_TRACE_PATHFINDER @@ -146,7 +170,8 @@ bool CaptureObjectsBehavior::shouldVisitObject(ObjectIdRef obj) const const int3 pos = objInstance->visitablePos(); - if(vstd::contains(ai->alreadyVisited, objInstance)) + if(objInstance->ID != Obj::CREATURE_GENERATOR1 && vstd::contains(ai->alreadyVisited, objInstance) + || obj->wasVisited(ai->playerID)) { return false; } diff --git a/AI/Nullkiller/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index ae70fd128..a002b3039 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -16,6 +16,7 @@ #include "../Goals/ExecuteHeroChain.h" #include "../Goals/ExchangeSwapTownHeroes.h" #include "lib/mapping/CMap.h" //for victory conditions +#include "lib/mapObjects/MapObjects.h" //for victory conditions #include "lib/CPathfinder.h" extern boost::thread_specific_ptr cb; @@ -78,7 +79,8 @@ bool needToRecruitHero(const CGTownInstance * startupTown) || obj->ID == Obj::WATER_WHEEL) { auto path = paths->getPathInfo(obj->visitablePos()); - if(path->accessible == CGPathNode::BLOCKVIS && path->turns != 255) + if((path->accessible == CGPathNode::BLOCKVIS || path->accessible == CGPathNode::VISIT) + && path->reachable()) { return true; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 591fa197e..8101274d0 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -331,6 +331,7 @@ void AINodeStorage::calculateHeroChain( { if(carrier->armyLoss < carrier->actor->armyValue && (carrier->action != CGPathNode::BATTLE || (carrier->actor->allowBattle && carrier->specialAction)) + && carrier->action != CGPathNode::BLOCKING_VISIT && other->armyLoss < other->actor->armyValue && carrier->actor->canExchange(other->actor)) { @@ -745,6 +746,7 @@ std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) path.armyLoss = node.armyLoss; path.targetObjectDanger = evaluateDanger(pos, path.targetHero); path.chainMask = node.actor->chainMask; + path.exchangeCount = node.actor->actorExchangeCount; fillChainInfo(&node, path, -1); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index a64f769ee..c4b5c2b38 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -10,7 +10,7 @@ #pragma once -#undef VCMI_TRACE_PATHFINDER +#define VCMI_TRACE_PATHFINDER 1 #include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" @@ -50,6 +50,7 @@ struct AIPath const CGHeroInstance * targetHero; const CCreatureSet * heroArmy; uint64_t chainMask; + uint8_t exchangeCount; AIPath(); diff --git a/AI/Nullkiller/Pathfinding/Actions/ISpecialAction.h b/AI/Nullkiller/Pathfinding/Actions/ISpecialAction.h index c8943aaa5..7be99baca 100644 --- a/AI/Nullkiller/Pathfinding/Actions/ISpecialAction.h +++ b/AI/Nullkiller/Pathfinding/Actions/ISpecialAction.h @@ -18,6 +18,11 @@ struct AIPathNode; class ISpecialAction { public: + virtual bool canAct(const CGHeroInstance * hero) const + { + return true; + } + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const = 0; virtual void applyOnDestination( diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 1d765a00f..6505bc7a4 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -10,9 +10,28 @@ #include "StdInc.h" #include "AIMovementAfterDestinationRule.h" #include "../Actions/BattleAction.h" +#include "../../Goals/Invalid.h" namespace AIPathfinding { + class QuestAction : public ISpecialAction + { + public: + QuestAction(QuestInfo questInfo) + { + } + + virtual bool canAct(const CGHeroInstance * hero) const override + { + return false; + } + + virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override + { + return Goals::sptr(Goals::Invalid()); + } + }; + AIMovementAfterDestinationRule::AIMovementAfterDestinationRule( CPlayerSpecificInfoCallback * cb, std::shared_ptr nodeStorage) @@ -50,6 +69,23 @@ namespace AIPathfinding destination.blocked = true; } + if(destination.nodeObject->ID == Obj::QUEST_GUARD || destination.nodeObject->ID == Obj::BORDERGUARD) + { + auto questObj = dynamic_cast(destination.nodeObject); + auto nodeHero = pathfinderHelper->hero; + + if(!destination.nodeObject->wasVisited(nodeHero->tempOwner) + || !questObj->checkQuest(nodeHero)) + { + nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) + { + auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); + + node->specialAction.reset(new QuestAction(questInfo)); + }); + } + } + return; } @@ -141,6 +177,12 @@ namespace AIPathfinding vstd::amax(battleNode->danger, danger); battleNode->specialAction = std::make_shared(destination.coord); + + if(source.nodeObject && isObjectRemovable(source.nodeObject)) + { + battleNode->theNodeBefore = source.node; + } + #ifdef VCMI_TRACE_PATHFINDER logAi->trace( "Begin bypass guard at destination with danger %s while moving %s -> %s", diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index 1de4efdec..2a567f998 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -603,7 +603,7 @@ void VCAI::init(std::shared_ptr CB) if(!fh) fh = new FuzzyHelper(); - if(playerID.getStr(false) == "blue") + if(playerID.getStr(false) == "red") { nullkiller.reset(new Nullkiller()); } @@ -2823,17 +2823,23 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj) { if(obj->tempOwner != h->tempOwner) return true; //flag just in case - bool canRecruitCreatures = false; + const CGDwelling * d = dynamic_cast(obj); + for(auto level : d->creatures) { for(auto c : level.second) { - if(h->getSlotFor(CreatureID(c)) != SlotID()) - canRecruitCreatures = true; + if(level.first + && h->getSlotFor(CreatureID(c)) != SlotID() + && cb->getResourceAmount().canAfford(c.toCreature()->cost)) + { + return true; + } } } - return canRecruitCreatures; + + return false; } case Obj::HILL_FORT: {