From 8fee46de7c835798d6de929cde1810fa3042b9af Mon Sep 17 00:00:00 2001 From: Andrii Danylchenko Date: Mon, 7 Jan 2019 23:33:31 +0200 Subject: [PATCH] AI: town portal support --- AI/VCAI/FuzzyHelper.cpp | 14 +++ AI/VCAI/FuzzyHelper.h | 1 + AI/VCAI/Goals/AdventureSpellCast.cpp | 21 ++++- AI/VCAI/Goals/AdventureSpellCast.h | 5 ++ AI/VCAI/Goals/BuildBoat.cpp | 2 + AI/VCAI/Goals/GatherArmy.cpp | 26 ++++-- AI/VCAI/Pathfinding/AINodeStorage.cpp | 121 ++++++++++++++++++++++++-- AI/VCAI/Pathfinding/AINodeStorage.h | 8 +- AI/VCAI/VCAI.cpp | 4 +- AI/VCAI/VCAI.h | 1 + lib/CGameInfoCallback.cpp | 2 +- lib/CPathfinder.cpp | 6 +- 12 files changed, 185 insertions(+), 26 deletions(-) diff --git a/AI/VCAI/FuzzyHelper.cpp b/AI/VCAI/FuzzyHelper.cpp index e43f98d8b..7b5ed25d3 100644 --- a/AI/VCAI/FuzzyHelper.cpp +++ b/AI/VCAI/FuzzyHelper.cpp @@ -98,6 +98,19 @@ float FuzzyHelper::evaluate(Goals::BuildBoat & g) return g.parent->accept(this) - buildBoatPenalty; } +float FuzzyHelper::evaluate(Goals::AdventureSpellCast & g) +{ + if(!g.parent) + { + return 0; + } + + const CSpell * spell = g.getSpell(); + const float spellCastPenalty = (float)g.hero->getSpellCost(spell) / g.hero->mana; + + return g.parent->accept(this) - spellCastPenalty; +} + float FuzzyHelper::evaluate(Goals::CompleteQuest & g) { // TODO: How to evaluate quest complexity? @@ -120,6 +133,7 @@ float FuzzyHelper::evaluate(Goals::VisitObj & g) return visitObjEngine.evaluate(g); } + float FuzzyHelper::evaluate(Goals::VisitHero & g) { auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar diff --git a/AI/VCAI/FuzzyHelper.h b/AI/VCAI/FuzzyHelper.h index 4d75aeda9..11552fab5 100644 --- a/AI/VCAI/FuzzyHelper.h +++ b/AI/VCAI/FuzzyHelper.h @@ -33,6 +33,7 @@ public: float evaluate(Goals::GatherArmy & g); float evaluate(Goals::ClearWayTo & g); float evaluate(Goals::CompleteQuest & g); + float evaluate(Goals::AdventureSpellCast & g); float evaluate(Goals::Invalid & g); float evaluate(Goals::AbstractGoal & g); void setPriority(Goals::TSubgoal & g); diff --git a/AI/VCAI/Goals/AdventureSpellCast.cpp b/AI/VCAI/Goals/AdventureSpellCast.cpp index f30000182..5f3e71f8c 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.cpp +++ b/AI/VCAI/Goals/AdventureSpellCast.cpp @@ -31,7 +31,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() if(!hero.validAndSet()) throw cannotFulfillGoalException("Invalid hero!"); - auto spell = spellID.toSpell(); + auto spell = getSpell(); logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name); @@ -49,7 +49,26 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve() void AdventureSpellCast::accept(VCAI * ai) { + if(town && spellID == SpellID::TOWN_PORTAL) + { + ai->selectedObject = town->id; + } + + auto wait = cb->waitTillRealize; + + cb->waitTillRealize = true; cb->castSpell(hero.h, spellID, tile); + ai->ah->resetPaths(); + + if(town && spellID == SpellID::TOWN_PORTAL) + { + // visit town + ai->moveHeroToTile(town->visitablePos(), hero); + } + + cb->waitTillRealize = wait; + + throw goalFulfilledException(sptr(*this)); } std::string AdventureSpellCast::name() const diff --git a/AI/VCAI/Goals/AdventureSpellCast.h b/AI/VCAI/Goals/AdventureSpellCast.h index 0d283cebd..28ded9dd1 100644 --- a/AI/VCAI/Goals/AdventureSpellCast.h +++ b/AI/VCAI/Goals/AdventureSpellCast.h @@ -30,6 +30,11 @@ namespace Goals return TGoalVec(); } + const CSpell * getSpell() const + { + return spellID.toSpell(); + } + TSubgoal whatToDoToAchieve() override; void accept(VCAI * ai) override; std::string name() const override; diff --git a/AI/VCAI/Goals/BuildBoat.cpp b/AI/VCAI/Goals/BuildBoat.cpp index 45f3ff1fb..070adb7c4 100644 --- a/AI/VCAI/Goals/BuildBoat.cpp +++ b/AI/VCAI/Goals/BuildBoat.cpp @@ -71,6 +71,8 @@ void BuildBoat::accept(VCAI * ai) shipyard->bestLocation().toString()); cb->buildBoat(shipyard); + + throw goalFulfilledException(sptr(*this)); } std::string BuildBoat::name() const diff --git a/AI/VCAI/Goals/GatherArmy.cpp b/AI/VCAI/Goals/GatherArmy.cpp index 6764aea09..fabfe511d 100644 --- a/AI/VCAI/Goals/GatherArmy.cpp +++ b/AI/VCAI/Goals/GatherArmy.cpp @@ -57,15 +57,17 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() //TODO: include evaluation of monsters gather in calculation for(auto t : cb->getTownsInfo()) { - auto pos = t->visitablePos(); - if(ai->isAccessibleForHero(pos, hero)) + auto waysToVisit = ai->ah->howToVisitObj(hero, t); + + if(waysToVisit.size()) { //grab army from town if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t)) { if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) - ret.push_back(sptr(VisitTile(pos).sethero(hero))); + vstd::concatenate(ret, waysToVisit); } + //buy army in town if (!t->visitingHero || t->visitingHero == hero.get(true)) { @@ -118,15 +120,20 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() else return false; }); + for(auto h : otherHeroes) { // Go to the other hero if we are faster - if (!vstd::contains(ai->visitedHeroes[hero], h) - && ai->isAccessibleForHero(h->visitablePos(), hero, true)) //visit only once each turn //FIXME: this is only bug workaround - ret.push_back(sptr(VisitHero(h->id.getNum()).setisAbstract(true).sethero(hero))); - // Let the other hero come to us - if (!vstd::contains(ai->visitedHeroes[h], hero)) - ret.push_back(sptr(VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h))); + if(!vstd::contains(ai->visitedHeroes[hero], h)) + { + vstd::concatenate(ret, ai->ah->howToVisitObj(hero, h)); + } + + // Go to the other hero if we are faster + if(!vstd::contains(ai->visitedHeroes[h], hero)) + { + vstd::concatenate(ret, ai->ah->howToVisitObj(h, hero.get())); + } } std::vector objs; @@ -161,6 +168,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals() } } } + for(auto h : cb->getHeroesInfo()) { for(auto obj : objs) diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index a8d4cf191..b6645f017 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -9,7 +9,12 @@ */ #include "StdInc.h" #include "AINodeStorage.h" +#include "../Goals/Goals.h" +#include "../../../CCallback.h" +#include "../../../lib/mapping/CMap.h" +#include "../../../lib/mapObjects/MapObjects.h" +extern boost::thread_specific_ptr cb; AINodeStorage::AINodeStorage(const int3 & Sizes) : sizes(Sizes) @@ -133,28 +138,128 @@ std::vector AINodeStorage::calculateNeighbours( return neighbours; } +void AINodeStorage::setHero(HeroPtr heroPtr) +{ + hero = heroPtr.get(); +} + +class TownPortalAction : public ISpecialAction +{ +private: + const CGTownInstance * target; + const HeroPtr hero; + +public: + TownPortalAction(const CGTownInstance * target) + :target(target) + { + } + + virtual Goals::TSubgoal whatToDo(HeroPtr hero) const override + { + const CGTownInstance * targetTown = target; // const pointer is not allowed in settown + + return sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos())); + } +}; + std::vector AINodeStorage::calculateTeleportations( const PathNodeInfo & source, const PathfinderConfig * pathfinderConfig, const CPathfinderHelper * pathfinderHelper) { std::vector neighbours; - auto accessibleExits = pathfinderHelper->getTeleportExits(source); - auto srcNode = getAINode(source.node); - for(auto & neighbour : accessibleExits) + if(source.isNodeObjectVisitable()) { - auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); + auto accessibleExits = pathfinderHelper->getTeleportExits(source); + auto srcNode = getAINode(source.node); - if(!node) - continue; + for(auto & neighbour : accessibleExits) + { + auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask); - neighbours.push_back(node.get()); + if(!node) + continue; + + neighbours.push_back(node.get()); + } + } + + if(hero->getPosition(false) == source.coord) + { + calculateTownPortalTeleportations(source, neighbours); } return neighbours; } +void AINodeStorage::calculateTownPortalTeleportations( + const PathNodeInfo & source, + std::vector & neighbours) +{ + SpellID spellID = SpellID::TOWN_PORTAL; + const CSpell * townPortal = spellID.toSpell(); + auto srcNode = getAINode(source.node); + + if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal)) + { + auto towns = cb->getTownsInfo(false); + + vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool + { + return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES; + }); + + if(!towns.size()) + { + return; + } + + // TODO: Copy/Paste from TownPortalMechanics + auto skillLevel = hero->getSpellSchoolLevel(townPortal); + auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3); + + if(hero->movement < movementCost) + { + return; + } + + if(skillLevel < SecSkillLevel::ADVANCED) + { + const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int + { + return hero->visitablePos().dist2dSQ(t->visitablePos()); + }); + + towns = std::vector{ nearestTown }; + } + + for(const CGTownInstance * targetTown : towns) + { + if(targetTown->visitingHero) + continue; + + auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->chainMask | CAST_CHAIN); + + if(nodeOptional) + { +#ifdef VCMI_TRACE_PATHFINDER + logAi->trace("Adding town portal node at %s", targetTown->name); +#endif + + AIPathNode * node = nodeOptional.get(); + + node->theNodeBefore = source.node; + node->specialAction.reset(new TownPortalAction(targetTown)); + node->moveRemains = source.node->moveRemains; + + neighbours.push_back(node); + } + } + } +} + bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const { auto pos = destination.coord; @@ -174,12 +279,14 @@ bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNode if(node.turns < destinationNode->turns || (node.turns == destinationNode->turns && node.moveRemains >= destinationNode->moveRemains)) { +#ifdef VCMI_TRACE_PATHFINDER logAi->trace( "Block ineficient move %s:->%s, mask=%i, mp diff: %i", source.coord.toString(), destination.coord.toString(), destinationNode->chainMask, node.moveRemains - destinationNode->moveRemains); +#endif return true; } diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 030c3a54d..4de24f4a8 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -113,13 +113,13 @@ public: std::vector getChainInfo(int3 pos, bool isOnLand) const; bool isTileAccessible(int3 pos, const EPathfindingLayer layer) const; - void setHero(HeroPtr heroPtr) - { - hero = heroPtr.get(); - } + void setHero(HeroPtr heroPtr); const CGHeroInstance * getHero() const { return hero; } + +private: + void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector & neighbours); }; diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 92edf231a..2effd4e3b 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -741,9 +741,7 @@ void VCAI::showMapObjectSelectDialog(QueryID askID, const Component & icon, cons { NET_EVENT_HANDLER; status.addQuery(askID, "Map object select query"); - requestActionASAP([=](){ answerQuery(askID, 0); }); - - //TODO: Town portal destination selection goes here + requestActionASAP([=](){ answerQuery(askID, selectedObject.getNum()); }); } void VCAI::saveGame(BinarySerializer & h, const int version) diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 6fbf24355..9ea02ca95 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -112,6 +112,7 @@ public: std::shared_ptr myCb; std::unique_ptr makingTurn; + ObjectInstanceID selectedObject; AIhelper * ah; diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index ce763f1b5..cf4701477 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -710,7 +710,7 @@ std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo( { for(const auto & town : i.second.towns) { - if (i.first==player || (isVisible(town, player) && !onlyOur)) + if(i.first == player || (!onlyOur && isVisible(town, player))) { ret.push_back(town); } diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index d3a16e166..2bb303f57 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -56,6 +56,10 @@ std::vector NodeStorage::calculateTeleportations( const CPathfinderHelper * pathfinderHelper) { std::vector neighbours; + + if(!source.isNodeObjectVisitable()) + return neighbours; + auto accessibleExits = pathfinderHelper->getTeleportExits(source); for(auto & neighbour : accessibleExits) @@ -315,7 +319,7 @@ void CPathfinder::calculatePaths() /// For now we disable teleports usage for patrol movement /// VCAI not aware about patrol and may stuck while attempt to use teleport - if(!source.isNodeObjectVisitable() || patrolState == PATROL_RADIUS) + if(patrolState == PATROL_RADIUS) continue; auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get());