diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index 7c047b84a..9d5b01586 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -99,7 +99,7 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() auto hero = path.targetHero; auto danger = path.getTotalDanger(); - if(danger == 0 && path.exchangeCount > 1) + if(ai->ah->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1) continue; auto isSafe = isSafeToVisit(hero, path.heroArmy, danger); @@ -131,13 +131,13 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks() for(auto way : waysToVisitObj) { + if(ai->nullkiller->arePathHeroesLocked(way->getPath())) + continue; + way->evaluationContext.closestWayRatio = way->evaluationContext.movementCost / closestWay->evaluationContext.movementCost; - if(way->hero && ai->nullkiller->canMove(way->hero.h)) - { - tasks.push_back(sptr(*way)); - } + tasks.push_back(sptr(*way)); } } }; diff --git a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp index d11e31fdd..e0aaa1829 100644 --- a/AI/Nullkiller/Behaviors/DefenceBehavior.cpp +++ b/AI/Nullkiller/Behaviors/DefenceBehavior.cpp @@ -227,13 +227,25 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta priority); #endif - tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get()).setpriority(priority))); + tasks.push_back(Goals::sptr(Goals::ExchangeSwapTownHeroes(town, town->visitingHero.get(), HeroLockedReason::DEFENCE).setpriority(priority))); continue; } if(path.turn() <= treat.turn && path.getHeroStrength() * SAFE_ATTACK_CONSTANT >= treat.danger) { + if(ai->nullkiller->arePathHeroesLocked(path)) + { +#if AI_TRACE_LEVEL >= 1 + logAi->trace("Can not move %s to defend town %s with priority %f. Path is locked.", + path.targetHero->name, + town->name, + priority); + +#endif + continue; + } + #if AI_TRACE_LEVEL >= 1 logAi->trace("Move %s to defend town %s with priority %f", path.targetHero->name, @@ -242,8 +254,6 @@ 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/Behaviors/StartupBehavior.cpp b/AI/Nullkiller/Behaviors/StartupBehavior.cpp index a002b3039..5f533c0e1 100644 --- a/AI/Nullkiller/Behaviors/StartupBehavior.cpp +++ b/AI/Nullkiller/Behaviors/StartupBehavior.cpp @@ -18,6 +18,7 @@ #include "lib/mapping/CMap.h" //for victory conditions #include "lib/mapObjects/MapObjects.h" //for victory conditions #include "lib/CPathfinder.h" +#include "../Engine/Nullkiller.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; @@ -147,17 +148,17 @@ Goals::TGoalVec StartupBehavior::getTasks() { if(canRecruitHero || ai->ah->howManyReinforcementsCanGet(visitingHero, garrisonHero) > 200) { - tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100))); + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100))); } } else if(ai->ah->howManyReinforcementsCanGet(garrisonHero, visitingHero) > 200) { - tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero).setpriority(100))); + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, garrisonHero, HeroLockedReason::STARTUP).setpriority(100))); } } else if(canRecruitHero) { - tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero).setpriority(100))); + tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(startupTown, visitingHero, HeroLockedReason::STARTUP).setpriority(100))); } } } @@ -171,7 +172,7 @@ Goals::TGoalVec StartupBehavior::getTasks() { for(const CGTownInstance * town : towns) { - if(town->garrisonHero && town->garrisonHero->movement) + if(town->garrisonHero && town->garrisonHero->movement && ai->nullkiller->getHeroLockedReason(town->garrisonHero) != HeroLockedReason::DEFENCE) tasks.push_back(Goals::sptr(ExchangeSwapTownHeroes(town, nullptr).setpriority(0.0001f))); } } diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index c13359fc3..528ce9ecd 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -79,13 +79,26 @@ void Nullkiller::updateAiState() auto activeHeroes = ai->getMyHeroes(); vstd::erase_if(activeHeroes, [this](const HeroPtr & hero) -> bool{ - return isHeroLocked(hero.h); + auto lockedReason = getHeroLockedReason(hero.h); + + return lockedReason == HeroLockedReason::DEFENCE || lockedReason == HeroLockedReason::STARTUP; }); ai->ah->updatePaths(activeHeroes, true); ai->ah->updateHeroRoles(); } +bool Nullkiller::arePathHeroesLocked(const AIPath & path) const +{ + for(auto & node : path.nodes) + { + if(isHeroLocked(node.targetHero)) + return true; + } + + return false; +} + void Nullkiller::makeTurn() { resetAiState(); @@ -97,17 +110,14 @@ 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); diff --git a/AI/Nullkiller/Engine/Nullkiller.h b/AI/Nullkiller/Engine/Nullkiller.h index 404916a9e..71d6644c2 100644 --- a/AI/Nullkiller/Engine/Nullkiller.h +++ b/AI/Nullkiller/Engine/Nullkiller.h @@ -14,12 +14,23 @@ #include "../Goals/AbstractGoal.h" #include "../Behaviors/Behavior.h" +enum class HeroLockedReason +{ + NOT_LOCKED = 0, + + STARTUP = 1, + + DEFENCE = 2, + + HERO_CHAIN = 3 +}; + class Nullkiller { private: std::unique_ptr priorityEvaluator; const CGHeroInstance * activeHero; - std::set lockedHeroes; + std::map lockedHeroes; public: std::unique_ptr dangerHitMap; @@ -28,10 +39,11 @@ public: void makeTurn(); bool isActive(const CGHeroInstance * hero) const { return activeHero == hero; } bool isHeroLocked(const CGHeroInstance * hero) const { return vstd::contains(lockedHeroes, hero); } + HeroLockedReason getHeroLockedReason(const CGHeroInstance * hero) const { return isHeroLocked(hero) ? lockedHeroes.at(hero) : HeroLockedReason::NOT_LOCKED; } void setActive(const CGHeroInstance * hero) { activeHero = hero; } - void lockHero(const CGHeroInstance * hero) { lockedHeroes.insert(hero); } + void lockHero(const CGHeroInstance * hero, HeroLockedReason lockReason) { lockedHeroes[hero] = lockReason; } void unlockHero(const CGHeroInstance * hero) { lockedHeroes.erase(hero); } - bool canMove(const CGHeroInstance * hero) { return hero->movement; } + bool arePathHeroesLocked(const AIPath & path) const; private: void resetAiState(); diff --git a/AI/Nullkiller/Engine/PriorityEvaluator.cpp b/AI/Nullkiller/Engine/PriorityEvaluator.cpp index fdbf07b74..f613df6dd 100644 --- a/AI/Nullkiller/Engine/PriorityEvaluator.cpp +++ b/AI/Nullkiller/Engine/PriorityEvaluator.cpp @@ -201,10 +201,10 @@ float getEnemyHeroStrategicalValue(const CGHeroInstance * enemy) for(auto obj : objectsUnderTreat) { - objectValue += getStrategicalValue(obj); + vstd::amax(objectValue, getStrategicalValue(obj)); } - return objectValue + enemy->level / 15.0f; + return objectValue / 2.0f + enemy->level / 15.0f; } float getStrategicalValue(const CGObjectInstance * target) @@ -400,7 +400,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) assert(result >= 0); #ifdef VCMI_TRACE_PATHFINDER - logAi->trace("Evaluated %s, hero %s, loss: %f, turns: %f, gold: %d, army gain: %d, danger: %d, role: %s, result %f", + logAi->trace("Evaluated %s, hero %s, loss: %f, turns: %f, gold: %d, army gain: %d, danger: %d, role: %s, strategical value: %f, result %f", task->name(), hero->name, armyLossPersentage, @@ -409,6 +409,7 @@ float PriorityEvaluator::evaluate(Goals::TSubgoal task) armyReward, danger, heroRole ? "scout" : "main", + strategicalValue, result); #endif diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp index ba4d9b9c1..9044efaba 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp @@ -23,8 +23,11 @@ extern FuzzyHelper * fh; using namespace Goals; -ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero) - :CGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero) +ExchangeSwapTownHeroes::ExchangeSwapTownHeroes( + const CGTownInstance * town, + const CGHeroInstance * garrisonHero, + HeroLockedReason lockingReason) + :CGoal(Goals::EXCHANGE_SWAP_TOWN_HEROES), town(town), garrisonHero(garrisonHero), lockingReason(lockingReason) { } @@ -90,7 +93,7 @@ void ExchangeSwapTownHeroes::accept(VCAI * ai) cb->swapGarrisonHero(town); // selected hero left in garrison with strongest army } - ai->nullkiller->lockHero(garrisonHero); + ai->nullkiller->lockHero(garrisonHero, lockingReason); if(town->visitingHero && town->visitingHero != garrisonHero) { diff --git a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h index 80c8a4f47..f8af93c2f 100644 --- a/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h +++ b/AI/Nullkiller/Goals/ExchangeSwapTownHeroes.h @@ -10,6 +10,7 @@ #pragma once #include "CGoal.h" +#include "..\Engine\Nullkiller.h" namespace Goals { @@ -18,9 +19,13 @@ namespace Goals private: const CGTownInstance * town; const CGHeroInstance * garrisonHero; + HeroLockedReason lockingReason; public: - ExchangeSwapTownHeroes(const CGTownInstance * town, const CGHeroInstance * garrisonHero = nullptr); + ExchangeSwapTownHeroes( + const CGTownInstance * town, + const CGHeroInstance * garrisonHero = nullptr, + HeroLockedReason lockingReason = HeroLockedReason::NOT_LOCKED); TGoalVec getAllPossibleSubgoals() override { diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index d393471f0..9020182b4 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.get()); + ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN); continue; } @@ -128,7 +128,7 @@ void ExecuteHeroChain::accept(VCAI * ai) { logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero.name, hero->movement, node.coord.toString()); - ai->nullkiller->lockHero(hero.get()); + ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN); return; } } @@ -148,13 +148,9 @@ void ExecuteHeroChain::accept(VCAI * ai) return; } - - // do not lock hero if it is simple one hero chain - if(chainPath.exchangeCount == 1) - return; - + // no exception means we were not able to rich the tile - ai->nullkiller->lockHero(hero.get()); + ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN); blockedIndexes.insert(node.parentIndex); } catch(goalFulfilledException) diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.h b/AI/Nullkiller/Goals/ExecuteHeroChain.h index 410c7254e..766ddc69b 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.h +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.h @@ -32,5 +32,6 @@ namespace Goals std::string name() const override; std::string completeMessage() const override; virtual bool operator==(const ExecuteHeroChain & other) const override; + const AIPath & getPath() const { return chainPath; } }; } diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index ddcb0b061..f43cc67ef 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -77,7 +77,8 @@ void AINodeStorage::clear() { actors.clear(); heroChainPass = false; - heroChainTurn = 1; + heroChainTurn = 0; + heroChainMaxTurns = 1; } const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const @@ -251,6 +252,16 @@ std::vector AINodeStorage::calculateNeighbours( return neighbours; } +bool AINodeStorage::increaseHeroChainTurnLimit() +{ + if(heroChainTurn >= heroChainMaxTurns) + return false; + + heroChainTurn++; + + return true; +} + bool AINodeStorage::calculateHeroChain() { heroChainPass = true; diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index 8afe10beb..e46a2886d 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -102,6 +102,7 @@ private: std::vector heroChain; bool heroChainPass; // true if we need to calculate hero chain int heroChainTurn; + int heroChainMaxTurns; PlayerColor playerID; public: @@ -113,6 +114,8 @@ public: void initialize(const PathfinderOptions & options, const CGameState * gs) override; + bool increaseHeroChainTurnLimit(); + virtual std::vector getInitialNodes() override; virtual std::vector calculateNeighbours( diff --git a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp index b783b8e75..25dbda5a7 100644 --- a/AI/Nullkiller/Pathfinding/AIPathfinder.cpp +++ b/AI/Nullkiller/Pathfinding/AIPathfinder.cpp @@ -62,12 +62,25 @@ void AIPathfinder::updatePaths(std::vector heroes, bool useHeroChain) } auto config = std::make_shared(cb, ai, storage); + bool continueCalculation = false; - do { + do + { logAi->trace("Recalculate paths pass %d", pass++); cb->calculatePaths(config); - logAi->trace("Recalculate chain pass %d", pass); - useHeroChain = useHeroChain && storage->calculateHeroChain(); - } while(useHeroChain); + if(useHeroChain) + { + logAi->trace("Recalculate chain pass %d", pass); + + continueCalculation = storage->calculateHeroChain(); + + if(!continueCalculation) + { + logAi->trace("Increase chain turn limit"); + + continueCalculation = storage->increaseHeroChainTurnLimit() && storage->calculateHeroChain(); + } + } + } while(continueCalculation); } diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index ad292d062..503744188 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -546,15 +546,22 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop) NET_EVENT_HANDLER; if(sop->what == ObjProperty::OWNER) { - if(myCb->getPlayerRelations(playerID, (PlayerColor)sop->val) == PlayerRelations::ENEMIES) + auto relations = myCb->getPlayerRelations(playerID, (PlayerColor)sop->val); + auto obj = myCb->getObj(sop->id, false); + + if(obj) { - //we want to visit objects owned by oppponents - auto obj = myCb->getObj(sop->id, false); - if(obj) + if(relations == PlayerRelations::ENEMIES) { + //we want to visit objects owned by oppponents addVisitableObj(obj); // TODO: Remove once save compatability broken. In past owned objects were removed from this set vstd::erase_if_present(alreadyVisited, obj); } + else if(relations == PlayerRelations::SAME_PLAYER && obj->ID == Obj::TOWN && nullkiller) + { + // reevaluate defence for a new town + nullkiller->dangerHitMap->reset(); + } } } }