diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index ad062c54a..e9f53c5a6 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -11,6 +11,7 @@ #include "AIUtility.h" #include "VCAI.h" #include "FuzzyHelper.h" +#include "Goals/Goals.h" #include "../../lib/UnlockGuard.h" #include "../../lib/CConfigHandler.h" diff --git a/AI/VCAI/CMakeLists.txt b/AI/VCAI/CMakeLists.txt index a1d738977..58ab90cb0 100644 --- a/AI/VCAI/CMakeLists.txt +++ b/AI/VCAI/CMakeLists.txt @@ -21,7 +21,26 @@ set(VCAI_SRCS MapObjectsEvaluator.cpp FuzzyEngines.cpp FuzzyHelper.cpp - Goals.cpp + Goals/AbstractGoal.cpp + Goals/BuildBoat.cpp + Goals/Build.cpp + Goals/BuildThis.cpp + Goals/Explore.cpp + Goals/GatherArmy.cpp + Goals/GatherTroops.cpp + Goals/BuyArmy.cpp + Goals/Win.cpp + Goals/VisitTile.cpp + Goals/VisitObj.cpp + Goals/VisitHero.cpp + Goals/CollectRes.cpp + Goals/Trade.cpp + Goals/RecruitHero.cpp + Goals/Conquer.cpp + Goals/ClearWayTo.cpp + Goals/DigAtTile.cpp + Goals/GetArtOfType.cpp + Goals/FindObj.cpp main.cpp VCAI.cpp ) @@ -42,7 +61,29 @@ set(VCAI_HEADERS MapObjectsEvaluator.h FuzzyEngines.h FuzzyHelper.h - Goals.h + Goals/AbstractGoal.h + Goals/CGoal.h + Goals/Invalid.h + Goals/BuildBoat.h + Goals/Build.h + Goals/BuildThis.h + Goals/Explore.h + Goals/GatherArmy.h + Goals/GatherTroops.h + Goals/BuyArmy.h + Goals/Win.h + Goals/VisitTile.h + Goals/VisitObj.h + Goals/VisitHero.h + Goals/CollectRes.h + Goals/Trade.h + Goals/RecruitHero.h + Goals/Conquer.h + Goals/ClearWayTo.h + Goals/DigAtTile.h + Goals/GetArtOfType.h + Goals/FindObj.h + Goals/Goals.h VCAI.h ) diff --git a/AI/VCAI/FuzzyEngines.cpp b/AI/VCAI/FuzzyEngines.cpp index 9126b23dd..d1e8cc28f 100644 --- a/AI/VCAI/FuzzyEngines.cpp +++ b/AI/VCAI/FuzzyEngines.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "FuzzyEngines.h" +#include "Goals/Goals.h" #include "../../lib/mapObjects/MapObjects.h" #include "VCAI.h" diff --git a/AI/VCAI/FuzzyEngines.h b/AI/VCAI/FuzzyEngines.h index 2797a7e32..26de9c1ef 100644 --- a/AI/VCAI/FuzzyEngines.h +++ b/AI/VCAI/FuzzyEngines.h @@ -9,7 +9,7 @@ */ #pragma once #include "fl/Headers.h" -#include "Goals.h" +#include "Goals/AbstractGoal.h" class CArmedInstance; diff --git a/AI/VCAI/FuzzyHelper.cpp b/AI/VCAI/FuzzyHelper.cpp index 7c6ca962c..d1a81f2d6 100644 --- a/AI/VCAI/FuzzyHelper.cpp +++ b/AI/VCAI/FuzzyHelper.cpp @@ -11,7 +11,7 @@ #include "FuzzyHelper.h" #include "../../lib/mapObjects/CommonConstructors.h" -#include "Goals.h" +#include "Goals/Goals.h" #include "VCAI.h" FuzzyHelper * fh; diff --git a/AI/VCAI/Goals.cpp b/AI/VCAI/Goals.cpp deleted file mode 100644 index b8f996b52..000000000 --- a/AI/VCAI/Goals.cpp +++ /dev/null @@ -1,1663 +0,0 @@ -/* - * Goals.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "Goals.h" -#include "VCAI.h" -#include "FuzzyHelper.h" -#include "ResourceManager.h" -#include "BuildingManager.h" -#include "../../lib/mapping/CMap.h" //for victory conditions -#include "../../lib/CPathfinder.h" -#include "../../lib/StringConstants.h" - -#include "AIhelper.h" - -extern boost::thread_specific_ptr cb; -extern boost::thread_specific_ptr ai; -extern FuzzyHelper * fh; - -using namespace Goals; - -TSubgoal Goals::sptr(const AbstractGoal & tmp) -{ - TSubgoal ptr; - ptr.reset(tmp.clone()); - return ptr; -} - -std::string Goals::AbstractGoal::name() const //TODO: virtualize -{ - std::string desc; - switch(goalType) - { - case INVALID: - return "INVALID"; - case WIN: - return "WIN"; - case DO_NOT_LOSE: - return "DO NOT LOOSE"; - case CONQUER: - return "CONQUER"; - case BUILD: - return "BUILD"; - case EXPLORE: - desc = "EXPLORE"; - break; - case GATHER_ARMY: - desc = "GATHER ARMY"; - break; - case BUY_ARMY: - return "BUY ARMY"; - break; - case BOOST_HERO: - desc = "BOOST_HERO (unsupported)"; - break; - case RECRUIT_HERO: - return "RECRUIT HERO"; - case BUILD_STRUCTURE: - return "BUILD STRUCTURE"; - case COLLECT_RES: - desc = "COLLECT RESOURCE " + GameConstants::RESOURCE_NAMES[resID] + " (" + boost::lexical_cast(value) + ")"; - break; - case TRADE: - { - auto obj = cb->getObjInstance(ObjectInstanceID(objid)); - if (obj) - desc = (boost::format("TRADE %d of %s at %s") % value % GameConstants::RESOURCE_NAMES[resID] % obj->getObjectName()).str(); - } - break; - case GATHER_TROOPS: - desc = "GATHER TROOPS"; - break; - case VISIT_OBJ: - { - auto obj = cb->getObjInstance(ObjectInstanceID(objid)); - if(obj) - desc = "GET OBJ " + obj->getObjectName(); - } - break; - case FIND_OBJ: - desc = "FIND OBJ " + boost::lexical_cast(objid); - break; - case VISIT_HERO: - { - auto obj = cb->getObjInstance(ObjectInstanceID(objid)); - if(obj) - desc = "VISIT HERO " + obj->getObjectName(); - } - break; - case GET_ART_TYPE: - desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name(); - break; - case ISSUE_COMMAND: - return "ISSUE COMMAND (unsupported)"; - case VISIT_TILE: - desc = "VISIT TILE " + tile.toString(); - break; - case CLEAR_WAY_TO: - desc = "CLEAR WAY TO " + tile.toString(); - break; - case DIG_AT_TILE: - desc = "DIG AT TILE " + tile.toString(); - break; - default: - return boost::lexical_cast(goalType); - } - if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal - desc += " (" + hero->name + ")"; - return desc; -} - -bool Goals::BuildBoat::operator==(const BuildBoat & other) const -{ - return shipyard->o->id == other.shipyard->o->id; -} - -bool Goals::Explore::operator==(const Explore & other) const -{ - return other.hero.h == hero.h; -} - -bool Goals::Conquer::operator==(const Conquer & other) const -{ - return other.hero.h == hero.h; -} - -bool Goals::GatherArmy::operator==(const GatherArmy & other) const -{ - return other.hero.h == hero.h || town == other.town; -} - -bool Goals::BuyArmy::operator==(const BuyArmy & other) const -{ - return town == other.town && objid == other.objid; -} - -bool Goals::BoostHero::operator==(const BoostHero & other) const -{ - return other.hero.h == hero.h; -} - -bool Goals::BuildThis::operator==(const BuildThis & other) const -{ - return town == other.town && bid == other.bid; -} - -bool Goals::CollectRes::operator==(const CollectRes & other) const -{ - return resID == other.resID; -} - -bool Goals::Trade::operator==(const Trade & other) const -{ - return resID == other.resID; -} - -bool Goals::GatherTroops::operator==(const GatherTroops & other) const -{ - return objid == other.objid; -} - -bool Goals::VisitObj::operator==(const VisitObj & other) const -{ - return other.hero.h == hero.h && other.objid == objid; -} - -bool Goals::FindObj::operator==(const FindObj & other) const -{ - return other.hero.h == hero.h && other.objid == objid; -} - -bool Goals::VisitHero::operator==(const VisitHero & other) const -{ - return other.hero.h == hero.h && other.objid == objid; -} - -bool Goals::GetArtOfType::operator==(const GetArtOfType & other) const -{ - return other.hero.h == hero.h && other.objid == objid; -} - -bool Goals::VisitTile::operator==(const VisitTile & other) const -{ - return other.hero.h == hero.h && other.tile == tile; -} - -bool Goals::ClearWayTo::operator==(const ClearWayTo & other) const -{ - return other.hero.h == hero.h && other.tile == tile; -} - -bool Goals::DigAtTile::operator==(const DigAtTile & other) const -{ - return other.hero.h == hero.h && other.tile == tile; -} - -bool Goals::AbstractGoal::operator==(const AbstractGoal & g) const -{ - return false; -} - -bool Goals::AbstractGoal::operator<(AbstractGoal & g) //for std::unique -{ - //TODO: make sure it gets goals consistent with == operator - if (goalType < g.goalType) - return true; - if (goalType > g.goalType) - return false; - if (hero < g.hero) - return true; - if (hero > g.hero) - return false; - if (tile < g.tile) - return true; - if (g.tile < tile) - return false; - if (objid < g.objid) - return true; - if (objid > g.objid) - return false; - if (town < g.town) - return true; - if (town > g.town) - return false; - if (value < g.value) - return true; - if (value > g.value) - return false; - if (priority < g.priority) - return true; - if (priority > g.priority) - return false; - if (resID < g.resID) - return true; - if (resID > g.resID) - return false; - if (bid < g.bid) - return true; - if (bid > g.bid) - return false; - if (aid < g.aid) - return true; - if (aid > g.aid) - return false; - return false; -} - -//TODO: find out why the following are not generated automatically on MVS? - -namespace Goals -{ - template<> - void CGoal::accept(VCAI * ai) - { - ai->tryRealize(static_cast(*this)); - } - - template<> - void CGoal::accept(VCAI * ai) - { - ai->tryRealize(static_cast(*this)); - } - template<> - float CGoal::accept(FuzzyHelper * f) - { - return f->evaluate(static_cast(*this)); - } - - template<> - float CGoal::accept(FuzzyHelper * f) - { - return f->evaluate(static_cast(*this)); - } - bool TSubgoal::operator==(const TSubgoal & rhs) const - { - return *get() == *rhs.get(); //comparison for Goals is overloaded, so they don't need to be identical to match - } - - bool TSubgoal::operator<(const TSubgoal & rhs) const - { - return get() < rhs.get(); //compae by value - } - - bool BuyArmy::fulfillsMe(TSubgoal goal) - { - //if (hero && hero != goal->hero) - // return false; - return town == goal->town && goal->value >= value; //can always buy more army - } - TSubgoal BuyArmy::whatToDoToAchieve() - { - //TODO: calculate the actual cost of units instead - TResources price; - price[Res::GOLD] = value * 0.4f; //some approximate value - return ai->ah->whatToDo(price, iAmElementar()); //buy right now or gather resources - } - std::string BuyArmy::completeMessage() const - { - return boost::format("Bought army of value %d in town of %s") % value, town->name; - } -} - -TSubgoal Trade::whatToDoToAchieve() -{ - return iAmElementar(); -} - -//TSubgoal AbstractGoal::whatToDoToAchieve() -//{ -// logAi->debug("Decomposing goal of type %s",name()); -// return sptr (Goals::Explore()); -//} - -TSubgoal Win::whatToDoToAchieve() -{ - auto toBool = [=](const EventCondition &) - { - // TODO: proper implementation - // Right now even already fulfilled goals will be included into generated list - // Proper check should test if event condition is already fulfilled - // Easiest way to do this is to call CGameState::checkForVictory but this function should not be - // used on client side or in AI code - return false; - }; - - std::vector goals; - - for(const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents) - { - //TODO: try to eliminate human player(s) using loss conditions that have isHuman element - - if(event.effect.type == EventEffect::VICTORY) - { - boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals)); - } - } - - //TODO: instead of returning first encountered goal AI should generate list of possible subgoals - for(const EventCondition & goal : goals) - { - switch(goal.condition) - { - case EventCondition::HAVE_ARTIFACT: - return sptr(Goals::GetArtOfType(goal.objectType)); - case EventCondition::DESTROY: - { - if(goal.object) - { - auto obj = cb->getObj(goal.object->id); - if(obj) - if(obj->getOwner() == ai->playerID) //we can't capture our own object - return sptr(Goals::Conquer()); - - - return sptr(Goals::VisitObj(goal.object->id.getNum())); - } - else - { - // TODO: destroy all objects of type goal.objectType - // This situation represents "kill all creatures" condition from H3 - break; - } - } - case EventCondition::HAVE_BUILDING: - { - // TODO build other buildings apart from Grail - // goal.objectType = buidingID to build - // goal.object = optional, town in which building should be built - // Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions) - - if(goal.objectType == BuildingID::GRAIL) - { - if(auto h = ai->getHeroWithGrail()) - { - //hero is in a town that can host Grail - if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL)) - { - const CGTownInstance * t = h->visitedTown; - return sptr(Goals::BuildThis(BuildingID::GRAIL, t).setpriority(10)); - } - else - { - auto towns = cb->getTownsInfo(); - towns.erase(boost::remove_if(towns, - [](const CGTownInstance * t) -> bool - { - return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); - }), - towns.end()); - boost::sort(towns, CDistanceSorter(h.get())); - if(towns.size()) - { - return sptr(Goals::VisitTile(towns.front()->visitablePos()).sethero(h)); - } - } - } - double ratio = 0; - // maybe make this check a bit more complex? For example: - // 0.75 -> dig randomly within 3 tiles radius - // 0.85 -> radius now 2 tiles - // 0.95 -> 1 tile radius, position is fully known - // AFAIK H3 AI does something like this - int3 grailPos = cb->getGrailPos(&ratio); - if(ratio > 0.99) - { - return sptr(Goals::DigAtTile(grailPos)); - } //TODO: use FIND_OBJ - else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID)) //there are unvisited Obelisks - return sptr(Goals::VisitObj(obj->id.getNum())); - else - return sptr(Goals::Explore()); - } - break; - } - case EventCondition::CONTROL: - { - if(goal.object) - { - auto objRelations = cb->getPlayerRelations(ai->playerID, goal.object->tempOwner); - - if(objRelations == PlayerRelations::ENEMIES) - { - return sptr(Goals::VisitObj(goal.object->id.getNum())); - } - else - { - // TODO: Defance - break; - } - } - else - { - //TODO: control all objects of type "goal.objectType" - // Represents H3 condition "Flag all mines" - break; - } - } - - case EventCondition::HAVE_RESOURCES: - //TODO mines? piles? marketplace? - //save? - return sptr(Goals::CollectRes(static_cast(goal.objectType), goal.value)); - case EventCondition::HAVE_CREATURES: - return sptr(Goals::GatherTroops(goal.objectType, goal.value)); - case EventCondition::TRANSPORT: - { - //TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it - // Represents "transport artifact" condition: - // goal.objectType = type of artifact - // goal.object = destination-town where artifact should be transported - break; - } - case EventCondition::STANDARD_WIN: - return sptr(Goals::Conquer()); - - // Conditions that likely don't need any implementation - case EventCondition::DAYS_PASSED: - break; // goal.value = number of days for condition to trigger - case EventCondition::DAYS_WITHOUT_TOWN: - break; // goal.value = number of days to trigger this - case EventCondition::IS_HUMAN: - break; // Should be only used in calculation of candidates (see toBool lambda) - case EventCondition::CONST_VALUE: - break; - - case EventCondition::HAVE_0: - case EventCondition::HAVE_BUILDING_0: - case EventCondition::DESTROY_0: - //TODO: support new condition format - return sptr(Goals::Conquer()); - default: - assert(0); - } - } - return sptr(Goals::Invalid()); -} - -TSubgoal BuildBoat::whatToDoToAchieve() -{ - if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES) - { - return fh->chooseSolution(ai->ah->howToVisitObj(shipyard->o)); - } - - if(shipyard->shipyardStatus() != IShipyard::GOOD) - { - throw cannotFulfillGoalException("Shipyard is busy."); - } - - TResources boatCost; - shipyard->getBoatCost(boatCost); - - return ai->ah->whatToDo(boatCost, this->iAmElementar()); -} - -void BuildBoat::accept(VCAI * ai) -{ - TResources boatCost; - shipyard->getBoatCost(boatCost); - - if(!cb->getResourceAmount().canAfford(boatCost)) - { - throw cannotFulfillGoalException("Can not afford boat"); - } - - if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES) - { - throw cannotFulfillGoalException("Can not build boat in enemy shipyard"); - } - - if(shipyard->shipyardStatus() != IShipyard::GOOD) - { - throw cannotFulfillGoalException("Shipyard is busy."); - } - - cb->buildBoat(shipyard); -} - -std::string BuildBoat::name() const -{ - return "BuildBoat"; -} - -std::string BuildBoat::completeMessage() const -{ - return "Boat have been built at " + shipyard->o->visitablePos().toString(); -} - -TSubgoal FindObj::whatToDoToAchieve() -{ - const CGObjectInstance * o = nullptr; - if(resID > -1) //specified - { - for(const CGObjectInstance * obj : ai->visitableObjs) - { - if(obj->ID == objid && obj->subID == resID) - { - o = obj; - break; //TODO: consider multiple objects and choose best - } - } - } - else - { - for(const CGObjectInstance * obj : ai->visitableObjs) - { - if(obj->ID == objid) - { - o = obj; - break; //TODO: consider multiple objects and choose best - } - } - } - if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is - return sptr(Goals::VisitObj(o->id.getNum())); - else - return sptr(Goals::Explore()); -} - -bool Goals::FindObj::fulfillsMe(TSubgoal goal) -{ - if (goal->goalType == Goals::VISIT_TILE) //visiting tile visits object at same time - { - if (!hero || hero == goal->hero) - for (auto obj : cb->getVisitableObjs(goal->tile)) //check if any object on that tile matches criteria - if (obj->visitablePos() == goal->tile) //object could be removed - if (obj->ID == objid && obj->subID == resID) //same type and subtype - return true; - } - return false; -} - -std::string VisitObj::completeMessage() const -{ - return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast(objid); -} - -TGoalVec VisitObj::getAllPossibleSubgoals() -{ - TGoalVec goalList; - const CGObjectInstance * obj = cb->getObjInstance(ObjectInstanceID(objid)); - if(!obj) - { - throw cannotFulfillGoalException("Object is missing - goal is invalid now!"); - } - - int3 pos = obj->visitablePos(); - if(hero) - { - if(ai->isAccessibleForHero(pos, hero)) - { - if(isSafeToVisit(hero, pos)) - goalList.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero))); - else - goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, hero.h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); - - return goalList; - } - } - else - { - for(auto potentialVisitor : cb->getHeroesInfo()) - { - if(ai->isAccessibleForHero(pos, potentialVisitor)) - { - if(isSafeToVisit(potentialVisitor, pos)) - goalList.push_back(sptr(Goals::VisitObj(obj->id.getNum()).sethero(potentialVisitor))); - else - goalList.push_back(sptr(Goals::GatherArmy(evaluateDanger(pos, potentialVisitor) * SAFE_ATTACK_CONSTANT).sethero(potentialVisitor).setisAbstract(true))); - } - } - if(!goalList.empty()) - { - return goalList; - } - } - - goalList.push_back(sptr(Goals::ClearWayTo(pos))); - return goalList; -} - -TSubgoal VisitObj::whatToDoToAchieve() -{ - auto bestGoal = fh->chooseSolution(getAllPossibleSubgoals()); - - if(bestGoal->goalType == Goals::VISIT_OBJ && bestGoal->hero) - bestGoal->setisElementar(true); - - return bestGoal; -} - -Goals::VisitObj::VisitObj(int Objid) : CGoal(Goals::VISIT_OBJ) -{ - objid = Objid; - tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); - priority = 3; -} - -bool VisitObj::fulfillsMe(TSubgoal goal) -{ - if(goal->goalType == Goals::VISIT_TILE) - { - if (!hero || hero == goal->hero) - { - auto obj = cb->getObjInstance(ObjectInstanceID(objid)); - if (obj && obj->visitablePos() == goal->tile) //object could be removed - return true; - } - } - return false; -} - -std::string VisitHero::completeMessage() const -{ - return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast(objid); -} - -TSubgoal VisitHero::whatToDoToAchieve() -{ - const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); - if(!obj) - return sptr(Goals::Explore()); - int3 pos = obj->visitablePos(); - - if(hero && ai->isAccessibleForHero(pos, hero, true) && isSafeToVisit(hero, pos)) //enemy heroes can get reinforcements - { - if(hero->pos == pos) - logAi->error("Hero %s tries to visit himself.", hero.name); - else - { - //can't use VISIT_TILE here as tile appears blocked by target hero - //FIXME: elementar goal should not be abstract - return sptr(Goals::VisitHero(objid).sethero(hero).settile(pos).setisElementar(true)); - } - } - return sptr(Goals::Invalid()); -} - -bool VisitHero::fulfillsMe(TSubgoal goal) -{ - //TODO: VisitObj shoudl not be used for heroes, but... - if(goal->goalType == Goals::VISIT_TILE) - { - auto obj = cb->getObj(ObjectInstanceID(objid)); - if (!obj) - { - logAi->error("Hero %s: VisitHero::fulfillsMe at %s: object %d not found", hero.name, goal->tile.toString(), objid); - return false; - } - return obj->visitablePos() == goal->tile; - } - return false; -} - -TSubgoal GetArtOfType::whatToDoToAchieve() -{ - TSubgoal alternativeWay = CGoal::lookForArtSmart(aid); //TODO: use - if(alternativeWay->invalid()) - return sptr(Goals::FindObj(Obj::ARTIFACT, aid)); - return sptr(Goals::Invalid()); -} - -TSubgoal ClearWayTo::whatToDoToAchieve() -{ - assert(cb->isInTheMap(tile)); //set tile - if(!cb->isVisible(tile)) - { - logAi->error("Clear way should be used with visible tiles!"); - return sptr(Goals::Explore()); - } - - return (fh->chooseSolution(getAllPossibleSubgoals())); -} - -bool Goals::ClearWayTo::fulfillsMe(TSubgoal goal) -{ - if (goal->goalType == Goals::VISIT_TILE) - { - if (!hero || hero == goal->hero) - return tile == goal->tile; - } - return false; -} - -TGoalVec ClearWayTo::getAllPossibleSubgoals() -{ - TGoalVec ret; - - std::vector heroes; - if(hero) - heroes.push_back(hero.h); - else - heroes = cb->getHeroesInfo(); - - for(auto h : heroes) - { - //TODO: handle clearing way to allied heroes that are blocked - //if ((hero && hero->visitablePos() == tile && hero == *h) || //we can't free the way ourselves - // h->visitablePos() == tile) //we are already on that tile! what does it mean? - // continue; - - //if our hero is trapped, make sure we request clearing the way from OUR perspective - - vstd::concatenate(ret, ai->ah->howToVisitTile(h, tile)); - } - - if(ret.empty() && ai->canRecruitAnyHero()) - ret.push_back(sptr(Goals::RecruitHero())); - - if(ret.empty()) - { - logAi->warn("There is no known way to clear the way to tile %s", tile.toString()); - throw goalFulfilledException(sptr(Goals::ClearWayTo(tile))); //make sure asigned hero gets unlocked - } - - return ret; -} - -std::string Explore::completeMessage() const -{ - return "Hero " + hero.get()->name + " completed exploration"; -} - -TSubgoal Explore::whatToDoToAchieve() -{ - auto ret = fh->chooseSolution(getAllPossibleSubgoals()); - if(hero) //use best step for this hero - { - return ret; - } - else - { - if(ret->hero.get(true)) - return sptr(sethero(ret->hero.h).setisAbstract(true)); //choose this hero and then continue with him - else - return ret; //other solutions, like buying hero from tavern - } -} - -TGoalVec Explore::getAllPossibleSubgoals() -{ - TGoalVec ret; - std::vector heroes; - - if(hero) - { - heroes.push_back(hero.h); - } - else - { - //heroes = ai->getUnblockedHeroes(); - heroes = cb->getHeroesInfo(); - vstd::erase_if(heroes, [](const HeroPtr h) - { - if(ai->getGoal(h)->goalType == Goals::EXPLORE) //do not reassign hero who is already explorer - return true; - - if(!ai->isAbleToExplore(h)) - return true; - - return !h->movement; //saves time, immobile heroes are useless anyway - }); - } - - //try to use buildings that uncover map - std::vector objs; - for(auto obj : ai->visitableObjs) - { - if(!vstd::contains(ai->alreadyVisited, obj)) - { - switch(obj->ID.num) - { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - case Obj::CARTOGRAPHER: - objs.push_back(obj); - break; - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); - if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) - objs.push_back(obj); - break; - } - } - else - { - switch(obj->ID.num) - { - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) - break; - for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) - { - if(!cb->getObj(exit)) - { // Always attempt to visit two-way teleports if one of channel exits is not visible - objs.push_back(obj); - break; - } - } - break; - } - } - } - - auto primaryHero = ai->primaryHero().h; - for(auto h : heroes) - { - for(auto obj : objs) //double loop, performance risk? - { - auto waysToVisitObj = ai->ah->howToVisitObj(h, obj); - - vstd::concatenate(ret, waysToVisitObj); - } - - int3 t = whereToExplore(h); - if(t.valid()) - { - ret.push_back(sptr(Goals::VisitTile(t).sethero(h))); - } - else - { - //FIXME: possible issues when gathering army to break - if(hero.h == h || //exporation is assigned to this hero - (!hero && h == primaryHero)) //not assigned to specific hero, let our main hero do the job - { - t = ai->explorationDesperate(h); //check this only ONCE, high cost - if (t.valid()) //don't waste time if we are completely blocked - { - auto waysToVisitTile = ai->ah->howToVisitTile(h, t); - - vstd::concatenate(ret, waysToVisitTile); - continue; - } - } - ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore - } - } - //we either don't have hero yet or none of heroes can explore - if((!hero || ret.empty()) && ai->canRecruitAnyHero()) - ret.push_back(sptr(Goals::RecruitHero())); - - if(ret.empty()) - { - throw goalFulfilledException(sptr(Goals::Explore().sethero(hero))); - } - //throw cannotFulfillGoalException("Cannot explore - no possible ways found!"); - - return ret; -} - -bool Explore::fulfillsMe(TSubgoal goal) -{ - if(goal->goalType == Goals::EXPLORE) - { - if(goal->hero) - return hero == goal->hero; - else - return true; //cancel ALL exploration - } - return false; -} - -TSubgoal RecruitHero::whatToDoToAchieve() -{ - const CGTownInstance * t = ai->findTownWithTavern(); - if(!t) - return sptr(Goals::BuildThis(BuildingID::TAVERN).setpriority(2)); - - TResources res; - res[Res::GOLD] = GameConstants::HERO_GOLD_COST; - return ai->ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res -} - -std::string VisitTile::completeMessage() const -{ - return "Hero " + hero.get()->name + " visited tile " + tile.toString(); -} - -TSubgoal VisitTile::whatToDoToAchieve() -{ - auto ret = fh->chooseSolution(getAllPossibleSubgoals()); - - if(ret->hero) - { - if(isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero)) - { - ret->setisElementar(true); - return ret; - } - else - { - return sptr(Goals::GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT) - .sethero(ret->hero).setisAbstract(true)); - } - } - return ret; -} - -TGoalVec VisitTile::getAllPossibleSubgoals() -{ - assert(cb->isInTheMap(tile)); - - TGoalVec ret; - if(!cb->isVisible(tile)) - ret.push_back(sptr(Goals::Explore())); //what sense does it make? - else - { - std::vector heroes; - if(hero) - heroes.push_back(hero.h); //use assigned hero if any - else - heroes = cb->getHeroesInfo(); //use most convenient hero - - for(auto h : heroes) - { - if(ai->isAccessibleForHero(tile, h)) - ret.push_back(sptr(Goals::VisitTile(tile).sethero(h))); - } - if(ai->canRecruitAnyHero()) - ret.push_back(sptr(Goals::RecruitHero())); - } - if(ret.empty()) - { - auto obj = vstd::frontOrNull(cb->getVisitableObjs(tile)); - if(obj && obj->ID == Obj::HERO && obj->tempOwner == ai->playerID) //our own hero stands on that tile - { - if(hero.get(true) && hero->id == obj->id) //if it's assigned hero, visit tile. If it's different hero, we can't visit tile now - ret.push_back(sptr(Goals::VisitTile(tile).sethero(dynamic_cast(obj)).setisElementar(true))); - else - throw cannotFulfillGoalException("Tile is already occupied by another hero "); //FIXME: we should give up this tile earlier - } - else - ret.push_back(sptr(Goals::ClearWayTo(tile))); - } - - //important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile) - return ret; -} - -TSubgoal DigAtTile::whatToDoToAchieve() -{ - const CGObjectInstance * firstObj = vstd::frontOrNull(cb->getVisitableObjs(tile)); - if(firstObj && firstObj->ID == Obj::HERO && firstObj->tempOwner == ai->playerID) //we have hero at dest - { - const CGHeroInstance * h = dynamic_cast(firstObj); - sethero(h).setisElementar(true); - return sptr(*this); - } - - return sptr(Goals::VisitTile(tile)); -} - -TSubgoal BuildThis::whatToDoToAchieve() -{ - auto b = BuildingID(bid); - - // find town if not set - if (!town && hero) - town = hero->visitedTown; - - if (!town) - { - for (const CGTownInstance * t : cb->getTownsInfo()) - { - switch (cb->canBuildStructure(town, b)) - { - case EBuildingState::ALLOWED: - town = t; - break; //TODO: look for prerequisites? this is not our reponsibility - default: - continue; - } - } - } - if (town) //we have specific town to build this - { - switch (cb->canBuildStructure(town, b)) - { - case EBuildingState::ALLOWED: - case EBuildingState::NO_RESOURCES: - { - auto res = town->town->buildings.at(BuildingID(bid))->resources; - return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources - } - break; - default: - throw cannotFulfillGoalException("Not possible to build"); - } - } - else - throw cannotFulfillGoalException("Cannot find town to build this"); -} - -TGoalVec Goals::CollectRes::getAllPossibleSubgoals() -{ - TGoalVec ret; - - auto givesResource = [this](const CGObjectInstance * obj) -> bool - { - //TODO: move this logic to object side - //TODO: remember mithril exists - //TODO: water objects - //TODO: Creature banks - - //return false first from once-visitable, before checking if they were even visited - switch (obj->ID.num) - { - case Obj::TREASURE_CHEST: - return resID == Res::GOLD; - break; - case Obj::RESOURCE: - return obj->subID == resID; - break; - case Obj::MINE: - return (obj->subID == resID && - (cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines - break; - case Obj::CAMPFIRE: - return true; //contains all resources - break; - case Obj::WINDMILL: - switch (resID) - { - case Res::GOLD: - case Res::WOOD: - return false; - } - break; - case Obj::WATER_WHEEL: - if (resID != Res::GOLD) - return false; - break; - case Obj::MYSTICAL_GARDEN: - if ((resID != Res::GOLD) && (resID != Res::GEMS)) - return false; - break; - case Obj::LEAN_TO: - case Obj::WAGON: - if (resID != Res::GOLD) - return false; - break; - default: - return false; - break; - } - return !vstd::contains(ai->alreadyVisited, obj); //for weekly / once visitable - }; - - std::vector objs; - for (auto obj : ai->visitableObjs) - { - if (givesResource(obj)) - objs.push_back(obj); - } - for (auto h : cb->getHeroesInfo()) - { - std::vector ourObjs(objs); //copy common objects - - for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero - { - if (givesResource(obj)) - ourObjs.push_back(obj); - } - - for (auto obj : ourObjs) - { - auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj)); - - vstd::concatenate(ret, waysToGo); - } - } - return ret; -} - -TSubgoal CollectRes::whatToDoToAchieve() -{ - auto goals = getAllPossibleSubgoals(); - auto trade = whatToDoToTrade(); - if (!trade->invalid()) - goals.push_back(trade); - - if (goals.empty()) - return sptr(Goals::Explore()); //we can always do that - else - return fh->chooseSolution(goals); //TODO: evaluate trading -} - -TSubgoal Goals::CollectRes::whatToDoToTrade() -{ - std::vector markets; - - std::vector visObjs; - ai->retrieveVisitableObjs(visObjs, true); - for (const CGObjectInstance * obj : visObjs) - { - if (const IMarket * m = IMarket::castFrom(obj, false)) - { - if (obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE)) - markets.push_back(m); - else if (obj->ID == Obj::TRADING_POST) - markets.push_back(m); - } - } - - boost::sort(markets, [](const IMarket * m1, const IMarket * m2) -> bool - { - return m1->getMarketEfficiency() < m2->getMarketEfficiency(); - }); - - markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool - { - if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID)) - { - if (!ai->isAccessible(market->o->visitablePos())) - return true; - } - return false; - }), markets.end()); - - if (!markets.size()) - { - for (const CGTownInstance * t : cb->getTownsInfo()) - { - if (cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED) - return sptr(Goals::BuildThis(BuildingID::MARKETPLACE, t).setpriority(2)); - } - } - else - { - const IMarket * m = markets.back(); - //attempt trade at back (best prices) - int howManyCanWeBuy = 0; - for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1)) - { - if (i == resID) - continue; - int toGive = -1, toReceive = -1; - m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE); - assert(toGive > 0 && toReceive > 0); - howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive); - } - - if (howManyCanWeBuy >= value) - { - auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace - assert(backObj); - auto objid = m->o->id.getNum(); - if (backObj->tempOwner != ai->playerID) //top object not owned - { - return sptr(Goals::VisitObj(objid)); //just go there - } - else //either it's our town, or we have hero there - { - Goals::Trade trade(resID, value, objid); - return sptr(trade.setisElementar(true)); //we can do this immediately - } - } - } - return sptr(Goals::Invalid()); //cannot trade -} - -bool CollectRes::fulfillsMe(TSubgoal goal) -{ - if (goal->resID == resID) - if (goal->value >= value) - return true; - - return false; -} - -int GatherTroops::getCreaturesCount(const CArmedInstance * army) -{ - int count = 0; - - for(auto stack : army->Slots()) - { - if(objid == stack.second->getCreatureID().num) - { - count += stack.second->count; - } - } - - return count; -} - -TSubgoal GatherTroops::whatToDoToAchieve() -{ - auto heroes = cb->getHeroesInfo(true); - - for(auto hero : heroes) - { - if(getCreaturesCount(hero) >= this->value) - { - logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name); - - throw goalFulfilledException(sptr(*this)); - } - } - - TGoalVec solutions = getAllPossibleSubgoals(); - - if(solutions.empty()) - return sptr(Goals::Explore()); - - return fh->chooseSolution(solutions); -} - - -TGoalVec GatherTroops::getAllPossibleSubgoals() -{ - TGoalVec solutions; - - for(const CGTownInstance * t : cb->getTownsInfo()) - { - int count = getCreaturesCount(t->getUpperArmy()); - - if(count >= this->value) - { - vstd::concatenate(solutions, ai->ah->howToVisitObj(t)); - continue; - } - - auto creature = VLC->creh->creatures[objid]; - if(t->subID == creature->faction) //TODO: how to force AI to build unupgraded creatures? :O - { - auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1); - if(!creatures) - continue; - - int upgradeNumber = vstd::find_pos(*creatures, creature->idNumber); - if(upgradeNumber < 0) - continue; - - BuildingID bid(BuildingID::DWELL_FIRST + creature->level - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN); - if(t->hasBuilt(bid)) //this assumes only creatures with dwellings are assigned to faction - { - solutions.push_back(sptr(Goals::BuyArmy(t, creature->AIValue * this->value).setobjid(objid))); - } - /*else //disable random building requests for now - this code needs to know a lot of town/resource context to do more good than harm - { - return sptr(Goals::BuildThis(bid, t).setpriority(priority)); - }*/ - } - } - for(auto obj : ai->visitableObjs) - { - auto d = dynamic_cast(obj); - - if(!d || obj->ID == Obj::TOWN) - continue; - - for(auto creature : d->creatures) - { - if(creature.first) //there are more than 0 creatures avaliabe - { - for(auto type : creature.second) - { - if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->creatures[type]->cost)) - vstd::concatenate(solutions, ai->ah->howToVisitObj(obj)); - } - } - } - } - - return solutions; - //TODO: exchange troops between heroes -} - -bool Goals::GatherTroops::fulfillsMe(TSubgoal goal) -{ - if (!hero || hero == goal->hero) //we got army for desired hero or any hero - if (goal->objid == objid) //same creature type //TODO: consider upgrades? - if (goal->value >= value) //notify every time we get resources? - return true; - return false; -} - -TSubgoal Conquer::whatToDoToAchieve() -{ - return fh->chooseSolution(getAllPossibleSubgoals()); -} -TGoalVec Conquer::getAllPossibleSubgoals() -{ - TGoalVec ret; - - auto conquerable = [](const CGObjectInstance * obj) -> bool - { - if(cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) - { - switch(obj->ID.num) - { - case Obj::TOWN: - case Obj::HERO: - case Obj::CREATURE_GENERATOR1: - case Obj::MINE: //TODO: check ai->knownSubterraneanGates - return true; - } - } - return false; - }; - - std::vector objs; - for(auto obj : ai->visitableObjs) - { - if(conquerable(obj)) - objs.push_back(obj); - } - - for(auto h : cb->getHeroesInfo()) - { - std::vector ourObjs(objs); //copy common objects - - for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero - { - if(conquerable(obj)) - ourObjs.push_back(obj); - } - for(auto obj : ourObjs) - { - auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj)); - - vstd::concatenate(ret, waysToGo); - } - } - if(!objs.empty() && ai->canRecruitAnyHero()) //probably no point to recruit hero if we see no objects to capture - ret.push_back(sptr(Goals::RecruitHero())); - - if(ret.empty()) - ret.push_back(sptr(Goals::Explore())); //we need to find an enemy - return ret; -} - -TGoalVec Goals::Build::getAllPossibleSubgoals() -{ - TGoalVec ret; - - for (const CGTownInstance * t : cb->getTownsInfo()) - { - //start fresh with every town - ai->ah->getBuildingOptions(t); - auto immediateBuilding = ai->ah->immediateBuilding(); - auto expensiveBuilding = ai->ah->expensiveBuilding(); - - //handling for early town development to save money and focus on income - if(!t->hasBuilt(ai->ah->getMaxPossibleGoldBuilding(t)) && expensiveBuilding.is_initialized()) - { - auto potentialBuilding = expensiveBuilding.get(); - switch(expensiveBuilding.get().bid) - { - case BuildingID::TOWN_HALL: - case BuildingID::CITY_HALL: - case BuildingID::CAPITOL: - case BuildingID::FORT: - case BuildingID::CITADEL: - case BuildingID::CASTLE: - //If above buildings are next to be bought, but no money... do not buy anything else, try to gather resources for these. Simple but has to suffice for now. - auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(Goals::BuildThis(potentialBuilding.bid, t).setpriority(2.25))); - ret.push_back(goal); - return ret; - break; - } - } - - if (immediateBuilding.is_initialized()) - { - ret.push_back(sptr(Goals::BuildThis(immediateBuilding.get().bid, t).setpriority(2))); //prioritize buildings we can build quick - } - else //try build later - { - if (expensiveBuilding.is_initialized()) - { - auto potentialBuilding = expensiveBuilding.get(); //gather resources for any we can't afford - auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(Goals::BuildThis(potentialBuilding.bid, t).setpriority(0.5))); - ret.push_back(goal); - } - } - } - - if (ret.empty()) - throw cannotFulfillGoalException("BUILD has been realized as much as possible."); - else - return ret; -} - -TSubgoal Build::whatToDoToAchieve() -{ - return fh->chooseSolution(getAllPossibleSubgoals()); -} - -bool Goals::Build::fulfillsMe(TSubgoal goal) -{ - if (goal->goalType == Goals::BUILD || goal->goalType == Goals::BUILD_STRUCTURE) - return (!town || town == goal->town); //building anything will do, in this town if set - else - return false; -} - -TSubgoal Invalid::whatToDoToAchieve() -{ - return iAmElementar(); -} - -std::string GatherArmy::completeMessage() const -{ - return "Hero " + hero.get()->name + " gathered army of value " + boost::lexical_cast(value); -} - -TSubgoal GatherArmy::whatToDoToAchieve() -{ - //TODO: find hero if none set - assert(hero.h); - - return fh->chooseSolution(getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing. -} - -static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, - BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7}; - -TGoalVec GatherArmy::getAllPossibleSubgoals() -{ - //get all possible towns, heroes and dwellings we may use - TGoalVec ret; - - if(!hero.validAndSet()) - { - return ret; - } - - //TODO: include evaluation of monsters gather in calculation - for(auto t : cb->getTownsInfo()) - { - auto pos = t->visitablePos(); - if(ai->isAccessibleForHero(pos, hero)) - { - //grab army from town - if(!t->visitingHero && howManyReinforcementsCanGet(hero, t)) - { - if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) - ret.push_back(sptr(Goals::VisitTile(pos).sethero(hero))); - } - //buy army in town - if (!t->visitingHero || t->visitingHero == hero.get(true)) - { - ui32 val = std::min(value, howManyReinforcementsCanBuy(hero, t)); - if (val) - { - auto goal = sptr(Goals::BuyArmy(t, val).sethero(hero)); - if(!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice - ret.push_back(goal); - else - logAi->debug("Can not buy army, because of ai->ah->containsObjective"); - } - } - //build dwelling - //TODO: plan building over multiple turns? - //auto bid = ah->canBuildAnyStructure(t, std::vector(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)); - - //Do not use below code for now, rely on generic Build. Code below needs to know a lot of town/resource context to do more good than harm - /*auto bid = ai->ah->canBuildAnyStructure(t, std::vector(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 1); - if (bid.is_initialized()) - { - auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority)); - if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice - ret.push_back(goal); - else - logAi->debug("Can not build a structure, because of ai->ah->containsObjective"); - }*/ - } - } - - auto otherHeroes = cb->getHeroesInfo(); - auto heroDummy = hero; - vstd::erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h) - { - if(h == heroDummy.h) - return true; - else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)) - return true; - else if(!ai->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue - return true; - else if(ai->getGoal(h)->goalType == Goals::GATHER_ARMY) - return true; - 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(Goals::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(Goals::VisitHero(hero->id.getNum()).setisAbstract(true).sethero(h))); - } - - std::vector objs; - for(auto obj : ai->visitableObjs) - { - if(obj->ID == Obj::CREATURE_GENERATOR1) - { - auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID); - - //Use flagged dwellings only when there are available creatures that we can afford - if(relationToOwner == PlayerRelations::SAME_PLAYER) - { - auto dwelling = dynamic_cast(obj); - - ui32 val = std::min(value, howManyReinforcementsCanBuy(hero, dwelling)); - - if(val) - { - for(auto & creLevel : dwelling->creatures) - { - if(creLevel.first) - { - for(auto & creatureID : creLevel.second) - { - auto creature = VLC->creh->creatures[creatureID]; - if(ai->ah->freeResources().canAfford(creature->cost)) - objs.push_back(obj); //TODO: reserve resources? - } - } - } - } - } - } - } - for(auto h : cb->getHeroesInfo()) - { - for(auto obj : objs) - { - //find safe dwelling - if(ai->isGoodForVisit(obj, h)) - { - vstd::concatenate(ret, ai->ah->howToVisitObj(h, obj)); - } - } - } - - if(ai->canRecruitAnyHero() && ai->ah->freeGold() > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game - { - if(auto t = ai->findTownWithTavern()) - { - for(auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes - { - if(h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy - { - ret.push_back(sptr(Goals::RecruitHero())); - break; - } - } - } - } - - if(ret.empty()) - { - if(hero == ai->primaryHero()) - ret.push_back(sptr(Goals::Explore())); - else - throw cannotFulfillGoalException("No ways to gather army"); - } - - return ret; -} - -//TSubgoal AbstractGoal::whatToDoToAchieve() -//{ -// logAi->debug("Decomposing goal of type %s",name()); -// return sptr (Goals::Explore()); -//} - -TSubgoal AbstractGoal::goVisitOrLookFor(const CGObjectInstance * obj) -{ - if(obj) - return sptr(Goals::VisitObj(obj->id.getNum())); - else - return sptr(Goals::Explore()); -} - -TSubgoal AbstractGoal::lookForArtSmart(int aid) -{ - return sptr(Goals::Invalid()); -} - -bool AbstractGoal::invalid() const -{ - return goalType == INVALID; -} - -void AbstractGoal::accept(VCAI * ai) -{ - ai->tryRealize(*this); -} - - -template -void CGoal::accept(VCAI * ai) -{ - ai->tryRealize(static_cast(*this)); //casting enforces template instantiation -} - -float AbstractGoal::accept(FuzzyHelper * f) -{ - return f->evaluate(*this); -} - -template -float CGoal::accept(FuzzyHelper * f) -{ - return f->evaluate(static_cast(*this)); //casting enforces template instantiation -} diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h deleted file mode 100644 index 2277e7066..000000000 --- a/AI/VCAI/Goals.h +++ /dev/null @@ -1,698 +0,0 @@ -/* - * Goals.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/VCMI_Lib.h" -#include "../../lib/CBuildingHandler.h" -#include "../../lib/CCreatureHandler.h" -#include "../../lib/CTownHandler.h" -#include "AIUtility.h" - -struct HeroPtr; -class VCAI; -class FuzzyHelper; - -namespace Goals -{ -class AbstractGoal; -class VisitTile; - -class DLL_EXPORT TSubgoal : public std::shared_ptr -{ - public: - bool operator==(const TSubgoal & rhs) const; - bool operator<(const TSubgoal & rhs) const; - //TODO: serialize? -}; - -typedef std::vector TGoalVec; - -enum EGoals -{ - INVALID = -1, - WIN, DO_NOT_LOSE, CONQUER, BUILD, //build needs to get a real reasoning - EXPLORE, GATHER_ARMY, - BOOST_HERO, - RECRUIT_HERO, - BUILD_STRUCTURE, //if hero set, then in visited town - COLLECT_RES, - GATHER_TROOPS, // val of creatures with objid - - OBJECT_GOALS_BEGIN, - VISIT_OBJ, //visit or defeat or collect the object - FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid) - VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn - - GET_ART_TYPE, - - //BUILD_STRUCTURE, - ISSUE_COMMAND, - - VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable - CLEAR_WAY_TO, - DIG_AT_TILE,//elementar with hero on tile - BUY_ARMY, //at specific town - TRADE, //val resID at object objid - BUILD_BOAT -}; - - //method chaining + clone pattern -#define VSETTER(type, field) virtual AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;}; -#define OSETTER(type, field) CGoal & set ## field(const type &rhs) override { field = rhs; return *this; }; - -#if 0 - #define SETTER -#endif // _DEBUG - -enum {LOW_PR = -1}; - -DLL_EXPORT TSubgoal sptr(const AbstractGoal & tmp); - -struct DLL_EXPORT EvaluationContext -{ - uint64_t movementCost; - int manaCost; - uint64_t danger; - - EvaluationContext() - :movementCost(0), danger(0), manaCost(0) - { - } -}; - -class DLL_EXPORT AbstractGoal -{ -public: - bool isElementar; VSETTER(bool, isElementar) - bool isAbstract; VSETTER(bool, isAbstract) - float priority; VSETTER(float, priority) - int value; VSETTER(int, value) - int resID; VSETTER(int, resID) - int objid; VSETTER(int, objid) - int aid; VSETTER(int, aid) - int3 tile; VSETTER(int3, tile) - HeroPtr hero; VSETTER(HeroPtr, hero) - const CGTownInstance *town; VSETTER(CGTownInstance *, town) - int bid; VSETTER(int, bid) - TSubgoal parent; VSETTER(TSubgoal, parent) - EvaluationContext evaluationContext; VSETTER(EvaluationContext, evaluationContext) - - AbstractGoal(EGoals goal = INVALID) - : goalType (goal), evaluationContext() - { - priority = 0; - isElementar = false; - isAbstract = false; - value = 0; - aid = -1; - resID = -1; - objid = -1; - tile = int3(-1, -1, -1); - town = nullptr; - bid = -1; - } - virtual ~AbstractGoal(){} - //FIXME: abstract goal should be abstract, but serializer fails to instantiate subgoals in such case - virtual AbstractGoal * clone() const - { - return const_cast(this); - } - virtual TGoalVec getAllPossibleSubgoals() - { - return TGoalVec(); - } - virtual TSubgoal whatToDoToAchieve() - { - return sptr(AbstractGoal()); - } - - EGoals goalType; - - virtual std::string name() const; - virtual std::string completeMessage() const - { - return "This goal is unspecified!"; - } - - bool invalid() const; - - static TSubgoal goVisitOrLookFor(const CGObjectInstance * obj); //if obj is nullptr, then we'll explore - static TSubgoal lookForArtSmart(int aid); //checks non-standard ways of obtaining art (merchants, quests, etc.) - - ///Visitor pattern - //TODO: make accept work for std::shared_ptr... somehow - virtual void accept(VCAI * ai); //unhandled goal will report standard error - virtual float accept(FuzzyHelper * f); - - virtual bool operator==(const AbstractGoal & g) const; - bool operator<(AbstractGoal & g); //final - virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check - { - return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately - } - - bool operator!=(const AbstractGoal & g) const - { - return !(*this == g); - } - - template void serialize(Handler & h, const int version) - { - h & goalType; - h & isElementar; - h & isAbstract; - h & priority; - h & value; - h & resID; - h & objid; - h & aid; - h & tile; - h & hero; - h & town; - h & bid; - } -}; - -template class DLL_EXPORT CGoal : public AbstractGoal -{ -public: - CGoal(EGoals goal = INVALID) : AbstractGoal(goal) - { - priority = 0; - isElementar = false; - isAbstract = false; - value = 0; - aid = -1; - objid = -1; - resID = -1; - tile = int3(-1, -1, -1); - town = nullptr; - } - - OSETTER(bool, isElementar) - OSETTER(bool, isAbstract) - OSETTER(float, priority) - OSETTER(int, value) - OSETTER(int, resID) - OSETTER(int, objid) - OSETTER(int, aid) - OSETTER(int3, tile) - OSETTER(HeroPtr, hero) - OSETTER(CGTownInstance *, town) - OSETTER(int, bid) - - void accept(VCAI * ai) override; - float accept(FuzzyHelper * f) override; - - CGoal * clone() const override - { - return new T(static_cast(*this)); //casting enforces template instantiation - } - TSubgoal iAmElementar() - { - setisElementar(true); //FIXME: it's not const-correct, maybe we shoudl only set returned clone? - TSubgoal ptr; - ptr.reset(clone()); - return ptr; - } - template void serialize(Handler & h, const int version) - { - h & static_cast(*this); - //h & goalType & isElementar & isAbstract & priority; - //h & value & resID & objid & aid & tile & hero & town & bid; - } - - virtual bool operator==(const AbstractGoal & g) const override - { - if(goalType != g.goalType) - return false; - - return (*this) == (dynamic_cast(g)); - } - - virtual bool operator==(const T & other) const = 0; -}; - -class DLL_EXPORT Invalid : public CGoal -{ -public: - Invalid() - : CGoal(Goals::INVALID) - { - priority = -1e10; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - - virtual bool operator==(const Invalid & other) const override - { - return true; - } -}; - -class DLL_EXPORT Win : public CGoal -{ -public: - Win() - : CGoal(Goals::WIN) - { - priority = 100; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - - virtual bool operator==(const Win & other) const override - { - return true; - } -}; - -class DLL_EXPORT NotLose : public CGoal -{ -public: - NotLose() - : CGoal(Goals::DO_NOT_LOSE) - { - priority = 100; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - //TSubgoal whatToDoToAchieve() override; - - virtual bool operator==(const NotLose & other) const override - { - return true; - } -}; - -class DLL_EXPORT Conquer : public CGoal -{ -public: - Conquer() - : CGoal(Goals::CONQUER) - { - priority = 10; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const Conquer & other) const override; -}; - -class DLL_EXPORT Build : public CGoal -{ -public: - Build() - : CGoal(Goals::BUILD) - { - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - bool fulfillsMe(TSubgoal goal) override; - - virtual bool operator==(const Build & other) const override - { - return true; - } -}; - -class DLL_EXPORT BuildBoat : public CGoal -{ -private: - const IShipyard * shipyard; - -public: - BuildBoat(const IShipyard * shipyard) - : CGoal(Goals::BUILD_BOAT), shipyard(shipyard) - { - priority = 0; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - void accept(VCAI * ai) override; - std::string name() const override; - std::string completeMessage() const override; - virtual bool operator==(const BuildBoat & other) const override; -}; - -class DLL_EXPORT Explore : public CGoal -{ -public: - Explore() - : CGoal(Goals::EXPLORE) - { - priority = 1; - } - Explore(HeroPtr h) - : CGoal(Goals::EXPLORE) - { - hero = h; - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const Explore & other) const override; -}; - -class DLL_EXPORT GatherArmy : public CGoal -{ -public: - GatherArmy() - : CGoal(Goals::GATHER_ARMY) - { - } - GatherArmy(int val) - : CGoal(Goals::GATHER_ARMY) - { - value = val; - priority = 2.5; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - virtual bool operator==(const GatherArmy & other) const override; -}; - -class DLL_EXPORT BuyArmy : public CGoal -{ -private: - BuyArmy() - : CGoal(Goals::BUY_ARMY) - {} -public: - BuyArmy(const CGTownInstance * Town, int val) - : CGoal(Goals::BUY_ARMY) - { - town = Town; //where to buy this army - value = val; //expressed in AI unit strength - priority = 3;//TODO: evaluate? - } - bool fulfillsMe(TSubgoal goal) override; - - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - virtual bool operator==(const BuyArmy & other) const override; -}; - -class DLL_EXPORT BoostHero : public CGoal -{ -public: - BoostHero() - : CGoal(Goals::INVALID) - { - priority = -1e10; //TODO - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - virtual bool operator==(const BoostHero & other) const override; - //TSubgoal whatToDoToAchieve() override {return sptr(Invalid());}; -}; - -class DLL_EXPORT RecruitHero : public CGoal -{ -public: - RecruitHero() - : CGoal(Goals::RECRUIT_HERO) - { - priority = 1; - } - - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - - TSubgoal whatToDoToAchieve() override; - - virtual bool operator==(const RecruitHero & other) const override - { - return true; - } -}; - -class DLL_EXPORT BuildThis : public CGoal -{ -public: - BuildThis() //should be private, but unit test uses it - : CGoal(Goals::BUILD_STRUCTURE) - {} - BuildThis(BuildingID Bid, const CGTownInstance * tid) - : CGoal(Goals::BUILD_STRUCTURE) - { - bid = Bid; - town = tid; - priority = 1; - } - BuildThis(BuildingID Bid) - : CGoal(Goals::BUILD_STRUCTURE) - { - bid = Bid; - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - //bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const BuildThis & other) const override; -}; - -class DLL_EXPORT CollectRes : public CGoal -{ -public: - CollectRes() - : CGoal(Goals::COLLECT_RES) - { - } - CollectRes(int rid, int val) - : CGoal(Goals::COLLECT_RES) - { - resID = rid; - value = val; - priority = 2; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - TSubgoal whatToDoToTrade(); - bool fulfillsMe(TSubgoal goal) override; //TODO: Trade - virtual bool operator==(const CollectRes & other) const override; -}; - -class DLL_EXPORT Trade : public CGoal -{ -public: - Trade() - : CGoal(Goals::TRADE) - { - } - Trade(int rid, int val, int Objid) - : CGoal(Goals::TRADE) - { - resID = rid; - value = val; - objid = Objid; - priority = 3; //trading is instant, but picking resources is free - } - TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const Trade & other) const override; -}; - -class DLL_EXPORT GatherTroops : public CGoal -{ -public: - GatherTroops() - : CGoal(Goals::GATHER_TROOPS) - { - priority = 2; - } - GatherTroops(int type, int val) - : CGoal(Goals::GATHER_TROOPS) - { - objid = type; - value = val; - priority = 2; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const GatherTroops & other) const override; - -private: - int getCreaturesCount(const CArmedInstance * army); -}; - -class DLL_EXPORT VisitObj : public CGoal //this goal was previously known as GetObj -{ -public: - VisitObj() = delete; // empty constructor not allowed - VisitObj(int Objid); - - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - bool fulfillsMe(TSubgoal goal) override; - std::string completeMessage() const override; - virtual bool operator==(const VisitObj & other) const override; -}; - -class DLL_EXPORT FindObj : public CGoal -{ -public: - FindObj() {} // empty constructor not allowed - - FindObj(int ID) - : CGoal(Goals::FIND_OBJ) - { - objid = ID; - resID = -1; //subid unspecified - priority = 1; - } - FindObj(int ID, int subID) - : CGoal(Goals::FIND_OBJ) - { - objid = ID; - resID = subID; - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const FindObj & other) const override; -}; - -class DLL_EXPORT VisitHero : public CGoal -{ -public: - VisitHero() - : CGoal(Goals::VISIT_HERO) - { - } - VisitHero(int hid) - : CGoal(Goals::VISIT_HERO) - { - objid = hid; - priority = 4; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - bool fulfillsMe(TSubgoal goal) override; - std::string completeMessage() const override; - virtual bool operator==(const VisitHero & other) const override; -}; - -class DLL_EXPORT GetArtOfType : public CGoal -{ -public: - GetArtOfType() - : CGoal(Goals::GET_ART_TYPE) - { - } - GetArtOfType(int type) - : CGoal(Goals::GET_ART_TYPE) - { - aid = type; - priority = 2; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const GetArtOfType & other) const override; -}; - -class DLL_EXPORT VisitTile : public CGoal - //tile, in conjunction with hero elementar; assumes tile is reachable -{ -public: - VisitTile() {} // empty constructor not allowed - - VisitTile(int3 Tile) - : CGoal(Goals::VISIT_TILE) - { - tile = Tile; - priority = 5; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - virtual bool operator==(const VisitTile & other) const override; -}; - -class DLL_EXPORT ClearWayTo : public CGoal -{ -public: - ClearWayTo() - : CGoal(Goals::CLEAR_WAY_TO) - { - } - ClearWayTo(int3 Tile) - : CGoal(Goals::CLEAR_WAY_TO) - { - tile = Tile; - priority = 5; - } - ClearWayTo(int3 Tile, HeroPtr h) - : CGoal(Goals::CLEAR_WAY_TO) - { - tile = Tile; - hero = h; - priority = 5; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - bool fulfillsMe(TSubgoal goal) override; - virtual bool operator==(const ClearWayTo & other) const override; -}; - -class DLL_EXPORT DigAtTile : public CGoal - //elementar with hero on tile -{ -public: - DigAtTile() - : CGoal(Goals::DIG_AT_TILE) - { - } - DigAtTile(int3 Tile) - : CGoal(Goals::DIG_AT_TILE) - { - tile = Tile; - priority = 20; - } - TGoalVec getAllPossibleSubgoals() override - { - return TGoalVec(); - } - TSubgoal whatToDoToAchieve() override; - virtual bool operator==(const DigAtTile & other) const override; -}; - -} diff --git a/AI/VCAI/Goals/AbstractGoal.cpp b/AI/VCAI/Goals/AbstractGoal.cpp new file mode 100644 index 000000000..2cecbc226 --- /dev/null +++ b/AI/VCAI/Goals/AbstractGoal.cpp @@ -0,0 +1,192 @@ +/* +* AbstractGoal.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "AbstractGoal.h" +#include "../VCAI.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +TSubgoal Goals::sptr(const AbstractGoal & tmp) +{ + TSubgoal ptr; + ptr.reset(tmp.clone()); + return ptr; +} + +std::string AbstractGoal::name() const //TODO: virtualize +{ + std::string desc; + switch(goalType) + { + case INVALID: + return "INVALID"; + case WIN: + return "WIN"; + case DO_NOT_LOSE: + return "DO NOT LOOSE"; + case CONQUER: + return "CONQUER"; + case BUILD: + return "BUILD"; + case EXPLORE: + desc = "EXPLORE"; + break; + case GATHER_ARMY: + desc = "GATHER ARMY"; + break; + case BUY_ARMY: + return "BUY ARMY"; + break; + case BOOST_HERO: + desc = "BOOST_HERO (unsupported)"; + break; + case RECRUIT_HERO: + return "RECRUIT HERO"; + case BUILD_STRUCTURE: + return "BUILD STRUCTURE"; + case COLLECT_RES: + desc = "COLLECT RESOURCE " + GameConstants::RESOURCE_NAMES[resID] + " (" + boost::lexical_cast(value) + ")"; + break; + case TRADE: + { + auto obj = cb->getObjInstance(ObjectInstanceID(objid)); + if (obj) + desc = (boost::format("TRADE %d of %s at %s") % value % GameConstants::RESOURCE_NAMES[resID] % obj->getObjectName()).str(); + } + break; + case GATHER_TROOPS: + desc = "GATHER TROOPS"; + break; + case VISIT_OBJ: + { + auto obj = cb->getObjInstance(ObjectInstanceID(objid)); + if(obj) + desc = "GET OBJ " + obj->getObjectName(); + } + break; + case FIND_OBJ: + desc = "FIND OBJ " + boost::lexical_cast(objid); + break; + case VISIT_HERO: + { + auto obj = cb->getObjInstance(ObjectInstanceID(objid)); + if(obj) + desc = "VISIT HERO " + obj->getObjectName(); + } + break; + case GET_ART_TYPE: + desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name(); + break; + case ISSUE_COMMAND: + return "ISSUE COMMAND (unsupported)"; + case VISIT_TILE: + desc = "VISIT TILE " + tile.toString(); + break; + case CLEAR_WAY_TO: + desc = "CLEAR WAY TO " + tile.toString(); + break; + case DIG_AT_TILE: + desc = "DIG AT TILE " + tile.toString(); + break; + default: + return boost::lexical_cast(goalType); + } + if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal + desc += " (" + hero->name + ")"; + return desc; +} + +bool AbstractGoal::operator==(const AbstractGoal & g) const +{ + return false; +} + +bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique +{ + //TODO: make sure it gets goals consistent with == operator + if (goalType < g.goalType) + return true; + if (goalType > g.goalType) + return false; + if (hero < g.hero) + return true; + if (hero > g.hero) + return false; + if (tile < g.tile) + return true; + if (g.tile < tile) + return false; + if (objid < g.objid) + return true; + if (objid > g.objid) + return false; + if (town < g.town) + return true; + if (town > g.town) + return false; + if (value < g.value) + return true; + if (value > g.value) + return false; + if (priority < g.priority) + return true; + if (priority > g.priority) + return false; + if (resID < g.resID) + return true; + if (resID > g.resID) + return false; + if (bid < g.bid) + return true; + if (bid > g.bid) + return false; + if (aid < g.aid) + return true; + if (aid > g.aid) + return false; + return false; +} + +//TODO: find out why the following are not generated automatically on MVS? +bool TSubgoal::operator==(const TSubgoal & rhs) const +{ + return *get() == *rhs.get(); //comparison for Goals is overloaded, so they don't need to be identical to match +} + +bool TSubgoal::operator<(const TSubgoal & rhs) const +{ + return get() < rhs.get(); //compae by value +} + +bool AbstractGoal::invalid() const +{ + return goalType == EGoals::INVALID; +} + +void AbstractGoal::accept(VCAI * ai) +{ + ai->tryRealize(*this); +} + +float AbstractGoal::accept(FuzzyHelper * f) +{ + return f->evaluate(*this); +} diff --git a/AI/VCAI/Goals/AbstractGoal.h b/AI/VCAI/Goals/AbstractGoal.h new file mode 100644 index 000000000..d5ddf6a03 --- /dev/null +++ b/AI/VCAI/Goals/AbstractGoal.h @@ -0,0 +1,193 @@ +/* +* AbstractGoal.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "../../../lib/VCMI_Lib.h" +#include "../../../lib/CBuildingHandler.h" +#include "../../../lib/CCreatureHandler.h" +#include "../../../lib/CTownHandler.h" +#include "../AIUtility.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class AbstractGoal; + class Explore; + class RecruitHero; + class VisitTile; + class VisitObj; + class VisitHero; + class BuildThis; + class DigAtTile; + class CollectRes; + class Build; + class BuyArmy; + class BuildBoat; + class GatherArmy; + class ClearWayTo; + class Invalid; + class Trade; + + class DLL_EXPORT TSubgoal : public std::shared_ptr + { + public: + bool operator==(const TSubgoal & rhs) const; + bool operator<(const TSubgoal & rhs) const; + //TODO: serialize? + }; + + typedef std::vector TGoalVec; + + enum EGoals + { + INVALID = -1, + WIN, DO_NOT_LOSE, CONQUER, BUILD, //build needs to get a real reasoning + EXPLORE, GATHER_ARMY, + BOOST_HERO, + RECRUIT_HERO, + BUILD_STRUCTURE, //if hero set, then in visited town + COLLECT_RES, + GATHER_TROOPS, // val of creatures with objid + + OBJECT_GOALS_BEGIN, + VISIT_OBJ, //visit or defeat or collect the object + FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid) + VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn + + GET_ART_TYPE, + + //BUILD_STRUCTURE, + ISSUE_COMMAND, + + VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable + CLEAR_WAY_TO, + DIG_AT_TILE,//elementar with hero on tile + BUY_ARMY, //at specific town + TRADE, //val resID at object objid + BUILD_BOAT + }; + + //method chaining + clone pattern +#define VSETTER(type, field) virtual AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;}; +#define OSETTER(type, field) CGoal & set ## field(const type &rhs) override { field = rhs; return *this; }; + +#if 0 +#define SETTER +#endif // _DEBUG + + enum { LOW_PR = -1 }; + + DLL_EXPORT TSubgoal sptr(const AbstractGoal & tmp); + + struct DLL_EXPORT EvaluationContext + { + uint64_t movementCost; + int manaCost; + uint64_t danger; + + EvaluationContext() + :movementCost(0), danger(0), manaCost(0) + { + } + }; + + class DLL_EXPORT AbstractGoal + { + public: + bool isElementar; VSETTER(bool, isElementar) + bool isAbstract; VSETTER(bool, isAbstract) + float priority; VSETTER(float, priority) + int value; VSETTER(int, value) + int resID; VSETTER(int, resID) + int objid; VSETTER(int, objid) + int aid; VSETTER(int, aid) + int3 tile; VSETTER(int3, tile) + HeroPtr hero; VSETTER(HeroPtr, hero) + const CGTownInstance *town; VSETTER(CGTownInstance *, town) + int bid; VSETTER(int, bid) + TSubgoal parent; VSETTER(TSubgoal, parent) + EvaluationContext evaluationContext; VSETTER(EvaluationContext, evaluationContext) + + AbstractGoal(EGoals goal = EGoals::INVALID) + : goalType(goal), evaluationContext() + { + priority = 0; + isElementar = false; + isAbstract = false; + value = 0; + aid = -1; + resID = -1; + objid = -1; + tile = int3(-1, -1, -1); + town = nullptr; + bid = -1; + } + virtual ~AbstractGoal() {} + //FIXME: abstract goal should be abstract, but serializer fails to instantiate subgoals in such case + virtual AbstractGoal * clone() const + { + return const_cast(this); + } + virtual TGoalVec getAllPossibleSubgoals() + { + return TGoalVec(); + } + virtual TSubgoal whatToDoToAchieve() + { + return sptr(AbstractGoal()); + } + + EGoals goalType; + + virtual std::string name() const; + virtual std::string completeMessage() const + { + return "This goal is unspecified!"; + } + + bool invalid() const; + + ///Visitor pattern + //TODO: make accept work for std::shared_ptr... somehow + virtual void accept(VCAI * ai); //unhandled goal will report standard error + virtual float accept(FuzzyHelper * f); + + virtual bool operator==(const AbstractGoal & g) const; + bool operator<(AbstractGoal & g); //final + virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check + { + return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately + } + + bool operator!=(const AbstractGoal & g) const + { + return !(*this == g); + } + + template void serialize(Handler & h, const int version) + { + h & goalType; + h & isElementar; + h & isAbstract; + h & priority; + h & value; + h & resID; + h & objid; + h & aid; + h & tile; + h & hero; + h & town; + h & bid; + } + }; +} diff --git a/AI/VCAI/Goals/Build.cpp b/AI/VCAI/Goals/Build.cpp new file mode 100644 index 000000000..140f7e99f --- /dev/null +++ b/AI/VCAI/Goals/Build.cpp @@ -0,0 +1,93 @@ +/* +* Build.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Build.h" +#include "BuildThis.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +TGoalVec Build::getAllPossibleSubgoals() +{ + TGoalVec ret; + + for(const CGTownInstance * t : cb->getTownsInfo()) + { + //start fresh with every town + ai->ah->getBuildingOptions(t); + auto immediateBuilding = ai->ah->immediateBuilding(); + auto expensiveBuilding = ai->ah->expensiveBuilding(); + + //handling for early town development to save money and focus on income + if(!t->hasBuilt(ai->ah->getMaxPossibleGoldBuilding(t)) && expensiveBuilding.is_initialized()) + { + auto potentialBuilding = expensiveBuilding.get(); + switch(expensiveBuilding.get().bid) + { + case BuildingID::TOWN_HALL: + case BuildingID::CITY_HALL: + case BuildingID::CAPITOL: + case BuildingID::FORT: + case BuildingID::CITADEL: + case BuildingID::CASTLE: + //If above buildings are next to be bought, but no money... do not buy anything else, try to gather resources for these. Simple but has to suffice for now. + auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(BuildThis(potentialBuilding.bid, t).setpriority(2.25))); + ret.push_back(goal); + return ret; + break; + } + } + + if(immediateBuilding.is_initialized()) + { + ret.push_back(sptr(BuildThis(immediateBuilding.get().bid, t).setpriority(2))); //prioritize buildings we can build quick + } + else //try build later + { + if(expensiveBuilding.is_initialized()) + { + auto potentialBuilding = expensiveBuilding.get(); //gather resources for any we can't afford + auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(BuildThis(potentialBuilding.bid, t).setpriority(0.5))); + ret.push_back(goal); + } + } + } + + if(ret.empty()) + throw cannotFulfillGoalException("BUILD has been realized as much as possible."); + else + return ret; +} + +TSubgoal Build::whatToDoToAchieve() +{ + return fh->chooseSolution(getAllPossibleSubgoals()); +} + +bool Build::fulfillsMe(TSubgoal goal) +{ + if(goal->goalType == BUILD || goal->goalType == BUILD_STRUCTURE) + return (!town || town == goal->town); //building anything will do, in this town if set + else + return false; +} diff --git a/AI/VCAI/Goals/Build.h b/AI/VCAI/Goals/Build.h new file mode 100644 index 000000000..c6d972d9f --- /dev/null +++ b/AI/VCAI/Goals/Build.h @@ -0,0 +1,37 @@ +/* +* Build.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT Build : public CGoal + { + public: + Build() + : CGoal(Goals::BUILD) + { + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + bool fulfillsMe(TSubgoal goal) override; + + virtual bool operator==(const Build & other) const override + { + return true; + } + }; +} diff --git a/AI/VCAI/Goals/BuildBoat.cpp b/AI/VCAI/Goals/BuildBoat.cpp new file mode 100644 index 000000000..45f3ff1fb --- /dev/null +++ b/AI/VCAI/Goals/BuildBoat.cpp @@ -0,0 +1,84 @@ +/* +* BuildBoat.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "BuildBoat.h" +#include "../VCAI.h" +#include "../FuzzyHelper.h" +#include "../AIhelper.h" +#include "../../lib/mapping/CMap.h" //for victory conditions +#include "../../lib/CPathfinder.h" + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool BuildBoat::operator==(const BuildBoat & other) const +{ + return shipyard->o->id == other.shipyard->o->id; +} + +TSubgoal BuildBoat::whatToDoToAchieve() +{ + if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES) + { + return fh->chooseSolution(ai->ah->howToVisitObj(shipyard->o)); + } + + if(shipyard->shipyardStatus() != IShipyard::GOOD) + { + throw cannotFulfillGoalException("Shipyard is busy."); + } + + TResources boatCost; + shipyard->getBoatCost(boatCost); + + return ai->ah->whatToDo(boatCost, this->iAmElementar()); +} + +void BuildBoat::accept(VCAI * ai) +{ + TResources boatCost; + shipyard->getBoatCost(boatCost); + + if(!cb->getResourceAmount().canAfford(boatCost)) + { + throw cannotFulfillGoalException("Can not afford boat"); + } + + if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES) + { + throw cannotFulfillGoalException("Can not build boat in enemy shipyard"); + } + + if(shipyard->shipyardStatus() != IShipyard::GOOD) + { + throw cannotFulfillGoalException("Shipyard is busy."); + } + + logAi->trace( + "Building boat at shipyard %s located at %s, estimated boat position %s", + shipyard->o->getObjectName(), + shipyard->o->visitablePos().toString(), + shipyard->bestLocation().toString()); + + cb->buildBoat(shipyard); +} + +std::string BuildBoat::name() const +{ + return "BuildBoat"; +} + +std::string BuildBoat::completeMessage() const +{ + return "Boat have been built at " + shipyard->o->visitablePos().toString(); +} diff --git a/AI/VCAI/Goals/BuildBoat.h b/AI/VCAI/Goals/BuildBoat.h new file mode 100644 index 000000000..367fa6ea9 --- /dev/null +++ b/AI/VCAI/Goals/BuildBoat.h @@ -0,0 +1,37 @@ +/* +* BuildBoat.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +namespace Goals +{ + class DLL_EXPORT BuildBoat : public CGoal + { + private: + const IShipyard * shipyard; + + public: + BuildBoat(const IShipyard * shipyard) + : CGoal(Goals::BUILD_BOAT), shipyard(shipyard) + { + priority = 0; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + void accept(VCAI * ai) override; + std::string name() const override; + std::string completeMessage() const override; + virtual bool operator==(const BuildBoat & other) const override; + }; +} diff --git a/AI/VCAI/Goals/BuildThis.cpp b/AI/VCAI/Goals/BuildThis.cpp new file mode 100644 index 000000000..68145538f --- /dev/null +++ b/AI/VCAI/Goals/BuildThis.cpp @@ -0,0 +1,74 @@ +/* +* BuildThis.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "BuildThis.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool BuildThis::operator==(const BuildThis & other) const +{ + return town == other.town && bid == other.bid; +} + +TSubgoal BuildThis::whatToDoToAchieve() +{ + auto b = BuildingID(bid); + + // find town if not set + if(!town && hero) + town = hero->visitedTown; + + if(!town) + { + for(const CGTownInstance * t : cb->getTownsInfo()) + { + switch(cb->canBuildStructure(town, b)) + { + case EBuildingState::ALLOWED: + town = t; + break; //TODO: look for prerequisites? this is not our reponsibility + default: + continue; + } + } + } + + if(town) //we have specific town to build this + { + switch(cb->canBuildStructure(town, b)) + { + case EBuildingState::ALLOWED: + case EBuildingState::NO_RESOURCES: + { + auto res = town->town->buildings.at(BuildingID(bid))->resources; + return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources + } + break; + default: + throw cannotFulfillGoalException("Not possible to build"); + } + } + else + throw cannotFulfillGoalException("Cannot find town to build this"); +} diff --git a/AI/VCAI/Goals/BuildThis.h b/AI/VCAI/Goals/BuildThis.h new file mode 100644 index 000000000..9cc86abb8 --- /dev/null +++ b/AI/VCAI/Goals/BuildThis.h @@ -0,0 +1,48 @@ +/* +* BuildThis.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT BuildThis : public CGoal + { + public: + BuildThis() //should be private, but unit test uses it + : CGoal(Goals::BUILD_STRUCTURE) + { + } + BuildThis(BuildingID Bid, const CGTownInstance * tid) + : CGoal(Goals::BUILD_STRUCTURE) + { + bid = Bid; + town = tid; + priority = 1; + } + BuildThis(BuildingID Bid) + : CGoal(Goals::BUILD_STRUCTURE) + { + bid = Bid; + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + //bool fulfillsMe(TSubgoal goal) override; + virtual bool operator==(const BuildThis & other) const override; + }; +} diff --git a/AI/VCAI/Goals/BuyArmy.cpp b/AI/VCAI/Goals/BuyArmy.cpp new file mode 100644 index 000000000..ffe376760 --- /dev/null +++ b/AI/VCAI/Goals/BuyArmy.cpp @@ -0,0 +1,45 @@ +/* +* BuyArmy.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "BuyArmy.h" +#include "../FuzzyHelper.h" +#include "../AIhelper.h" +#include "../../lib/mapObjects/CGTownInstance.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool BuyArmy::operator==(const BuyArmy & other) const +{ + return town == other.town && objid == other.objid; +} + +bool BuyArmy::fulfillsMe(TSubgoal goal) +{ + //if (hero && hero != goal->hero) + // return false; + return town == goal->town && goal->value >= value; //can always buy more army +} +TSubgoal BuyArmy::whatToDoToAchieve() +{ + //TODO: calculate the actual cost of units instead + TResources price; + price[Res::GOLD] = value * 0.4f; //some approximate value + return ai->ah->whatToDo(price, iAmElementar()); //buy right now or gather resources +} + +std::string BuyArmy::completeMessage() const +{ + return boost::format("Bought army of value %d in town of %s") % value, town->name; +} diff --git a/AI/VCAI/Goals/BuyArmy.h b/AI/VCAI/Goals/BuyArmy.h new file mode 100644 index 000000000..55dc97ec4 --- /dev/null +++ b/AI/VCAI/Goals/BuyArmy.h @@ -0,0 +1,41 @@ +/* +* BuyArmy.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT BuyArmy : public CGoal + { + private: + BuyArmy() + : CGoal(Goals::BUY_ARMY) + { + } + public: + BuyArmy(const CGTownInstance * Town, int val) + : CGoal(Goals::BUY_ARMY) + { + town = Town; //where to buy this army + value = val; //expressed in AI unit strength + priority = 3;//TODO: evaluate? + } + bool fulfillsMe(TSubgoal goal) override; + + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + virtual bool operator==(const BuyArmy & other) const override; + }; +} diff --git a/AI/VCAI/Goals/CGoal.h b/AI/VCAI/Goals/CGoal.h new file mode 100644 index 000000000..bf6761be2 --- /dev/null +++ b/AI/VCAI/Goals/CGoal.h @@ -0,0 +1,87 @@ +/* +* CGoal.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "AbstractGoal.h" +#include "../FuzzyHelper.h" +#include "../VCAI.h" + +struct HeroPtr; +class VCAI; + +namespace Goals +{ + template class DLL_EXPORT CGoal : public AbstractGoal + { + public: + CGoal(EGoals goal = INVALID) : AbstractGoal(goal) + { + priority = 0; + isElementar = false; + isAbstract = false; + value = 0; + aid = -1; + objid = -1; + resID = -1; + tile = int3(-1, -1, -1); + town = nullptr; + } + + OSETTER(bool, isElementar) + OSETTER(bool, isAbstract) + OSETTER(float, priority) + OSETTER(int, value) + OSETTER(int, resID) + OSETTER(int, objid) + OSETTER(int, aid) + OSETTER(int3, tile) + OSETTER(HeroPtr, hero) + OSETTER(CGTownInstance *, town) + OSETTER(int, bid) + + void accept(VCAI * ai) override + { + ai->tryRealize(static_cast(*this)); //casting enforces template instantiation + } + + float accept(FuzzyHelper * f) override + { + return f->evaluate(static_cast(*this)); //casting enforces template instantiation + } + + CGoal * clone() const override + { + return new T(static_cast(*this)); //casting enforces template instantiation + } + TSubgoal iAmElementar() + { + setisElementar(true); //FIXME: it's not const-correct, maybe we shoudl only set returned clone? + TSubgoal ptr; + ptr.reset(clone()); + return ptr; + } + template void serialize(Handler & h, const int version) + { + h & static_cast(*this); + //h & goalType & isElementar & isAbstract & priority; + //h & value & resID & objid & aid & tile & hero & town & bid; + } + + virtual bool operator==(const AbstractGoal & g) const override + { + if(goalType != g.goalType) + return false; + + return (*this) == (dynamic_cast(g)); + } + + virtual bool operator==(const T & other) const = 0; + }; +} diff --git a/AI/VCAI/Goals/ClearWayTo.cpp b/AI/VCAI/Goals/ClearWayTo.cpp new file mode 100644 index 000000000..e20c26ad5 --- /dev/null +++ b/AI/VCAI/Goals/ClearWayTo.cpp @@ -0,0 +1,85 @@ +/* +* ClearWayTo.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "ClearWayTo.h" +#include "Explore.h" +#include "RecruitHero.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../FuzzyHelper.h" +#include "../AIhelper.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool ClearWayTo::operator==(const ClearWayTo & other) const +{ + return other.hero.h == hero.h && other.tile == tile; +} + +TSubgoal ClearWayTo::whatToDoToAchieve() +{ + assert(cb->isInTheMap(tile)); //set tile + if(!cb->isVisible(tile)) + { + logAi->error("Clear way should be used with visible tiles!"); + return sptr(Explore()); + } + + return (fh->chooseSolution(getAllPossibleSubgoals())); +} + +bool ClearWayTo::fulfillsMe(TSubgoal goal) +{ + if (goal->goalType == VISIT_TILE) + { + if (!hero || hero == goal->hero) + return tile == goal->tile; + } + return false; +} + +TGoalVec ClearWayTo::getAllPossibleSubgoals() +{ + TGoalVec ret; + + std::vector heroes; + if(hero) + heroes.push_back(hero.h); + else + heroes = cb->getHeroesInfo(); + + for(auto h : heroes) + { + //TODO: handle clearing way to allied heroes that are blocked + //if ((hero && hero->visitablePos() == tile && hero == *h) || //we can't free the way ourselves + // h->visitablePos() == tile) //we are already on that tile! what does it mean? + // continue; + + //if our hero is trapped, make sure we request clearing the way from OUR perspective + + vstd::concatenate(ret, ai->ah->howToVisitTile(h, tile)); + } + + if(ret.empty() && ai->canRecruitAnyHero()) + ret.push_back(sptr(RecruitHero())); + + if(ret.empty()) + { + logAi->warn("There is no known way to clear the way to tile %s", tile.toString()); + throw goalFulfilledException(sptr(ClearWayTo(tile))); //make sure asigned hero gets unlocked + } + + return ret; +} diff --git a/AI/VCAI/Goals/ClearWayTo.h b/AI/VCAI/Goals/ClearWayTo.h new file mode 100644 index 000000000..432f1ad79 --- /dev/null +++ b/AI/VCAI/Goals/ClearWayTo.h @@ -0,0 +1,45 @@ +/* +* ClearWayTo.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT ClearWayTo : public CGoal + { + public: + ClearWayTo() + : CGoal(Goals::CLEAR_WAY_TO) + { + } + ClearWayTo(int3 Tile) + : CGoal(Goals::CLEAR_WAY_TO) + { + tile = Tile; + priority = 5; + } + ClearWayTo(int3 Tile, HeroPtr h) + : CGoal(Goals::CLEAR_WAY_TO) + { + tile = Tile; + hero = h; + priority = 5; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + bool fulfillsMe(TSubgoal goal) override; + virtual bool operator==(const ClearWayTo & other) const override; + }; +} diff --git a/AI/VCAI/Goals/CollectRes.cpp b/AI/VCAI/Goals/CollectRes.cpp new file mode 100644 index 000000000..0d0b8f822 --- /dev/null +++ b/AI/VCAI/Goals/CollectRes.cpp @@ -0,0 +1,208 @@ +/* +* CollectRes.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool CollectRes::operator==(const CollectRes & other) const +{ + return resID == other.resID; +} + +TGoalVec CollectRes::getAllPossibleSubgoals() +{ + TGoalVec ret; + + auto givesResource = [this](const CGObjectInstance * obj) -> bool + { + //TODO: move this logic to object side + //TODO: remember mithril exists + //TODO: water objects + //TODO: Creature banks + + //return false first from once-visitable, before checking if they were even visited + switch (obj->ID.num) + { + case Obj::TREASURE_CHEST: + return resID == Res::GOLD; + break; + case Obj::RESOURCE: + return obj->subID == resID; + break; + case Obj::MINE: + return (obj->subID == resID && + (cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines + break; + case Obj::CAMPFIRE: + return true; //contains all resources + break; + case Obj::WINDMILL: + switch (resID) + { + case Res::GOLD: + case Res::WOOD: + return false; + } + break; + case Obj::WATER_WHEEL: + if (resID != Res::GOLD) + return false; + break; + case Obj::MYSTICAL_GARDEN: + if ((resID != Res::GOLD) && (resID != Res::GEMS)) + return false; + break; + case Obj::LEAN_TO: + case Obj::WAGON: + if (resID != Res::GOLD) + return false; + break; + default: + return false; + break; + } + return !vstd::contains(ai->alreadyVisited, obj); //for weekly / once visitable + }; + + std::vector objs; + for (auto obj : ai->visitableObjs) + { + if (givesResource(obj)) + objs.push_back(obj); + } + for (auto h : cb->getHeroesInfo()) + { + std::vector ourObjs(objs); //copy common objects + + for (auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero + { + if (givesResource(obj)) + ourObjs.push_back(obj); + } + + for (auto obj : ourObjs) + { + auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj)); + + vstd::concatenate(ret, waysToGo); + } + } + return ret; +} + +TSubgoal CollectRes::whatToDoToAchieve() +{ + auto goals = getAllPossibleSubgoals(); + auto trade = whatToDoToTrade(); + if (!trade->invalid()) + goals.push_back(trade); + + if (goals.empty()) + return sptr(Explore()); //we can always do that + else + return fh->chooseSolution(goals); //TODO: evaluate trading +} + +TSubgoal CollectRes::whatToDoToTrade() +{ + std::vector markets; + + std::vector visObjs; + ai->retrieveVisitableObjs(visObjs, true); + for (const CGObjectInstance * obj : visObjs) + { + if (const IMarket * m = IMarket::castFrom(obj, false)) + { + if (obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE)) + markets.push_back(m); + else if (obj->ID == Obj::TRADING_POST) + markets.push_back(m); + } + } + + boost::sort(markets, [](const IMarket * m1, const IMarket * m2) -> bool + { + return m1->getMarketEfficiency() < m2->getMarketEfficiency(); + }); + + markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool + { + if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID)) + { + if (!ai->isAccessible(market->o->visitablePos())) + return true; + } + return false; + }), markets.end()); + + if (!markets.size()) + { + for (const CGTownInstance * t : cb->getTownsInfo()) + { + if (cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED) + return sptr(BuildThis(BuildingID::MARKETPLACE, t).setpriority(2)); + } + } + else + { + const IMarket * m = markets.back(); + //attempt trade at back (best prices) + int howManyCanWeBuy = 0; + for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1)) + { + if (i == resID) + continue; + int toGive = -1, toReceive = -1; + m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE); + assert(toGive > 0 && toReceive > 0); + howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive); + } + + if (howManyCanWeBuy >= value) + { + auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace + assert(backObj); + auto objid = m->o->id.getNum(); + if (backObj->tempOwner != ai->playerID) //top object not owned + { + return sptr(VisitObj(objid)); //just go there + } + else //either it's our town, or we have hero there + { + return sptr(Trade(resID, value, objid).setisElementar(true)); //we can do this immediately + } + } + } + return sptr(Invalid()); //cannot trade +} + +bool CollectRes::fulfillsMe(TSubgoal goal) +{ + if (goal->resID == resID) + if (goal->value >= value) + return true; + + return false; +} diff --git a/AI/VCAI/Goals/CollectRes.h b/AI/VCAI/Goals/CollectRes.h new file mode 100644 index 000000000..ea2d7893c --- /dev/null +++ b/AI/VCAI/Goals/CollectRes.h @@ -0,0 +1,40 @@ +/* +* CollectRes.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT CollectRes : public CGoal + { + public: + CollectRes() + : CGoal(Goals::COLLECT_RES) + { + } + CollectRes(int rid, int val) + : CGoal(Goals::COLLECT_RES) + { + resID = rid; + value = val; + priority = 2; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + TSubgoal whatToDoToTrade(); + bool fulfillsMe(TSubgoal goal) override; //TODO: Trade + virtual bool operator==(const CollectRes & other) const override; + }; +} diff --git a/AI/VCAI/Goals/Conquer.cpp b/AI/VCAI/Goals/Conquer.cpp new file mode 100644 index 000000000..33f46d3f4 --- /dev/null +++ b/AI/VCAI/Goals/Conquer.cpp @@ -0,0 +1,88 @@ +/* +* Conquer.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool Conquer::operator==(const Conquer & other) const +{ + return other.hero.h == hero.h; +} + +TSubgoal Conquer::whatToDoToAchieve() +{ + return fh->chooseSolution(getAllPossibleSubgoals()); +} + +TGoalVec Conquer::getAllPossibleSubgoals() +{ + TGoalVec ret; + + auto conquerable = [](const CGObjectInstance * obj) -> bool + { + if(cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES) + { + switch(obj->ID.num) + { + case Obj::TOWN: + case Obj::HERO: + case Obj::CREATURE_GENERATOR1: + case Obj::MINE: //TODO: check ai->knownSubterraneanGates + return true; + } + } + return false; + }; + + std::vector objs; + for(auto obj : ai->visitableObjs) + { + if(conquerable(obj)) + objs.push_back(obj); + } + + for(auto h : cb->getHeroesInfo()) + { + std::vector ourObjs(objs); //copy common objects + + for(auto obj : ai->reservedHeroesMap[h]) //add objects reserved by this hero + { + if(conquerable(obj)) + ourObjs.push_back(obj); + } + for(auto obj : ourObjs) + { + auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj)); + + vstd::concatenate(ret, waysToGo); + } + } + if(!objs.empty() && ai->canRecruitAnyHero()) //probably no point to recruit hero if we see no objects to capture + ret.push_back(sptr(RecruitHero())); + + if(ret.empty()) + ret.push_back(sptr(Explore())); //we need to find an enemy + return ret; +} diff --git a/AI/VCAI/Goals/Conquer.h b/AI/VCAI/Goals/Conquer.h new file mode 100644 index 000000000..295930347 --- /dev/null +++ b/AI/VCAI/Goals/Conquer.h @@ -0,0 +1,32 @@ +/* +* Conquer.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT Conquer : public CGoal + { + public: + Conquer() + : CGoal(Goals::CONQUER) + { + priority = 10; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + virtual bool operator==(const Conquer & other) const override; + }; +} diff --git a/AI/VCAI/Goals/DigAtTile.cpp b/AI/VCAI/Goals/DigAtTile.cpp new file mode 100644 index 000000000..230d62a5e --- /dev/null +++ b/AI/VCAI/Goals/DigAtTile.cpp @@ -0,0 +1,39 @@ +/* +* DigAtTile.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "DigAtTile.h" +#include "VisitTile.h" +#include "../VCAI.h" +#include "../AIUtility.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool DigAtTile::operator==(const DigAtTile & other) const +{ + return other.hero.h == hero.h && other.tile == tile; +} + +TSubgoal DigAtTile::whatToDoToAchieve() +{ + const CGObjectInstance * firstObj = vstd::frontOrNull(cb->getVisitableObjs(tile)); + if(firstObj && firstObj->ID == Obj::HERO && firstObj->tempOwner == ai->playerID) //we have hero at dest + { + const CGHeroInstance * h = dynamic_cast(firstObj); + sethero(h).setisElementar(true); + return sptr(*this); + } + + return sptr(VisitTile(tile)); +} diff --git a/AI/VCAI/Goals/DigAtTile.h b/AI/VCAI/Goals/DigAtTile.h new file mode 100644 index 000000000..963306539 --- /dev/null +++ b/AI/VCAI/Goals/DigAtTile.h @@ -0,0 +1,41 @@ +/* +* DigAtTile.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT DigAtTile : public CGoal + //elementar with hero on tile + { + public: + DigAtTile() + : CGoal(Goals::DIG_AT_TILE) + { + } + DigAtTile(int3 Tile) + : CGoal(Goals::DIG_AT_TILE) + { + tile = Tile; + priority = 20; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + virtual bool operator==(const DigAtTile & other) const override; + }; +} diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp new file mode 100644 index 000000000..b9edcdd5a --- /dev/null +++ b/AI/VCAI/Goals/Explore.cpp @@ -0,0 +1,181 @@ +/* +* Explore.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool Explore::operator==(const Explore & other) const +{ + return other.hero.h == hero.h; +} + +std::string Explore::completeMessage() const +{ + return "Hero " + hero.get()->name + " completed exploration"; +} + +TSubgoal Explore::whatToDoToAchieve() +{ + auto ret = fh->chooseSolution(getAllPossibleSubgoals()); + if(hero) //use best step for this hero + { + return ret; + } + else + { + if(ret->hero.get(true)) + return sptr(sethero(ret->hero.h).setisAbstract(true)); //choose this hero and then continue with him + else + return ret; //other solutions, like buying hero from tavern + } +} + +TGoalVec Explore::getAllPossibleSubgoals() +{ + TGoalVec ret; + std::vector heroes; + + if(hero) + { + heroes.push_back(hero.h); + } + else + { + //heroes = ai->getUnblockedHeroes(); + heroes = cb->getHeroesInfo(); + vstd::erase_if(heroes, [](const HeroPtr h) + { + if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer + return true; + + if(!ai->isAbleToExplore(h)) + return true; + + return !h->movement; //saves time, immobile heroes are useless anyway + }); + } + + //try to use buildings that uncover map + std::vector objs; + for(auto obj : ai->visitableObjs) + { + if(!vstd::contains(ai->alreadyVisited, obj)) + { + switch(obj->ID.num) + { + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + case Obj::CARTOGRAPHER: + objs.push_back(obj); + break; + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); + if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) + objs.push_back(obj); + break; + } + } + else + { + switch(obj->ID.num) + { + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) + break; + for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) + { + if(!cb->getObj(exit)) + { // Always attempt to visit two-way teleports if one of channel exits is not visible + objs.push_back(obj); + break; + } + } + break; + } + } + } + + auto primaryHero = ai->primaryHero().h; + for(auto h : heroes) + { + for(auto obj : objs) //double loop, performance risk? + { + auto waysToVisitObj = ai->ah->howToVisitObj(h, obj); + + vstd::concatenate(ret, waysToVisitObj); + } + + int3 t = whereToExplore(h); + if(t.valid()) + { + ret.push_back(sptr(VisitTile(t).sethero(h))); + } + else + { + //FIXME: possible issues when gathering army to break + if(hero.h == h || //exporation is assigned to this hero + (!hero && h == primaryHero)) //not assigned to specific hero, let our main hero do the job + { + t = ai->explorationDesperate(h); //check this only ONCE, high cost + if (t.valid()) //don't waste time if we are completely blocked + { + auto waysToVisitTile = ai->ah->howToVisitTile(h, t); + + vstd::concatenate(ret, waysToVisitTile); + continue; + } + } + ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore + } + } + //we either don't have hero yet or none of heroes can explore + if((!hero || ret.empty()) && ai->canRecruitAnyHero()) + ret.push_back(sptr(RecruitHero())); + + if(ret.empty()) + { + throw goalFulfilledException(sptr(Explore().sethero(hero))); + } + //throw cannotFulfillGoalException("Cannot explore - no possible ways found!"); + + return ret; +} + +bool Explore::fulfillsMe(TSubgoal goal) +{ + if(goal->goalType == EXPLORE) + { + if(goal->hero) + return hero == goal->hero; + else + return true; //cancel ALL exploration + } + return false; +} diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h new file mode 100644 index 000000000..66266a7bb --- /dev/null +++ b/AI/VCAI/Goals/Explore.h @@ -0,0 +1,40 @@ +/* +* Explore.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT Explore : public CGoal + { + public: + Explore() + : CGoal(Goals::EXPLORE) + { + priority = 1; + } + Explore(HeroPtr h) + : CGoal(Goals::EXPLORE) + { + hero = h; + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + bool fulfillsMe(TSubgoal goal) override; + virtual bool operator==(const Explore & other) const override; + }; +} diff --git a/AI/VCAI/Goals/FindObj.cpp b/AI/VCAI/Goals/FindObj.cpp new file mode 100644 index 000000000..4588ca4e5 --- /dev/null +++ b/AI/VCAI/Goals/FindObj.cpp @@ -0,0 +1,70 @@ +/* +* FindObj.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "FindObj.h" +#include "VisitObj.h" +#include "Explore.h" +#include "../VCAI.h" +#include "../AIUtility.h" + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool FindObj::operator==(const FindObj & other) const +{ + return other.hero.h == hero.h && other.objid == objid; +} + +TSubgoal FindObj::whatToDoToAchieve() +{ + const CGObjectInstance * o = nullptr; + if(resID > -1) //specified + { + for(const CGObjectInstance * obj : ai->visitableObjs) + { + if(obj->ID == objid && obj->subID == resID) + { + o = obj; + break; //TODO: consider multiple objects and choose best + } + } + } + else + { + for(const CGObjectInstance * obj : ai->visitableObjs) + { + if(obj->ID == objid) + { + o = obj; + break; //TODO: consider multiple objects and choose best + } + } + } + if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is + return sptr(VisitObj(o->id.getNum())); + else + return sptr(Explore()); +} + +bool FindObj::fulfillsMe(TSubgoal goal) +{ + if (goal->goalType == VISIT_TILE) //visiting tile visits object at same time + { + if (!hero || hero == goal->hero) + for (auto obj : cb->getVisitableObjs(goal->tile)) //check if any object on that tile matches criteria + if (obj->visitablePos() == goal->tile) //object could be removed + if (obj->ID == objid && obj->subID == resID) //same type and subtype + return true; + } + return false; +} diff --git a/AI/VCAI/Goals/FindObj.h b/AI/VCAI/Goals/FindObj.h new file mode 100644 index 000000000..fad944dec --- /dev/null +++ b/AI/VCAI/Goals/FindObj.h @@ -0,0 +1,47 @@ +/* +* FindObj.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT FindObj : public CGoal + { + public: + FindObj() {} // empty constructor not allowed + + FindObj(int ID) + : CGoal(Goals::FIND_OBJ) + { + objid = ID; + resID = -1; //subid unspecified + priority = 1; + } + FindObj(int ID, int subID) + : CGoal(Goals::FIND_OBJ) + { + objid = ID; + resID = subID; + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + bool fulfillsMe(TSubgoal goal) override; + virtual bool operator==(const FindObj & other) const override; + }; +} \ No newline at end of file diff --git a/AI/VCAI/Goals/GatherArmy.cpp b/AI/VCAI/Goals/GatherArmy.cpp new file mode 100644 index 000000000..5eb9f7a27 --- /dev/null +++ b/AI/VCAI/Goals/GatherArmy.cpp @@ -0,0 +1,196 @@ +/* +* GatherArmy.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool GatherArmy::operator==(const GatherArmy & other) const +{ + return other.hero.h == hero.h || town == other.town; +} + +std::string GatherArmy::completeMessage() const +{ + return "Hero " + hero.get()->name + " gathered army of value " + boost::lexical_cast(value); +} + +TSubgoal GatherArmy::whatToDoToAchieve() +{ + //TODO: find hero if none set + assert(hero.h); + + return fh->chooseSolution(getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing. +} + +static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, + BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7}; + +TGoalVec GatherArmy::getAllPossibleSubgoals() +{ + //get all possible towns, heroes and dwellings we may use + TGoalVec ret; + + if(!hero.validAndSet()) + { + return ret; + } + + //TODO: include evaluation of monsters gather in calculation + for(auto t : cb->getTownsInfo()) + { + auto pos = t->visitablePos(); + if(ai->isAccessibleForHero(pos, hero)) + { + //grab army from town + if(!t->visitingHero && howManyReinforcementsCanGet(hero, t)) + { + if(!vstd::contains(ai->townVisitsThisWeek[hero], t)) + ret.push_back(sptr(VisitTile(pos).sethero(hero))); + } + //buy army in town + if (!t->visitingHero || t->visitingHero == hero.get(true)) + { + ui32 val = std::min(value, howManyReinforcementsCanBuy(hero, t)); + if (val) + { + auto goal = sptr(BuyArmy(t, val).sethero(hero)); + if(!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice + ret.push_back(goal); + else + logAi->debug("Can not buy army, because of ai->ah->containsObjective"); + } + } + //build dwelling + //TODO: plan building over multiple turns? + //auto bid = ah->canBuildAnyStructure(t, std::vector(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)); + + //Do not use below code for now, rely on generic Build. Code below needs to know a lot of town/resource context to do more good than harm + /*auto bid = ai->ah->canBuildAnyStructure(t, std::vector(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 1); + if (bid.is_initialized()) + { + auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority)); + if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice + ret.push_back(goal); + else + logAi->debug("Can not build a structure, because of ai->ah->containsObjective"); + }*/ + } + } + + auto otherHeroes = cb->getHeroesInfo(); + auto heroDummy = hero; + vstd::erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h) + { + if(h == heroDummy.h) + return true; + else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true)) + return true; + else if(!ai->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue + return true; + else if(ai->getGoal(h)->goalType == GATHER_ARMY) + return true; + 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))); + } + + std::vector objs; + for(auto obj : ai->visitableObjs) + { + if(obj->ID == Obj::CREATURE_GENERATOR1) + { + auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID); + + //Use flagged dwellings only when there are available creatures that we can afford + if(relationToOwner == PlayerRelations::SAME_PLAYER) + { + auto dwelling = dynamic_cast(obj); + + ui32 val = std::min(value, howManyReinforcementsCanBuy(hero, dwelling)); + + if(val) + { + for(auto & creLevel : dwelling->creatures) + { + if(creLevel.first) + { + for(auto & creatureID : creLevel.second) + { + auto creature = VLC->creh->creatures[creatureID]; + if(ai->ah->freeResources().canAfford(creature->cost)) + objs.push_back(obj); //TODO: reserve resources? + } + } + } + } + } + } + } + for(auto h : cb->getHeroesInfo()) + { + for(auto obj : objs) + { + //find safe dwelling + if(ai->isGoodForVisit(obj, h)) + { + vstd::concatenate(ret, ai->ah->howToVisitObj(h, obj)); + } + } + } + + if(ai->canRecruitAnyHero() && ai->ah->freeGold() > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game + { + if(auto t = ai->findTownWithTavern()) + { + for(auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes + { + if(h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy + { + ret.push_back(sptr(RecruitHero())); + break; + } + } + } + } + + if(ret.empty()) + { + if(hero == ai->primaryHero()) + ret.push_back(sptr(Explore())); + else + throw cannotFulfillGoalException("No ways to gather army"); + } + + return ret; +} diff --git a/AI/VCAI/Goals/GatherArmy.h b/AI/VCAI/Goals/GatherArmy.h new file mode 100644 index 000000000..97108df76 --- /dev/null +++ b/AI/VCAI/Goals/GatherArmy.h @@ -0,0 +1,38 @@ +/* +* GatherArmy.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT GatherArmy : public CGoal + { + public: + GatherArmy() + : CGoal(Goals::GATHER_ARMY) + { + } + GatherArmy(int val) + : CGoal(Goals::GATHER_ARMY) + { + value = val; + priority = 2.5; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + virtual bool operator==(const GatherArmy & other) const override; + }; +} diff --git a/AI/VCAI/Goals/GatherTroops.cpp b/AI/VCAI/Goals/GatherTroops.cpp new file mode 100644 index 000000000..580a7af86 --- /dev/null +++ b/AI/VCAI/Goals/GatherTroops.cpp @@ -0,0 +1,139 @@ +/* +* GatherTroops.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool GatherTroops::operator==(const GatherTroops & other) const +{ + return objid == other.objid; +} + +int GatherTroops::getCreaturesCount(const CArmedInstance * army) +{ + int count = 0; + + for(auto stack : army->Slots()) + { + if(objid == stack.second->getCreatureID().num) + { + count += stack.second->count; + } + } + + return count; +} + +TSubgoal GatherTroops::whatToDoToAchieve() +{ + auto heroes = cb->getHeroesInfo(true); + + for(auto hero : heroes) + { + if(getCreaturesCount(hero) >= this->value) + { + logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name); + + throw goalFulfilledException(sptr(*this)); + } + } + + TGoalVec solutions = getAllPossibleSubgoals(); + + if(solutions.empty()) + return sptr(Explore()); + + return fh->chooseSolution(solutions); +} + + +TGoalVec GatherTroops::getAllPossibleSubgoals() +{ + TGoalVec solutions; + + for(const CGTownInstance * t : cb->getTownsInfo()) + { + int count = getCreaturesCount(t->getUpperArmy()); + + if(count >= this->value) + { + vstd::concatenate(solutions, ai->ah->howToVisitObj(t)); + continue; + } + + auto creature = VLC->creh->creatures[objid]; + if(t->subID == creature->faction) //TODO: how to force AI to build unupgraded creatures? :O + { + auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1); + if(!creatures) + continue; + + int upgradeNumber = vstd::find_pos(*creatures, creature->idNumber); + if(upgradeNumber < 0) + continue; + + BuildingID bid(BuildingID::DWELL_FIRST + creature->level - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN); + if(t->hasBuilt(bid)) //this assumes only creatures with dwellings are assigned to faction + { + solutions.push_back(sptr(BuyArmy(t, creature->AIValue * this->value).setobjid(objid))); + } + /*else //disable random building requests for now - this code needs to know a lot of town/resource context to do more good than harm + { + return sptr(BuildThis(bid, t).setpriority(priority)); + }*/ + } + } + for(auto obj : ai->visitableObjs) + { + auto d = dynamic_cast(obj); + + if(!d || obj->ID == Obj::TOWN) + continue; + + for(auto creature : d->creatures) + { + if(creature.first) //there are more than 0 creatures avaliabe + { + for(auto type : creature.second) + { + if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->creatures[type]->cost)) + vstd::concatenate(solutions, ai->ah->howToVisitObj(obj)); + } + } + } + } + + return solutions; + //TODO: exchange troops between heroes +} + +bool GatherTroops::fulfillsMe(TSubgoal goal) +{ + if (!hero || hero == goal->hero) //we got army for desired hero or any hero + if (goal->objid == objid) //same creature type //TODO: consider upgrades? + if (goal->value >= value) //notify every time we get resources? + return true; + return false; +} diff --git a/AI/VCAI/Goals/GatherTroops.h b/AI/VCAI/Goals/GatherTroops.h new file mode 100644 index 000000000..ff93ca186 --- /dev/null +++ b/AI/VCAI/Goals/GatherTroops.h @@ -0,0 +1,43 @@ +/* +* GatherTroops.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT GatherTroops : public CGoal + { + public: + GatherTroops() + : CGoal(Goals::GATHER_TROOPS) + { + priority = 2; + } + GatherTroops(int type, int val) + : CGoal(Goals::GATHER_TROOPS) + { + objid = type; + value = val; + priority = 2; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + bool fulfillsMe(TSubgoal goal) override; + virtual bool operator==(const GatherTroops & other) const override; + + private: + int getCreaturesCount(const CArmedInstance * army); + }; +} diff --git a/AI/VCAI/Goals/GetArtOfType.cpp b/AI/VCAI/Goals/GetArtOfType.cpp new file mode 100644 index 000000000..5195796ec --- /dev/null +++ b/AI/VCAI/Goals/GetArtOfType.cpp @@ -0,0 +1,31 @@ +/* +* GetArtOfType.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "GetArtOfType.h" +#include "FindObj.h" +#include "../VCAI.h" +#include "../AIUtility.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool GetArtOfType::operator==(const GetArtOfType & other) const +{ + return other.hero.h == hero.h && other.objid == objid; +} + +TSubgoal GetArtOfType::whatToDoToAchieve() +{ + return sptr(FindObj(Obj::ARTIFACT, aid)); +} \ No newline at end of file diff --git a/AI/VCAI/Goals/GetArtOfType.h b/AI/VCAI/Goals/GetArtOfType.h new file mode 100644 index 000000000..ba31c2a56 --- /dev/null +++ b/AI/VCAI/Goals/GetArtOfType.h @@ -0,0 +1,40 @@ +/* +* GetArtOfType.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT GetArtOfType : public CGoal + { + public: + GetArtOfType() + : CGoal(Goals::GET_ART_TYPE) + { + } + GetArtOfType(int type) + : CGoal(Goals::GET_ART_TYPE) + { + aid = type; + priority = 2; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + virtual bool operator==(const GetArtOfType & other) const override; + }; +} diff --git a/AI/VCAI/Goals/Goals.h b/AI/VCAI/Goals/Goals.h new file mode 100644 index 000000000..7e1f32909 --- /dev/null +++ b/AI/VCAI/Goals/Goals.h @@ -0,0 +1,32 @@ +/* +* Goals.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" +#include "Invalid.h" +#include "BuildBoat.h" +#include "Build.h" +#include "BuildThis.h" +#include "Conquer.h" +#include "GatherArmy.h" +#include "Win.h" +#include "VisitObj.h" +#include "VisitTile.h" +#include "VisitHero.h" +#include "Explore.h" +#include "BuyArmy.h" +#include "GatherTroops.h" +#include "Trade.h" +#include "CollectRes.h" +#include "RecruitHero.h" +#include "GetArtOfType.h" +#include "ClearWayTo.h" +#include "DigAtTile.h" +#include "FindObj.h" \ No newline at end of file diff --git a/AI/VCAI/Goals/Invalid.h b/AI/VCAI/Goals/Invalid.h new file mode 100644 index 000000000..cd77ac929 --- /dev/null +++ b/AI/VCAI/Goals/Invalid.h @@ -0,0 +1,41 @@ +/* +* Invalid.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; + +namespace Goals +{ + class DLL_EXPORT Invalid : public CGoal + { + public: + Invalid() + : CGoal(Goals::INVALID) + { + priority = -1e10; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override + { + return iAmElementar(); + } + + virtual bool operator==(const Invalid & other) const override + { + return true; + } + }; +} diff --git a/AI/VCAI/Goals/RecruitHero.cpp b/AI/VCAI/Goals/RecruitHero.cpp new file mode 100644 index 000000000..aff943bc6 --- /dev/null +++ b/AI/VCAI/Goals/RecruitHero.cpp @@ -0,0 +1,38 @@ +/* +* RecruitHero.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +TSubgoal RecruitHero::whatToDoToAchieve() +{ + const CGTownInstance * t = ai->findTownWithTavern(); + if(!t) + return sptr(BuildThis(BuildingID::TAVERN).setpriority(2)); + + TResources res; + res[Res::GOLD] = GameConstants::HERO_GOLD_COST; + return ai->ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res +} diff --git a/AI/VCAI/Goals/RecruitHero.h b/AI/VCAI/Goals/RecruitHero.h new file mode 100644 index 000000000..c381eb2ad --- /dev/null +++ b/AI/VCAI/Goals/RecruitHero.h @@ -0,0 +1,41 @@ +/* +* RecruitHero.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT RecruitHero : public CGoal + { + public: + RecruitHero() + : CGoal(Goals::RECRUIT_HERO) + { + priority = 1; + } + + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + + TSubgoal whatToDoToAchieve() override; + + virtual bool operator==(const RecruitHero & other) const override + { + return true; + } + }; +} diff --git a/AI/VCAI/Goals/Trade.cpp b/AI/VCAI/Goals/Trade.cpp new file mode 100644 index 000000000..c4fa8f05f --- /dev/null +++ b/AI/VCAI/Goals/Trade.cpp @@ -0,0 +1,23 @@ +/* +* Trade.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Trade.h" + +using namespace Goals; + +bool Trade::operator==(const Trade & other) const +{ + return resID == other.resID; +} + +TSubgoal Trade::whatToDoToAchieve() +{ + return iAmElementar(); +} diff --git a/AI/VCAI/Goals/Trade.h b/AI/VCAI/Goals/Trade.h new file mode 100644 index 000000000..c8da46fc2 --- /dev/null +++ b/AI/VCAI/Goals/Trade.h @@ -0,0 +1,38 @@ +/* +* Trade.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT Trade : public CGoal + { + public: + Trade() + : CGoal(Goals::TRADE) + { + } + Trade(int rid, int val, int Objid) + : CGoal(Goals::TRADE) + { + resID = rid; + value = val; + objid = Objid; + priority = 3; //trading is instant, but picking resources is free + } + TSubgoal whatToDoToAchieve() override; + virtual bool operator==(const Trade & other) const override; + }; +} diff --git a/AI/VCAI/Goals/VisitHero.cpp b/AI/VCAI/Goals/VisitHero.cpp new file mode 100644 index 000000000..701ac0d71 --- /dev/null +++ b/AI/VCAI/Goals/VisitHero.cpp @@ -0,0 +1,74 @@ +/* +* VisitHero.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "VisitHero.h" +#include "Explore.h" +#include "Invalid.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool VisitHero::operator==(const VisitHero & other) const +{ + return other.hero.h == hero.h && other.objid == objid; +} + +std::string VisitHero::completeMessage() const +{ + return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast(objid); +} + +TSubgoal VisitHero::whatToDoToAchieve() +{ + const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(objid)); + if(!obj) + return sptr(Explore()); + int3 pos = obj->visitablePos(); + + if(hero && ai->isAccessibleForHero(pos, hero, true) && isSafeToVisit(hero, pos)) //enemy heroes can get reinforcements + { + if(hero->pos == pos) + logAi->error("Hero %s tries to visit himself.", hero.name); + else + { + //can't use VISIT_TILE here as tile appears blocked by target hero + //FIXME: elementar goal should not be abstract + return sptr(VisitHero(objid).sethero(hero).settile(pos).setisElementar(true)); + } + } + return sptr(Invalid()); +} + +bool VisitHero::fulfillsMe(TSubgoal goal) +{ + //TODO: VisitObj shoudl not be used for heroes, but... + if(goal->goalType == VISIT_TILE) + { + auto obj = cb->getObj(ObjectInstanceID(objid)); + if (!obj) + { + logAi->error("Hero %s: VisitHero::fulfillsMe at %s: object %d not found", hero.name, goal->tile.toString(), objid); + return false; + } + return obj->visitablePos() == goal->tile; + } + return false; +} diff --git a/AI/VCAI/Goals/VisitHero.h b/AI/VCAI/Goals/VisitHero.h new file mode 100644 index 000000000..c209a0abf --- /dev/null +++ b/AI/VCAI/Goals/VisitHero.h @@ -0,0 +1,42 @@ +/* +* VisitHero.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT VisitHero : public CGoal + { + public: + VisitHero() + : CGoal(Goals::VISIT_HERO) + { + } + VisitHero(int hid) + : CGoal(Goals::VISIT_HERO) + { + objid = hid; + priority = 4; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + bool fulfillsMe(TSubgoal goal) override; + std::string completeMessage() const override; + virtual bool operator==(const VisitHero & other) const override; + }; +} diff --git a/AI/VCAI/Goals/VisitObj.cpp b/AI/VCAI/Goals/VisitObj.cpp new file mode 100644 index 000000000..33e69325b --- /dev/null +++ b/AI/VCAI/Goals/VisitObj.cpp @@ -0,0 +1,112 @@ +/* +* VisitObj.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool VisitObj::operator==(const VisitObj & other) const +{ + return other.hero.h == hero.h && other.objid == objid; +} + +std::string VisitObj::completeMessage() const +{ + return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast(objid); +} + +TGoalVec VisitObj::getAllPossibleSubgoals() +{ + TGoalVec goalList; + const CGObjectInstance * obj = cb->getObjInstance(ObjectInstanceID(objid)); + if(!obj) + { + throw cannotFulfillGoalException("Object is missing - goal is invalid now!"); + } + + int3 pos = obj->visitablePos(); + if(hero) + { + if(ai->isAccessibleForHero(pos, hero)) + { + if(isSafeToVisit(hero, pos)) + goalList.push_back(sptr(VisitObj(obj->id.getNum()).sethero(hero))); + else + goalList.push_back(sptr(GatherArmy(evaluateDanger(pos, hero.h) * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true))); + + return goalList; + } + } + else + { + for(auto potentialVisitor : cb->getHeroesInfo()) + { + if(ai->isAccessibleForHero(pos, potentialVisitor)) + { + if(isSafeToVisit(potentialVisitor, pos)) + goalList.push_back(sptr(VisitObj(obj->id.getNum()).sethero(potentialVisitor))); + else + goalList.push_back(sptr(GatherArmy(evaluateDanger(pos, potentialVisitor) * SAFE_ATTACK_CONSTANT).sethero(potentialVisitor).setisAbstract(true))); + } + } + if(!goalList.empty()) + { + return goalList; + } + } + + goalList.push_back(sptr(ClearWayTo(pos))); + return goalList; +} + +TSubgoal VisitObj::whatToDoToAchieve() +{ + auto bestGoal = fh->chooseSolution(getAllPossibleSubgoals()); + + if(bestGoal->goalType == VISIT_OBJ && bestGoal->hero) + bestGoal->setisElementar(true); + + return bestGoal; +} + +VisitObj::VisitObj(int Objid) : CGoal(VISIT_OBJ) +{ + objid = Objid; + tile = ai->myCb->getObjInstance(ObjectInstanceID(objid))->visitablePos(); + priority = 3; +} + +bool VisitObj::fulfillsMe(TSubgoal goal) +{ + if(goal->goalType == VISIT_TILE) + { + if (!hero || hero == goal->hero) + { + auto obj = cb->getObjInstance(ObjectInstanceID(objid)); + if (obj && obj->visitablePos() == goal->tile) //object could be removed + return true; + } + } + return false; +} diff --git a/AI/VCAI/Goals/VisitObj.h b/AI/VCAI/Goals/VisitObj.h new file mode 100644 index 000000000..666a9acf0 --- /dev/null +++ b/AI/VCAI/Goals/VisitObj.h @@ -0,0 +1,32 @@ +/* +* VisitObj.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT VisitObj : public CGoal //this goal was previously known as GetObj + { + public: + VisitObj() = delete; // empty constructor not allowed + VisitObj(int Objid); + + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + bool fulfillsMe(TSubgoal goal) override; + std::string completeMessage() const override; + virtual bool operator==(const VisitObj & other) const override; + }; +} diff --git a/AI/VCAI/Goals/VisitTile.cpp b/AI/VCAI/Goals/VisitTile.cpp new file mode 100644 index 000000000..ffac99195 --- /dev/null +++ b/AI/VCAI/Goals/VisitTile.cpp @@ -0,0 +1,98 @@ +/* +* VisitTile.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +bool VisitTile::operator==(const VisitTile & other) const +{ + return other.hero.h == hero.h && other.tile == tile; +} + +std::string VisitTile::completeMessage() const +{ + return "Hero " + hero.get()->name + " visited tile " + tile.toString(); +} + +TSubgoal VisitTile::whatToDoToAchieve() +{ + auto ret = fh->chooseSolution(getAllPossibleSubgoals()); + + if(ret->hero) + { + if(isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero)) + { + ret->setisElementar(true); + return ret; + } + else + { + return sptr(GatherArmy(evaluateDanger(tile, *ret->hero) * SAFE_ATTACK_CONSTANT) + .sethero(ret->hero).setisAbstract(true)); + } + } + return ret; +} + +TGoalVec VisitTile::getAllPossibleSubgoals() +{ + assert(cb->isInTheMap(tile)); + + TGoalVec ret; + if(!cb->isVisible(tile)) + ret.push_back(sptr(Explore())); //what sense does it make? + else + { + std::vector heroes; + if(hero) + heroes.push_back(hero.h); //use assigned hero if any + else + heroes = cb->getHeroesInfo(); //use most convenient hero + + for(auto h : heroes) + { + if(ai->isAccessibleForHero(tile, h)) + ret.push_back(sptr(VisitTile(tile).sethero(h))); + } + if(ai->canRecruitAnyHero()) + ret.push_back(sptr(RecruitHero())); + } + if(ret.empty()) + { + auto obj = vstd::frontOrNull(cb->getVisitableObjs(tile)); + if(obj && obj->ID == Obj::HERO && obj->tempOwner == ai->playerID) //our own hero stands on that tile + { + if(hero.get(true) && hero->id == obj->id) //if it's assigned hero, visit tile. If it's different hero, we can't visit tile now + ret.push_back(sptr(VisitTile(tile).sethero(dynamic_cast(obj)).setisElementar(true))); + else + throw cannotFulfillGoalException("Tile is already occupied by another hero "); //FIXME: we should give up this tile earlier + } + else + ret.push_back(sptr(ClearWayTo(tile))); + } + + //important - at least one sub-goal must handle case which is impossible to fulfill (unreachable tile) + return ret; +} diff --git a/AI/VCAI/Goals/VisitTile.h b/AI/VCAI/Goals/VisitTile.h new file mode 100644 index 000000000..334b44c32 --- /dev/null +++ b/AI/VCAI/Goals/VisitTile.h @@ -0,0 +1,37 @@ +/* +* VisitTile.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT VisitTile : public CGoal + //tile, in conjunction with hero elementar; assumes tile is reachable + { + public: + VisitTile() {} // empty constructor not allowed + + VisitTile(int3 Tile) + : CGoal(Goals::VISIT_TILE) + { + tile = Tile; + priority = 5; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + virtual bool operator==(const VisitTile & other) const override; + }; +} diff --git a/AI/VCAI/Goals/Win.cpp b/AI/VCAI/Goals/Win.cpp new file mode 100644 index 000000000..9c0c268e8 --- /dev/null +++ b/AI/VCAI/Goals/Win.cpp @@ -0,0 +1,191 @@ +/* +* Win.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/mapping/CMap.h" //for victory conditions +#include "../../../lib/CPathfinder.h" +#include "../../../lib/StringConstants.h" + + +extern boost::thread_specific_ptr cb; +extern boost::thread_specific_ptr ai; +extern FuzzyHelper * fh; + +using namespace Goals; + +TSubgoal Win::whatToDoToAchieve() +{ + auto toBool = [=](const EventCondition &) + { + // TODO: proper implementation + // Right now even already fulfilled goals will be included into generated list + // Proper check should test if event condition is already fulfilled + // Easiest way to do this is to call CGameState::checkForVictory but this function should not be + // used on client side or in AI code + return false; + }; + + std::vector goals; + + for(const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents) + { + //TODO: try to eliminate human player(s) using loss conditions that have isHuman element + + if(event.effect.type == EventEffect::VICTORY) + { + boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals)); + } + } + + //TODO: instead of returning first encountered goal AI should generate list of possible subgoals + for(const EventCondition & goal : goals) + { + switch(goal.condition) + { + case EventCondition::HAVE_ARTIFACT: + return sptr(GetArtOfType(goal.objectType)); + case EventCondition::DESTROY: + { + if(goal.object) + { + auto obj = cb->getObj(goal.object->id); + if(obj) + if(obj->getOwner() == ai->playerID) //we can't capture our own object + return sptr(Conquer()); + + + return sptr(VisitObj(goal.object->id.getNum())); + } + else + { + // TODO: destroy all objects of type goal.objectType + // This situation represents "kill all creatures" condition from H3 + break; + } + } + case EventCondition::HAVE_BUILDING: + { + // TODO build other buildings apart from Grail + // goal.objectType = buidingID to build + // goal.object = optional, town in which building should be built + // Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions) + + if(goal.objectType == BuildingID::GRAIL) + { + if(auto h = ai->getHeroWithGrail()) + { + //hero is in a town that can host Grail + if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL)) + { + const CGTownInstance * t = h->visitedTown; + return sptr(BuildThis(BuildingID::GRAIL, t).setpriority(10)); + } + else + { + auto towns = cb->getTownsInfo(); + towns.erase(boost::remove_if(towns, + [](const CGTownInstance * t) -> bool + { + return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL); + }), + towns.end()); + boost::sort(towns, CDistanceSorter(h.get())); + if(towns.size()) + { + return sptr(VisitTile(towns.front()->visitablePos()).sethero(h)); + } + } + } + double ratio = 0; + // maybe make this check a bit more complex? For example: + // 0.75 -> dig randomly within 3 tiles radius + // 0.85 -> radius now 2 tiles + // 0.95 -> 1 tile radius, position is fully known + // AFAIK H3 AI does something like this + int3 grailPos = cb->getGrailPos(&ratio); + if(ratio > 0.99) + { + return sptr(DigAtTile(grailPos)); + } //TODO: use FIND_OBJ + else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID)) //there are unvisited Obelisks + return sptr(VisitObj(obj->id.getNum())); + else + return sptr(Explore()); + } + break; + } + case EventCondition::CONTROL: + { + if(goal.object) + { + auto objRelations = cb->getPlayerRelations(ai->playerID, goal.object->tempOwner); + + if(objRelations == PlayerRelations::ENEMIES) + { + return sptr(VisitObj(goal.object->id.getNum())); + } + else + { + // TODO: Defance + break; + } + } + else + { + //TODO: control all objects of type "goal.objectType" + // Represents H3 condition "Flag all mines" + break; + } + } + + case EventCondition::HAVE_RESOURCES: + //TODO mines? piles? marketplace? + //save? + return sptr(CollectRes(static_cast(goal.objectType), goal.value)); + case EventCondition::HAVE_CREATURES: + return sptr(GatherTroops(goal.objectType, goal.value)); + case EventCondition::TRANSPORT: + { + //TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it + // Represents "transport artifact" condition: + // goal.objectType = type of artifact + // goal.object = destination-town where artifact should be transported + break; + } + case EventCondition::STANDARD_WIN: + return sptr(Conquer()); + + // Conditions that likely don't need any implementation + case EventCondition::DAYS_PASSED: + break; // goal.value = number of days for condition to trigger + case EventCondition::DAYS_WITHOUT_TOWN: + break; // goal.value = number of days to trigger this + case EventCondition::IS_HUMAN: + break; // Should be only used in calculation of candidates (see toBool lambda) + case EventCondition::CONST_VALUE: + break; + + case EventCondition::HAVE_0: + case EventCondition::HAVE_BUILDING_0: + case EventCondition::DESTROY_0: + //TODO: support new condition format + return sptr(Conquer()); + default: + assert(0); + } + } + return sptr(Invalid()); +} diff --git a/AI/VCAI/Goals/Win.h b/AI/VCAI/Goals/Win.h new file mode 100644 index 000000000..07dfcdee5 --- /dev/null +++ b/AI/VCAI/Goals/Win.h @@ -0,0 +1,39 @@ +/* +* Win.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + class DLL_EXPORT Win : public CGoal + { + public: + Win() + : CGoal(Goals::WIN) + { + priority = 100; + } + TGoalVec getAllPossibleSubgoals() override + { + return TGoalVec(); + } + TSubgoal whatToDoToAchieve() override; + + virtual bool operator==(const Win & other) const override + { + return true; + } + }; +} diff --git a/AI/VCAI/Pathfinding/AINodeStorage.cpp b/AI/VCAI/Pathfinding/AINodeStorage.cpp index 6a77dd14d..c23820841 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.cpp +++ b/AI/VCAI/Pathfinding/AINodeStorage.cpp @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* AINodeStorage.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/AI/VCAI/Pathfinding/AINodeStorage.h b/AI/VCAI/Pathfinding/AINodeStorage.h index 2405abc9a..2d72cdb19 100644 --- a/AI/VCAI/Pathfinding/AINodeStorage.h +++ b/AI/VCAI/Pathfinding/AINodeStorage.h @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* AINodeStorage.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -13,7 +13,7 @@ #include "../../../lib/CPathfinder.h" #include "../../../lib/mapObjects/CGHeroInstance.h" #include "../AIUtility.h" -#include "../Goals.h" +#include "../Goals/AbstractGoal.h" class ISpecialAction { diff --git a/AI/VCAI/Pathfinding/AIPathfinder.cpp b/AI/VCAI/Pathfinding/AIPathfinder.cpp index 7f3254504..506a689b7 100644 --- a/AI/VCAI/Pathfinding/AIPathfinder.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinder.cpp @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* AIPathfinder.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -67,4 +67,4 @@ std::vector AIPathfinder::getPathInfo(HeroPtr hero, int3 tile) } return nodeStorage->getChainInfo(tile, !tileInfo->isWater()); -} \ No newline at end of file +} diff --git a/AI/VCAI/Pathfinding/AIPathfinder.h b/AI/VCAI/Pathfinding/AIPathfinder.h index 971c0790f..e33e3c1db 100644 --- a/AI/VCAI/Pathfinding/AIPathfinder.h +++ b/AI/VCAI/Pathfinding/AIPathfinder.h @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* AIPathfinder.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp index 3c678dc88..eb57e2148 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.cpp @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* AIPathfinderConfig.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "AIPathfinderConfig.h" +#include "../Goals/Goals.h" #include "../../../CCallback.h" #include "../../../lib/mapping/CMap.h" #include "../../../lib/mapObjects/MapObjects.h" diff --git a/AI/VCAI/Pathfinding/AIPathfinderConfig.h b/AI/VCAI/Pathfinding/AIPathfinderConfig.h index 22ecd1d67..e57fc9954 100644 --- a/AI/VCAI/Pathfinding/AIPathfinderConfig.h +++ b/AI/VCAI/Pathfinding/AIPathfinderConfig.h @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* AIPathfinderConfig.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -23,4 +23,4 @@ namespace AIPathfinding VCAI * ai, std::shared_ptr nodeStorage); }; -} \ No newline at end of file +} diff --git a/AI/VCAI/Pathfinding/PathfindingManager.cpp b/AI/VCAI/Pathfinding/PathfindingManager.cpp index 825112b39..552fc2439 100644 --- a/AI/VCAI/Pathfinding/PathfindingManager.cpp +++ b/AI/VCAI/Pathfinding/PathfindingManager.cpp @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* PathfindingManager.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,6 +11,7 @@ #include "PathfindingManager.h" #include "AIPathfinder.h" #include "AIPathfinderConfig.h" +#include "Goals/Goals.h" #include "../../../lib/CGameInfoCallback.h" #include "../../../lib/mapping/CMap.h" @@ -227,4 +228,4 @@ void PathfindingManager::resetPaths() { logAi->debug("AIPathfinder has been reseted."); pathfinder->clear(); -} \ No newline at end of file +} diff --git a/AI/VCAI/Pathfinding/PathfindingManager.h b/AI/VCAI/Pathfinding/PathfindingManager.h index ea80f7bbc..975447776 100644 --- a/AI/VCAI/Pathfinding/PathfindingManager.h +++ b/AI/VCAI/Pathfinding/PathfindingManager.h @@ -1,5 +1,5 @@ /* -* AIhelper.h, part of VCMI engine +* PathfindingManager.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/AI/VCAI/ResourceManager.cpp b/AI/VCAI/ResourceManager.cpp index cdf1f1acb..918956ef3 100644 --- a/AI/VCAI/ResourceManager.cpp +++ b/AI/VCAI/ResourceManager.cpp @@ -9,6 +9,7 @@ */ #include "StdInc.h" #include "ResourceManager.h" +#include "Goals/Goals.h" #include "../../CCallback.h" #include "../../lib/mapObjects/MapObjects.h" diff --git a/AI/VCAI/ResourceManager.h b/AI/VCAI/ResourceManager.h index ef9657695..5ec08433c 100644 --- a/AI/VCAI/ResourceManager.h +++ b/AI/VCAI/ResourceManager.h @@ -10,7 +10,6 @@ #pragma once #include "AIUtility.h" -#include "Goals.h" #include "../../lib/GameConstants.h" #include "../../lib/VCMI_Lib.h" #include "VCAI.h" diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 54a6f6c0f..10a679d86 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -12,6 +12,7 @@ #include "FuzzyHelper.h" #include "ResourceManager.h" #include "BuildingManager.h" +#include "Goals/Goals.h" #include "../../lib/UnlockGuard.h" #include "../../lib/mapObjects/MapObjects.h" diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 9bb388217..2e7369fdf 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -10,8 +10,7 @@ #pragma once #include "AIUtility.h" -#include "SectorMap.h" -#include "Goals.h" +#include "Goals/AbstractGoal.h" #include "../../lib/AI_Base.h" #include "../../CCallback.h"