diff --git a/AI/Nullkiller/AIUtility.cpp b/AI/Nullkiller/AIUtility.cpp index 3dbf404a5..b6ecb376c 100644 --- a/AI/Nullkiller/AIUtility.cpp +++ b/AI/Nullkiller/AIUtility.cpp @@ -18,6 +18,8 @@ #include "../../lib/mapObjects/MapObjects.h" #include "../../lib/mapping/CMapDefines.h" +#include "../../lib/CModHandler.h" + extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; @@ -272,14 +274,6 @@ bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, Pla return false; } -bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder -{ - if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE) - return false; - auto gate = dynamic_cast(cb->getTile(tileToHit)->topVisitableObj()); - return !gate->passableFor(ai->playerID); -} - bool isBlockVisitObj(const int3 & pos) { if(auto obj = cb->getTopObj(pos)) @@ -358,4 +352,124 @@ uint64_t timeElapsed(boost::chrono::time_point star auto end = boost::chrono::high_resolution_clock::now(); return boost::chrono::duration_cast(end - start).count(); +} + +// todo: move to obj manager +bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj) +{ + switch(obj->ID) + { + case Obj::TOWN: + case Obj::HERO: //never visit our heroes at random + return obj->tempOwner != h->tempOwner; //do not visit our towns at random + case Obj::BORDER_GATE: + { + for(auto q : ai->myCb->getMyQuests()) + { + if(q.obj == obj) + { + return false; // do not visit guards or gates when wandering + } + } + return true; //we don't have this quest yet + } + case Obj::BORDERGUARD: //open borderguard if possible + return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); + case Obj::SEER_HUT: + case Obj::QUEST_GUARD: + { + for(auto q : ai->myCb->getMyQuests()) + { + if(q.obj == obj) + { + if(q.quest->checkQuest(h)) + return true; //we completed the quest + else + return false; //we can't complete this quest + } + } + return true; //we don't have this quest yet + } + case Obj::CREATURE_GENERATOR1: + { + if(obj->tempOwner != h->tempOwner) + return true; //flag just in case + + const CGDwelling * d = dynamic_cast(obj); + + for(auto level : d->creatures) + { + for(auto c : level.second) + { + if(level.first + && h->getSlotFor(CreatureID(c)) != SlotID() + && cb->getResourceAmount().canAfford(c.toCreature()->cost)) + { + return true; + } + } + } + + return false; + } + case Obj::HILL_FORT: + { + for(auto slot : h->Slots()) + { + if(slot.second->type->upgrades.size()) + return true; //TODO: check price? + } + return false; + } + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_ONE_WAY_EXIT: + case Obj::MONOLITH_TWO_WAY: + case Obj::WHIRLPOOL: + return false; + case Obj::SCHOOL_OF_MAGIC: + case Obj::SCHOOL_OF_WAR: + { + if(cb->getResourceAmount(Res::GOLD) < 1000) + return false; + break; + } + case Obj::LIBRARY_OF_ENLIGHTENMENT: + if(h->level < 12) + return false; + break; + case Obj::TREE_OF_KNOWLEDGE: + { + if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT) + return false; + + TResources myRes = cb->getResourceAmount(); + if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10) + return false; + break; + } + case Obj::MAGIC_WELL: + return h->mana < h->manaLimit(); + case Obj::PRISON: + return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; + case Obj::TAVERN: + { + //TODO: make AI actually recruit heroes + //TODO: only on request + if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) + return false; + else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) + return false; + break; + } + case Obj::BOAT: + return false; + //Boats are handled by pathfinder + case Obj::EYE_OF_MAGI: + return false; //this object is useless to visit, but could be visited indefinitely + } + + if(obj->wasVisited(h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player); + return false; + + return true; } \ No newline at end of file diff --git a/AI/Nullkiller/AIUtility.h b/AI/Nullkiller/AIUtility.h index b9fd8b3e5..f1595a2e5 100644 --- a/AI/Nullkiller/AIUtility.h +++ b/AI/Nullkiller/AIUtility.h @@ -167,7 +167,6 @@ void foreach_neighbour(const int3 & pos, std::function f void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function foo); // avoid costly retrieval of thread-specific pointer bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater); -//bool isBlockedBorderGate(int3 tileToHit); bool isObjectPassable(const CGObjectInstance * obj); bool isObjectPassable(const CGObjectInstance * obj, PlayerColor playerColor, PlayerRelations::PlayerRelations objectRelations); bool isBlockVisitObj(const int3 & pos); @@ -184,6 +183,9 @@ bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2 uint64_t timeElapsed(boost::chrono::time_point start); +// todo: move to obj manager +bool shouldVisit(const CGHeroInstance * h, const CGObjectInstance * obj); + class CDistanceSorter { const CGHeroInstance * hero; diff --git a/AI/Nullkiller/Analyzers/HeroManager.cpp b/AI/Nullkiller/Analyzers/HeroManager.cpp index 0af3b5cd8..9c1898937 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.cpp +++ b/AI/Nullkiller/Analyzers/HeroManager.cpp @@ -89,11 +89,42 @@ float HeroManager::evaluateFightingStrength(const CGHeroInstance * hero) const return evaluateSpeciality(hero) + wariorSkillsScores.evaluateSecSkills(hero) + hero->level * 1.5f; } +std::vector> clusterizeHeroes(CCallback * cb, std::vector heroes) +{ + std::vector> clusters; + + for(auto hero : heroes) + { + auto paths = cb->getPathsInfo(hero); + std::vector newCluster = {hero}; + + for(auto cluster = clusters.begin(); cluster != clusters.end();) + { + auto hero = std::find_if(cluster->begin(), cluster->end(), [&](const CGHeroInstance * h) -> bool + { + return paths->getNode(h->visitablePos())->turns <= SCOUT_TURN_DISTANCE_LIMIT; + }); + + if(hero != cluster->end()) + { + vstd::concatenate(newCluster, *cluster); + clusters.erase(cluster); + } + else + cluster++; + } + + clusters.push_back(newCluster); + } + + return clusters; +} + void HeroManager::update() { logAi->trace("Start analysing our heroes"); - std::map scores; + std::map scores; auto myHeroes = cb->getHeroesInfo(); for(auto & hero : myHeroes) @@ -101,16 +132,43 @@ void HeroManager::update() scores[hero] = evaluateFightingStrength(hero); } - std::sort(myHeroes.begin(), myHeroes.end(), [&](const HeroPtr & h1, const HeroPtr & h2) -> bool + auto scoreSort = [&](const CGHeroInstance * h1, const CGHeroInstance * h2) -> bool { return scores.at(h1) > scores.at(h2); - }); + }; - int mainHeroCount = (myHeroes.size() + 2) / 3; + int globalMainCount = std::min(((int)myHeroes.size() + 2) / 3, cb->getMapSize().x / 100 + 1); + + std::sort(myHeroes.begin(), myHeroes.end(), scoreSort); + + for(auto hero : myHeroes) + { + heroRoles[hero] = (globalMainCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; + } + + for(auto cluster : clusterizeHeroes(cb, myHeroes)) + { + std::sort(cluster.begin(), cluster.end(), scoreSort); + + auto localMainCountMax = (cluster.size() + 2) / 3; + + for(auto hero : cluster) + { + if(heroRoles[hero] != HeroRole::MAIN) + { + heroRoles[hero] = HeroRole::MAIN; + break; + } + + localMainCountMax--; + + if(localMainCountMax == 0) + break; + } + } for(auto hero : myHeroes) { - heroRoles[hero] = (mainHeroCount--) > 0 ? HeroRole::MAIN : HeroRole::SCOUT; logAi->trace("Hero %s has role %s", hero->name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout"); } } diff --git a/AI/Nullkiller/Analyzers/HeroManager.h b/AI/Nullkiller/Analyzers/HeroManager.h index 79cae4126..5ac745c02 100644 --- a/AI/Nullkiller/Analyzers/HeroManager.h +++ b/AI/Nullkiller/Analyzers/HeroManager.h @@ -51,12 +51,12 @@ private: static SecondarySkillEvaluator wariorSkillsScores; static SecondarySkillEvaluator scountSkillsScores; - CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback + CCallback * cb; //this is enough, but we downcast from CCallback const Nullkiller * ai; std::map heroRoles; public: - HeroManager(CPlayerSpecificInfoCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} + HeroManager(CCallback * CB, const Nullkiller * ai) : cb(CB), ai(ai) {} const std::map & getHeroRoles() const override; HeroRole getHeroRole(const HeroPtr & hero) const override; int selectBestSkill(const HeroPtr & hero, const std::vector & skills) const override; diff --git a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp index 29810a57d..dfd8cab20 100644 --- a/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp +++ b/AI/Nullkiller/Analyzers/ObjectClusterizer.cpp @@ -122,13 +122,17 @@ const CGObjectInstance * ObjectClusterizer::getBlocker(const AIPath & path) cons || blocker->ID == Obj::MONSTER || blocker->ID == Obj::GARRISON2 || blocker->ID == Obj::BORDERGUARD - || blocker->ID == Obj::QUEST_GUARD || blocker->ID == Obj::BORDER_GATE || blocker->ID == Obj::SHIPYARD) { if(!isObjectPassable(blocker)) return blocker; } + + if(blocker->ID == Obj::QUEST_GUARD && node->actionIsBlocked) + { + return blocker; + } } return nullptr; @@ -179,13 +183,25 @@ void ObjectClusterizer::clusterize() blockedObjects.clear(); Obj ignoreObjects[] = { - Obj::MONSTER, - Obj::SIGN, - Obj::REDWOOD_OBSERVATORY, - Obj::MONOLITH_TWO_WAY, + Obj::BOAT, + Obj::EYE_OF_MAGI, Obj::MONOLITH_ONE_WAY_ENTRANCE, Obj::MONOLITH_ONE_WAY_EXIT, - Obj::BUOY + Obj::MONOLITH_TWO_WAY, + Obj::SUBTERRANEAN_GATE, + Obj::WHIRLPOOL, + Obj::BUOY, + Obj::SIGN, + Obj::SIGN, + Obj::GARRISON, + Obj::MONSTER, + Obj::GARRISON2, + Obj::BORDERGUARD, + Obj::QUEST_GUARD, + Obj::BORDER_GATE, + Obj::REDWOOD_OBSERVATORY, + Obj::CARTOGRAPHER, + Obj::PILLAR_OF_FIRE }; logAi->debug("Begin object clusterization"); @@ -195,10 +211,19 @@ void ObjectClusterizer::clusterize() if(!shouldVisitObject(obj)) continue; +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Check object %s%s.", obj->getObjectName(), obj->visitablePos().toString()); +#endif + auto paths = ai->pathfinder->getPathInfo(obj->visitablePos()); if(paths.empty()) + { +#if AI_TRACE_LEVEL >= 2 + logAi->trace("No paths found."); +#endif continue; + } std::sort(paths.begin(), paths.end(), [](const AIPath & p1, const AIPath & p2) -> bool { @@ -209,19 +234,29 @@ void ObjectClusterizer::clusterize() { farObjects.addObject(obj, paths.front(), 0); +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Object ignored. Moved to far objects with path %s", paths.front().toString()); +#endif + continue; } - bool added = false; bool directlyAccessible = false; std::set heroesProcessed; for(auto & path : paths) { - if(vstd::contains(heroesProcessed, path.targetHero)) - continue; +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Checking path %s", path.toString()); +#endif - heroesProcessed.insert(path.targetHero); + if(!shouldVisit(path.targetHero, obj)) + { +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Hero %s does not need to visit %s", path.targetHero->name, obj->getObjectName()); +#endif + continue; + } if(path.nodes.size() > 1) { @@ -229,6 +264,16 @@ void ObjectClusterizer::clusterize() if(blocker) { + if(vstd::contains(heroesProcessed, path.targetHero)) + { + #if AI_TRACE_LEVEL >= 2 + logAi->trace("Hero %s is already processed.", path.targetHero->name); + #endif + continue; + } + + heroesProcessed.insert(path.targetHero); + auto cluster = blockedObjects[blocker]; if(!cluster) @@ -237,34 +282,64 @@ void ObjectClusterizer::clusterize() blockedObjects[blocker] = cluster; } - if(!vstd::contains(cluster->objects, obj)) - { - float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); + float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(path, obj))); - cluster->addObject(obj, path, priority); + if(priority < MIN_PRIORITY) + continue; - added = true; - } + cluster->addObject(obj, path, priority); + +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Path added to cluster %s%s", blocker->getObjectName(), blocker->visitablePos().toString()); +#endif + } + else + { + directlyAccessible = true; } } else { directlyAccessible = true; } + + heroesProcessed.insert(path.targetHero); } - if(!added || directlyAccessible) + if(directlyAccessible) { AIPath & shortestPath = paths.front(); float priority = ai->priorityEvaluator->evaluate(Goals::sptr(Goals::ExecuteHeroChain(shortestPath, obj))); - if(shortestPath.turn() <= 2 || priority > 0.6f) - nearObjects.addObject(obj, shortestPath, 0); + if(priority < MIN_PRIORITY) + continue; + + bool interestingObject = shortestPath.turn() <= 2 || priority > 0.5f; + + if(interestingObject) + { + nearObjects.addObject(obj, shortestPath, priority); + } else - farObjects.addObject(obj, shortestPath, 0); + { + farObjects.addObject(obj, shortestPath, priority); + } + +#if AI_TRACE_LEVEL >= 2 + logAi->trace("Path %s added to %s objects. Turn: %d, priority: %f", + shortestPath.toString(), + interestingObject ? "near" : "far", + shortestPath.turn(), + priority); +#endif } } + vstd::erase_if(blockedObjects, [](std::pair> pair) -> bool + { + return pair.second->objects.empty(); + }); + logAi->trace("Near objects count: %i", nearObjects.objects.size()); logAi->trace("Far objects count: %i", farObjects.objects.size()); for(auto pair : blockedObjects) diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp index c9466ee84..fbba09c1f 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.cpp @@ -14,9 +14,6 @@ #include "../Goals/ExecuteHeroChain.h" #include "CaptureObjectsBehavior.h" #include "../AIUtility.h" -#include "../../../lib/mapping/CMap.h" //for victory conditions -#include "../../../lib/CPathfinder.h" -#include "../../../lib/CModHandler.h" extern boost::thread_specific_ptr cb; extern boost::thread_specific_ptr ai; @@ -163,7 +160,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const logAi->trace("Checking object %s, %s", objToVisit->getObjectName(), objToVisit->visitablePos().toString()); #endif - if(!shouldVisitObject(objToVisit)) + if(!objectMatchesFilter(objToVisit)) continue; const int3 pos = objToVisit->visitablePos(); @@ -199,7 +196,7 @@ Goals::TGoalVec CaptureObjectsBehavior::decompose() const return tasks; } -bool CaptureObjectsBehavior::shouldVisitObject(const CGObjectInstance * obj) const +bool CaptureObjectsBehavior::objectMatchesFilter(const CGObjectInstance * obj) const { if(objectTypes.size() && !vstd::contains(objectTypes, obj->ID.num)) { @@ -213,122 +210,3 @@ bool CaptureObjectsBehavior::shouldVisitObject(const CGObjectInstance * obj) con return true; } - -bool CaptureObjectsBehavior::shouldVisit(HeroPtr h, const CGObjectInstance * obj) -{ - switch(obj->ID) - { - case Obj::TOWN: - case Obj::HERO: //never visit our heroes at random - return obj->tempOwner != h->tempOwner; //do not visit our towns at random - case Obj::BORDER_GATE: - { - for(auto q : ai->myCb->getMyQuests()) - { - if(q.obj == obj) - { - return false; // do not visit guards or gates when wandering - } - } - return true; //we don't have this quest yet - } - case Obj::BORDERGUARD: //open borderguard if possible - return (dynamic_cast(obj))->wasMyColorVisited(ai->playerID); - case Obj::SEER_HUT: - case Obj::QUEST_GUARD: - { - for(auto q : ai->myCb->getMyQuests()) - { - if(q.obj == obj) - { - if(q.quest->checkQuest(h.h)) - return true; //we completed the quest - else - return false; //we can't complete this quest - } - } - return true; //we don't have this quest yet - } - case Obj::CREATURE_GENERATOR1: - { - if(obj->tempOwner != h->tempOwner) - return true; //flag just in case - - const CGDwelling * d = dynamic_cast(obj); - - for(auto level : d->creatures) - { - for(auto c : level.second) - { - if(level.first - && h->getSlotFor(CreatureID(c)) != SlotID() - && cb->getResourceAmount().canAfford(c.toCreature()->cost)) - { - return true; - } - } - } - - return false; - } - case Obj::HILL_FORT: - { - for(auto slot : h->Slots()) - { - if(slot.second->type->upgrades.size()) - return true; //TODO: check price? - } - return false; - } - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_ONE_WAY_EXIT: - case Obj::MONOLITH_TWO_WAY: - case Obj::WHIRLPOOL: - return false; - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - { - if(cb->getResourceAmount(Res::GOLD) < 1000) - return false; - break; - } - case Obj::LIBRARY_OF_ENLIGHTENMENT: - if(h->level < 12) - return false; - break; - case Obj::TREE_OF_KNOWLEDGE: - { - if(ai->nullkiller->heroManager->getHeroRole(h) == HeroRole::SCOUT) - return false; - - TResources myRes = cb->getResourceAmount(); - if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10) - return false; - break; - } - case Obj::MAGIC_WELL: - return h->mana < h->manaLimit(); - case Obj::PRISON: - return ai->myCb->getHeroesInfo().size() < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER; - case Obj::TAVERN: - { - //TODO: make AI actually recruit heroes - //TODO: only on request - if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER) - return false; - else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) - return false; - break; - } - case Obj::BOAT: - return false; - //Boats are handled by pathfinder - case Obj::EYE_OF_MAGI: - return false; //this object is useless to visit, but could be visited indefinitely - } - - if(obj->wasVisited(*h)) //it must pointer to hero instance, heroPtr calls function wasVisited(ui8 player); - return false; - - return true; -} diff --git a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h index 800adc032..45a4a907c 100644 --- a/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h +++ b/AI/Nullkiller/Behaviors/CaptureObjectsBehavior.h @@ -68,8 +68,7 @@ namespace Goals static Goals::TGoalVec getVisitGoals(const std::vector & paths, const CGObjectInstance * objToVisit = nullptr); private: - bool shouldVisitObject(const CGObjectInstance * obj) const; - static bool shouldVisit(HeroPtr h, const CGObjectInstance * obj); + bool objectMatchesFilter(const CGObjectInstance * obj) const; }; } diff --git a/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp b/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp index 9bca7a21b..0c01bb14f 100644 --- a/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp +++ b/AI/Nullkiller/Behaviors/CompleteQuestBehavior.cpp @@ -42,13 +42,14 @@ TGoalVec CompleteQuest::decompose() const } return solutions;*/ - logAi->debug("Trying to realize quest: %s", questToString()); if(q.obj && (q.obj->ID == Obj::BORDER_GATE || q.obj->ID == Obj::BORDERGUARD)) { return missionKeymaster(); } + logAi->debug("Trying to realize quest: %s", questToString()); + switch(q.quest->missionType) { case CQuest::MISSION_ART: @@ -107,7 +108,7 @@ TGoalVec CompleteQuest::tryCompleteQuest() const { TGoalVec solutions; - auto tasks = CaptureObjectsBehavior(q.obj).decompose(); //TODO: choose best / free hero from among many possibilities? + auto tasks = CaptureObjectsBehavior(q.obj).decompose(); for(auto task : tasks) { @@ -177,14 +178,14 @@ TGoalVec CompleteQuest::missionLevel() const TGoalVec CompleteQuest::missionKeymaster() const { - TGoalVec solutions = tryCompleteQuest(); - - if(solutions.empty()) + if(isObjectPassable(q.obj)) + { + return CaptureObjectsBehavior(q.obj).decompose(); + } + else { return CaptureObjectsBehavior().ofType(Obj::KEYMASTER, q.obj->subID).decompose(); } - - return solutions; } TGoalVec CompleteQuest::missionResources() const diff --git a/AI/Nullkiller/Engine/Nullkiller.cpp b/AI/Nullkiller/Engine/Nullkiller.cpp index 556b6f998..5269a09ff 100644 --- a/AI/Nullkiller/Engine/Nullkiller.cpp +++ b/AI/Nullkiller/Engine/Nullkiller.cpp @@ -182,6 +182,7 @@ void Nullkiller::updateAiState(int pass) PathfinderSettings cfg; cfg.useHeroChain = true; + cfg.scoutTurnDistanceLimit = SCOUT_TURN_DISTANCE_LIMIT; pathfinder->updatePaths(activeHeroes, cfg); diff --git a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp index e8ca5b50c..f941dcaee 100644 --- a/AI/Nullkiller/Goals/ExecuteHeroChain.cpp +++ b/AI/Nullkiller/Goals/ExecuteHeroChain.cpp @@ -20,7 +20,7 @@ extern boost::thread_specific_ptr ai; using namespace Goals; ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance * obj) - :ElementarGoal(Goals::EXECUTE_HERO_CHAIN), chainPath(path) + :ElementarGoal(Goals::EXECUTE_HERO_CHAIN), chainPath(path), closestWayRatio(1) { hero = path.targetHero; tile = path.targetTile(); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index 24b9f1e75..2a3e77d66 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -19,13 +19,30 @@ #include "../../../lib/PathfinderUtil.h" #include "../../../lib/CPlayerState.h" -/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations) -boost::multi_array nodes; +std::shared_ptr> AISharedStorage::shared; + +AISharedStorage::AISharedStorage(int3 sizes) +{ + if(!shared){ + shared.reset(new boost::multi_array( + boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][AINodeStorage::NUM_CHAINS])); + } + + nodes = shared; +} + +AISharedStorage::~AISharedStorage() +{ + nodes.reset(); + if(shared && shared.use_count() == 1) + { + shared.reset(); + } +} AINodeStorage::AINodeStorage(const Nullkiller * ai, const int3 & Sizes) - : sizes(Sizes), ai(ai), cb(ai->cb.get()) + : sizes(Sizes), ai(ai), cb(ai->cb.get()), nodes(Sizes) { - nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]); dangerEvaluator.reset(new FuzzyHelper(ai)); } @@ -104,9 +121,7 @@ boost::optional AINodeStorage::getOrCreateNode( const EPathfindingLayer layer, const ChainActor * actor) { - auto chains = nodes[pos.x][pos.y][pos.z][layer]; - - for(AIPathNode & node : chains) + for(AIPathNode & node : nodes.get(pos, layer)) { if(node.actor == actor) { @@ -165,10 +180,8 @@ std::vector AINodeStorage::getInitialNodes() void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility) { - for(int i = 0; i < NUM_CHAINS; i++) + for(AIPathNode & heroNode : nodes.get(coord, layer)) { - AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i]; - heroNode.actor = nullptr; heroNode.danger = 0; heroNode.manaCost = 0; @@ -279,7 +292,7 @@ bool AINodeStorage::calculateHeroChainFinal() { foreach_tile_pos([&](const int3 & pos) { - auto chains = nodes[pos.x][pos.y][pos.z][layer]; + auto chains = nodes.get(pos, layer); for(AIPathNode & node : chains) { @@ -313,7 +326,7 @@ bool AINodeStorage::calculateHeroChain() { foreach_tile_pos([&](const int3 & pos) { - auto chains = nodes[pos.x][pos.y][pos.z][layer]; + auto chains = nodes.get(pos, layer); existingChains.resize(0); newChains.resize(0); @@ -394,7 +407,7 @@ void AINodeStorage::cleanupInefectiveChains(std::vector & res vstd::erase_if(result, [&](const ExchangeCandidate & chainInfo) -> bool { auto pos = chainInfo.coord; - auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND]; + auto chains = nodes.get(pos, EPathfindingLayer::LAND); return hasBetterChain(chainInfo.carrierParent, &chainInfo, chains) || hasBetterChain(chainInfo.carrierParent, &chainInfo, result); @@ -910,7 +923,7 @@ void AINodeStorage::calculateTownPortalTeleportations(std::vector bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const { auto pos = destination.coord; - auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND]; + auto chains = nodes.get(pos, EPathfindingLayer::LAND); return hasBetterChain(source.node, getAINode(destination.node), chains); } @@ -950,7 +963,7 @@ bool AINodeStorage::hasBetterChain( } } - if(candidateActor->chainMask != node.actor->chainMask && heroChainPass == EHeroChainPass::CHAIN) + if(candidateActor->chainMask != node.actor->chainMask && heroChainPass != EHeroChainPass::FINAL) continue; auto nodeActor = node.actor; @@ -1006,7 +1019,7 @@ bool AINodeStorage::hasBetterChain( bool AINodeStorage::isTileAccessible(const HeroPtr & hero, const int3 & pos, const EPathfindingLayer layer) const { - auto chains = nodes[pos.x][pos.y][pos.z][layer]; + auto chains = nodes.get(pos, layer); for(const AIPathNode & node : chains) { @@ -1026,7 +1039,7 @@ std::vector AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) paths.reserve(NUM_CHAINS / 4); - auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL]; + auto chains = nodes.get(pos, isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL); for(const AIPathNode & node : chains) { @@ -1074,10 +1087,13 @@ void AINodeStorage::fillChainInfo(const AIPathNode * node, AIPath & path, int pa pathNode.danger = node->danger; pathNode.coord = node->coord; pathNode.parentIndex = parentIndex; + pathNode.actionIsBlocked = false; if(pathNode.specialAction) { - pathNode.actionIsBlocked = !pathNode.specialAction->canAct(node); + auto targetNode =node->theNodeBefore ? getAINode(node->theNodeBefore) : node; + + pathNode.actionIsBlocked = !pathNode.specialAction->canAct(targetNode); } parentIndex = path.nodes.size(); diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.h b/AI/Nullkiller/Pathfinding/AINodeStorage.h index dd5ff2fde..2db675faf 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.h +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.h @@ -12,6 +12,7 @@ #define PATHFINDER_TRACE_LEVEL 0 #define AI_TRACE_LEVEL 0 +#define SCOUT_TURN_DISTANCE_LIMIT 3 #include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" @@ -101,6 +102,28 @@ enum EHeroChainPass FINAL // same as SINGLE but for heroes from CHAIN pass }; +class AISharedStorage +{ + /// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations) + static std::shared_ptr> shared; + std::shared_ptr> nodes; +public: + AISharedStorage(int3 mapSize); + ~AISharedStorage(); + + /*STRONG_INLINE + boost::detail::multi_array::sub_array get(int3 tile, EPathfindingLayer layer) + { + return (*nodes)[tile.x][tile.y][tile.z][layer]; + }*/ + + STRONG_INLINE + boost::detail::multi_array::sub_array get(int3 tile, EPathfindingLayer layer) const + { + return (*nodes)[tile.x][tile.y][tile.z][layer]; + } +}; + class AINodeStorage : public INodeStorage { private: @@ -109,6 +132,7 @@ private: const CPlayerSpecificInfoCallback * cb; const Nullkiller * ai; std::unique_ptr dangerEvaluator; + AISharedStorage nodes; std::vector> actors; std::vector heroChain; EHeroChainPass heroChainPass; // true if we need to calculate hero chain diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp index 45183d856..4661c7b2b 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.cpp @@ -93,6 +93,55 @@ namespace AIPathfinding return false; } + bool AIMovementAfterDestinationRule::bypassQuest( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const + { + const AIPathNode * destinationNode = nodeStorage->getAINode(destination.node); + auto questObj = dynamic_cast(destination.nodeObject); + auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); + auto nodeHero = pathfinderHelper->hero; + QuestAction questAction(questInfo); + + if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE) + { + return false; + } + + if(!questAction.canAct(destinationNode)) + { + if(!destinationNode->actor->allowUseResources) + { + boost::optional questNode = nodeStorage->getOrCreateNode( + destination.coord, + destination.node->layer, + destinationNode->actor->resourceActor); + + if(!questNode || questNode.get()->cost < destination.cost) + { + return false; + } + + destinationNode = questNode.get(); + destination.node = questNode.get(); + + nodeStorage->commit(destination, source); + AIPreviousNodeRule(nodeStorage).process(source, destination, pathfinderConfig, pathfinderHelper); + } + + nodeStorage->updateAINode(destination.node, [&](AIPathNode * node) + { + auto questInfo = QuestInfo(questObj->quest, destination.nodeObject, destination.coord); + + node->specialAction.reset(new QuestAction(questAction)); + }); + } + + return true; + } + bool AIMovementAfterDestinationRule::bypassRemovableObject( const PathNodeInfo & source, CDestinationNodeInfo & destination, @@ -103,26 +152,7 @@ namespace AIPathfinding || destination.nodeObject->ID == Obj::BORDERGUARD || destination.nodeObject->ID == Obj::BORDER_GATE) { - auto questObj = dynamic_cast(destination.nodeObject); - auto nodeHero = pathfinderHelper->hero; - - if(destination.nodeObject->ID == Obj::QUEST_GUARD && questObj->quest->missionType == CQuest::MISSION_NONE) - { - return false; - } - - 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 true; + return bypassQuest(source, destination, pathfinderConfig, pathfinderHelper); } auto enemyHero = destination.nodeHero && destination.heroRelations == PlayerRelations::ENEMIES; diff --git a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h index 08f5d8bf0..fd1193889 100644 --- a/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h +++ b/AI/Nullkiller/Pathfinding/Rules/AIMovementAfterDestinationRule.h @@ -58,5 +58,11 @@ namespace AIPathfinding CDestinationNodeInfo & destination, const PathfinderConfig * pathfinderConfig, CPathfinderHelper * pathfinderHelper) const; + + bool bypassQuest( + const PathNodeInfo & source, + CDestinationNodeInfo & destination, + const PathfinderConfig * pathfinderConfig, + CPathfinderHelper * pathfinderHelper) const; }; } diff --git a/AI/Nullkiller/VCAI.cpp b/AI/Nullkiller/VCAI.cpp index 9a54d19c3..be4ceb006 100644 --- a/AI/Nullkiller/VCAI.cpp +++ b/AI/Nullkiller/VCAI.cpp @@ -343,6 +343,9 @@ void VCAI::objectRemoved(const CGObjectInstance * obj) LOG_TRACE(logAi); NET_EVENT_HANDLER; + if(!nullkiller) // crash protection + return; + nullkiller->memory->removeFromMemory(obj); if(obj->ID == Obj::HERO && obj->tempOwner == playerID) @@ -441,6 +444,9 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop) auto relations = myCb->getPlayerRelations(playerID, (PlayerColor)sop->val); auto obj = myCb->getObj(sop->id, false); + if(!nullkiller) // crash protection + return; + if(obj) { if(relations == PlayerRelations::ENEMIES) @@ -1446,6 +1452,8 @@ void VCAI::finish() makingTurn->join(); makingTurn.reset(); } + + nullkiller.reset(); } void VCAI::requestActionASAP(std::function whatToDo)