1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Nullkiller: initial cleanup of unused code

This commit is contained in:
Andrii Danylchenko 2019-12-15 15:47:21 +02:00 committed by Andrii Danylchenko
parent 5344df51c6
commit af0dcf97c4
67 changed files with 378 additions and 2913 deletions

View File

@ -13,8 +13,6 @@
AIhelper::AIhelper()
{
resourceManager.reset(new ResourceManager());
buildingManager.reset(new BuildingManager());
pathfindingManager.reset(new PathfindingManager());
armyManager.reset(new ArmyManager());
heroManager.reset(new HeroManager());
@ -24,15 +22,8 @@ AIhelper::~AIhelper()
{
}
bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
{
return resourceManager->notifyGoalCompleted(goal);
}
void AIhelper::init(CPlayerSpecificInfoCallback * CB)
{
resourceManager->init(CB);
buildingManager->init(CB);
pathfindingManager->init(CB);
armyManager->init(CB);
heroManager->init(CB);
@ -40,8 +31,6 @@ void AIhelper::init(CPlayerSpecificInfoCallback * CB)
void AIhelper::setAI(VCAI * AI)
{
resourceManager->setAI(AI);
buildingManager->setAI(AI);
pathfindingManager->setAI(AI);
armyManager->setAI(AI);
heroManager->setAI(AI);
@ -53,106 +42,6 @@ void AIhelper::update()
heroManager->update();
}
bool AIhelper::getBuildingOptions(const CGTownInstance * t)
{
return buildingManager->getBuildingOptions(t);
}
BuildingID AIhelper::getMaxPossibleGoldBuilding(const CGTownInstance * t)
{
return buildingManager->getMaxPossibleGoldBuilding(t);
}
boost::optional<PotentialBuilding> AIhelper::immediateBuilding() const
{
return buildingManager->immediateBuilding();
}
boost::optional<PotentialBuilding> AIhelper::expensiveBuilding() const
{
return buildingManager->expensiveBuilding();
}
boost::optional<BuildingID> AIhelper::canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const
{
return buildingManager->canBuildAnyStructure(t, buildList, maxDays);
}
Goals::TSubgoal AIhelper::whatToDo(TResources & res, Goals::TSubgoal goal)
{
return resourceManager->whatToDo(res, goal);
}
Goals::TSubgoal AIhelper::whatToDo() const
{
return resourceManager->whatToDo();
}
bool AIhelper::containsObjective(Goals::TSubgoal goal) const
{
return resourceManager->containsObjective(goal);
}
bool AIhelper::hasTasksLeft() const
{
return resourceManager->hasTasksLeft();
}
bool AIhelper::removeOutdatedObjectives(std::function<bool(const Goals::TSubgoal&)> predicate)
{
return resourceManager->removeOutdatedObjectives(predicate);
}
bool AIhelper::canAfford(const TResources & cost) const
{
return resourceManager->canAfford(cost);
}
TResources AIhelper::reservedResources() const
{
return resourceManager->reservedResources();
}
TResources AIhelper::freeResources() const
{
return resourceManager->freeResources();
}
TResource AIhelper::freeGold() const
{
return resourceManager->freeGold();
}
TResources AIhelper::allResources() const
{
return resourceManager->allResources();
}
TResource AIhelper::allGold() const
{
return resourceManager->allGold();
}
Goals::TGoalVec AIhelper::howToVisitTile(const int3 & tile, bool allowGatherArmy) const
{
return pathfindingManager->howToVisitTile(tile, allowGatherArmy);
}
Goals::TGoalVec AIhelper::howToVisitObj(ObjectIdRef obj, bool allowGatherArmy) const
{
return pathfindingManager->howToVisitObj(obj, allowGatherArmy);
}
Goals::TGoalVec AIhelper::howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy) const
{
return pathfindingManager->howToVisitTile(hero, tile, allowGatherArmy);
}
Goals::TGoalVec AIhelper::howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy) const
{
return pathfindingManager->howToVisitObj(hero, obj, allowGatherArmy);
}
std::vector<AIPath> AIhelper::getPathsToTile(const HeroPtr & hero, const int3 & tile) const
{
return pathfindingManager->getPathsToTile(hero, tile);

View File

@ -14,8 +14,6 @@
!!! Note: Include THIS file at the end of include list to avoid "undefined base class" error
*/
#include "ResourceManager.h"
#include "BuildingManager.h"
#include "ArmyManager.h"
#include "HeroManager.h"
#include "Pathfinding/PathfindingManager.h"
@ -24,14 +22,11 @@ class ResourceManager;
class BuildingManager;
//indirection interface for various modules
class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager, public IArmyManager, public IHeroManager
//TODO: remove class, put managers to engine
class DLL_EXPORT AIhelper : public IPathfindingManager, public IArmyManager, public IHeroManager
{
friend class VCAI;
friend struct SetGlobalState; //mess?
std::shared_ptr<ResourceManager> resourceManager;
std::shared_ptr<BuildingManager> buildingManager;
//std::shared_ptr<ResourceManager> resourceManager;
//std::shared_ptr<BuildingManager> buildingManager;
std::shared_ptr<PathfindingManager> pathfindingManager;
std::shared_ptr<ArmyManager> armyManager;
std::shared_ptr<HeroManager> heroManager;
@ -40,29 +35,6 @@ public:
AIhelper();
~AIhelper();
bool canAfford(const TResources & cost) const;
TResources reservedResources() const override;
TResources freeResources() const override;
TResource freeGold() const override;
TResources allResources() const override;
TResource allGold() const override;
Goals::TSubgoal whatToDo(TResources &res, Goals::TSubgoal goal) override;
Goals::TSubgoal whatToDo() const override;
bool containsObjective(Goals::TSubgoal goal) const override;
bool hasTasksLeft() const override;
bool removeOutdatedObjectives(std::function<bool(const Goals::TSubgoal &)> predicate) override;
bool getBuildingOptions(const CGTownInstance * t) override;
BuildingID getMaxPossibleGoldBuilding(const CGTownInstance * t);
boost::optional<PotentialBuilding> immediateBuilding() const override;
boost::optional<PotentialBuilding> expensiveBuilding() const override;
boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
Goals::TGoalVec howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override;
std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
std::vector<AIPath> getPathsToTile(const int3 & tile) const override;
void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) override;
@ -95,9 +67,6 @@ public:
void update() override;
private:
bool notifyGoalCompleted(Goals::TSubgoal goal) override;
void init(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
};

View File

@ -91,6 +91,11 @@ Goals::TGoalVec CaptureObjectsBehavior::getTasks()
if(ai->ah->getHeroRole(hero) == HeroRole::SCOUT && danger == 0 && path.exchangeCount > 1)
continue;
if(path.specialAction && !path.specialAction->canAct(path.targetHero))
{
auto subGoal = path.specialAction->whatToDo(path.targetHero);
}
auto isSafe = isSafeToVisit(hero, path.heroArmy, danger);
#ifdef AI_TRACE_LEVEL >= 2

View File

@ -148,7 +148,9 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
if(treatIsUnderControl)
continue;
if(cb->getResourceAmount(Res::GOLD) > GameConstants::HERO_GOLD_COST)
if(!town->visitingHero
&& town->hasBuilt(BuildingID::TAVERN)
&& cb->getResourceAmount(Res::GOLD) > GameConstants::HERO_GOLD_COST)
{
auto heroesInTavern = cb->getAvailableHeroes(town);

View File

@ -56,6 +56,8 @@ Goals::TGoalVec GatherArmyBehavior::getTasks()
{
vstd::concatenate(tasks, upgradeArmy(town));
}
return tasks;
}
Goals::TGoalVec GatherArmyBehavior::deliverArmyToHero(const CGHeroInstance * hero) const

View File

@ -1,257 +0,0 @@
/*
* BuildingManager.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 "BuildingManager.h"
#include "../../CCallback.h"
#include "../../lib/mapObjects/MapObjects.h"
bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays)
{
if (maxDays == 0)
{
logAi->warn("Request to build building %d in 0 days!", building.toEnum());
return false;
}
if (!vstd::contains(t->town->buildings, building))
return false; // no such building in town
if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
return true;
const CBuilding * buildPtr = t->town->buildings.at(building);
auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
{
return t->hasBuilt(buildID);
});
toBuild.push_back(building);
for (BuildingID buildID : toBuild)
{
EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
if (canBuild == EBuildingState::HAVE_CAPITAL || canBuild == EBuildingState::FORBIDDEN || canBuild == EBuildingState::NO_WATER)
return false; //we won't be able to build this
}
if (maxDays && toBuild.size() > maxDays)
return false;
//TODO: calculate if we have enough resources to build it in maxDays?
for (const auto & buildID : toBuild)
{
const CBuilding * b = t->town->buildings.at(buildID);
EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
if (canBuild == EBuildingState::ALLOWED)
{
PotentialBuilding pb;
pb.bid = buildID;
pb.price = t->getBuildingCost(buildID);
immediateBuildings.push_back(pb); //these are checked again in try
return true;
}
else if (canBuild == EBuildingState::PREREQUIRES)
{
// can happen when dependencies have their own missing dependencies
if (tryBuildThisStructure(t, buildID, maxDays - 1))
return true;
}
else if (canBuild == EBuildingState::MISSING_BASE)
{
if (tryBuildThisStructure(t, b->upgrade, maxDays - 1))
return true;
}
else if (canBuild == EBuildingState::NO_RESOURCES)
{
//we may need to gather resources for those
PotentialBuilding pb;
pb.bid = buildID;
pb.price = t->getBuildingCost(buildID);
expensiveBuildings.push_back(pb); //these are checked again in try
return false;
}
else
return false;
}
return false;
}
bool BuildingManager::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
for (const auto & building : buildList)
{
if (t->hasBuilt(building))
continue;
return tryBuildThisStructure(t, building, maxDays);
}
return false; //Can't build anything
}
boost::optional<BuildingID> BuildingManager::canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const
{
for (const auto & building : buildList)
{
if (t->hasBuilt(building))
continue;
switch (cb->canBuildStructure(t, building))
{
case EBuildingState::ALLOWED:
case EBuildingState::NO_RESOURCES: //TODO: allow this via optional parameter?
return boost::optional<BuildingID>(building);
break;
}
}
return boost::optional<BuildingID>(); //Can't build anything
}
bool BuildingManager::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
for (const auto & building : buildList)
{
if (t->hasBuilt(building))
continue;
return tryBuildThisStructure(t, building, maxDays);
}
return false; //Nothing to build
}
void BuildingManager::init(CPlayerSpecificInfoCallback * CB)
{
cb = CB;
}
void BuildingManager::setAI(VCAI * AI)
{
ai = AI;
}
//Set of buildings for different goals. Does not include any prerequisites.
static const std::vector<BuildingID> essential = { BuildingID::TAVERN, BuildingID::TOWN_HALL };
static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL, BuildingID::CITY_HALL };
static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
static const std::vector<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 };
static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
static const std::vector<BuildingID> unitGrowth = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
static const std::vector<BuildingID> extra = { BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
{
//TODO make *real* town development system
//TODO: faction-specific development: use special buildings, build dwellings in better order, etc
//TODO: build resource silo, defences when needed
//Possible - allow "locking" on specific building (build prerequisites and then building itself)
//TODO: There is some disabled building code in GatherTroops and GatherArmy - take it into account when enhancing building. For now AI works best with building only via Build goal.
immediateBuildings.clear();
expensiveBuildings.clear();
//below algorithm focuses on economy growth at start of the game, saving money instead of build rushing is handled by Build goal
//changing code blocks order will alter behavior by changing order of adding elements to immediateBuildings / expensiveBuildings
TResources currentRes = cb->getResourceAmount();
TResources currentIncome = t->dailyIncome();
if(tryBuildAnyStructure(t, essential))
return true;
//the more gold the better and less problems later //TODO: what about building mage guild / marketplace etc. with city hall disabled in editor?
if(tryBuildNextStructure(t, basicGoldSource))
return true;
//workaround for mantis #2696 - build capitol with separate algorithm if it is available
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
{
if(tryBuildNextStructure(t, capitolAndRequirements))
return true;
}
if(!t->hasBuilt(BuildingID::FORT)) //in vast majority of situations fort is top priority building if we already have city hall, TODO: unite with unitGrowth building chain
if(tryBuildThisStructure(t, BuildingID::FORT))
return true;
if (cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
{
if (tryBuildNextStructure(t, unitGrowth, 2))
return true;
}
//try building dwellings
if (t->hasBuilt(BuildingID::FORT))
{
if (tryBuildAnyStructure(t, unitsSource, 8 - cb->getDate(Date::DAY_OF_WEEK)))
return true;
}
//try to upgrade dwelling
for (int i = 0; i < unitsUpgrade.size(); i++)
{
if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]) && t->hasBuilt(BuildingID::FORT))
{
if (tryBuildThisStructure(t, unitsUpgrade[i]))
return true;
}
}
//remaining tasks
if (tryBuildNextStructure(t, _spells))
return true;
if (tryBuildAnyStructure(t, extra))
return true;
//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
std::vector<BuildingID> extraBuildings;
for (auto buildingInfo : t->town->buildings)
{
if (buildingInfo.first > 43)
extraBuildings.push_back(buildingInfo.first);
}
return tryBuildAnyStructure(t, extraBuildings);
}
BuildingID BuildingManager::getMaxPossibleGoldBuilding(const CGTownInstance * t)
{
if(cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
return BuildingID::CAPITOL;
else if(cb->canBuildStructure(t, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
return BuildingID::CITY_HALL;
else if(cb->canBuildStructure(t, BuildingID::TOWN_HALL) != EBuildingState::FORBIDDEN)
return BuildingID::TOWN_HALL;
else
return BuildingID::VILLAGE_HALL;
}
boost::optional<PotentialBuilding> BuildingManager::immediateBuilding() const
{
if (immediateBuildings.size())
return boost::optional<PotentialBuilding>(immediateBuildings.front()); //back? whatever
else
return boost::optional<PotentialBuilding>();
}
boost::optional<PotentialBuilding> BuildingManager::expensiveBuilding() const
{
if (expensiveBuildings.size())
return boost::optional<PotentialBuilding>(expensiveBuildings.front());
else
return boost::optional<PotentialBuilding>();
}

View File

@ -1,75 +0,0 @@
/*
* BuildingManager.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 "AIUtility.h"
#include "../../lib/GameConstants.h"
#include "../../lib/VCMI_Lib.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CBuildingHandler.h"
#include "VCAI.h"
struct DLL_EXPORT PotentialBuilding
{
BuildingID bid;
TResources price;
//days to build?
};
class DLL_EXPORT IBuildingManager //: public: IAbstractManager
{ //info about town development
public:
virtual ~IBuildingManager() = default;
virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
virtual void setAI(VCAI * AI) = 0;
virtual bool getBuildingOptions(const CGTownInstance * t) = 0;
virtual boost::optional<PotentialBuilding> immediateBuilding() const = 0;
virtual boost::optional<PotentialBuilding> expensiveBuilding() const = 0;
virtual boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const = 0;
};
class DLL_EXPORT BuildingManager : public IBuildingManager
{
friend class VCAI;
friend class AIhelper;
friend struct SetGlobalState;
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
VCAI * ai;
public:
//try build anything in given town, and execute resulting Goal if any
bool getBuildingOptions(const CGTownInstance * t) override;
BuildingID getMaxPossibleGoldBuilding(const CGTownInstance * t);
boost::optional<PotentialBuilding> immediateBuilding() const override;
boost::optional<PotentialBuilding> expensiveBuilding() const override;
boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
protected:
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
//try build first unbuilt structure
bool tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays = 7);
//try build ANY unbuilt structure
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
private:
//TODO: remember current town?
std::vector<PotentialBuilding> immediateBuildings; //what we can build right now in current town
std::vector<PotentialBuilding> expensiveBuildings; //what we coudl build but can't afford
void init(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
};

View File

@ -17,19 +17,12 @@ set(VCAI_SRCS
AIhelper.cpp
ArmyManager.cpp
HeroManager.cpp
ResourceManager.cpp
BuildingManager.cpp
SectorMap.cpp
BuildingManager.cpp
MapObjectsEvaluator.cpp
FuzzyEngines.cpp
FuzzyHelper.cpp
Goals/AbstractGoal.cpp
Goals/BuildBoat.cpp
Goals/Build.cpp
Goals/BuildThis.cpp
Goals/Explore.cpp
Goals/GatherArmy.cpp
Goals/DismissHero.cpp
Goals/GatherTroops.cpp
Goals/BuyArmy.cpp
@ -41,8 +34,6 @@ set(VCAI_SRCS
Goals/CollectRes.cpp
Goals/Trade.cpp
Goals/RecruitHero.cpp
Goals/Conquer.cpp
Goals/ClearWayTo.cpp
Goals/DigAtTile.cpp
Goals/GetArtOfType.cpp
Goals/FindObj.cpp
@ -85,10 +76,6 @@ set(VCAI_HEADERS
AIhelper.h
ArmyManager.h
HeroManager.h
ResourceManager.h
BuildingManager.h
SectorMap.h
BuildingManager.h
MapObjectsEvaluator.h
FuzzyEngines.h
FuzzyHelper.h
@ -96,10 +83,7 @@ set(VCAI_HEADERS
Goals/CGoal.h
Goals/Invalid.h
Goals/BuildBoat.h
Goals/Build.h
Goals/BuildThis.h
Goals/Explore.h
Goals/GatherArmy.h
Goals/DismissHero.h
Goals/GatherTroops.h
Goals/BuyArmy.h
@ -111,8 +95,6 @@ set(VCAI_HEADERS
Goals/CollectRes.h
Goals/Trade.h
Goals/RecruitHero.h
Goals/Conquer.h
Goals/ClearWayTo.h
Goals/DigAtTile.h
Goals/GetArtOfType.h
Goals/FindObj.h

View File

@ -184,7 +184,7 @@ void Nullkiller::makeTurn()
}
catch(goalFulfilledException &)
{
logAi->trace(bestTask->completeMessage());
logAi->trace("Task %s completed", bestTask->name());
}
catch(std::exception & e)
{

View File

@ -23,7 +23,8 @@ extern FuzzyHelper * fh;
engineBase::engineBase()
{
engine.addRuleBlock(&rules);
rules = new fl::RuleBlock();
engine.addRuleBlock(rules);
}
void engineBase::configure()
@ -34,7 +35,7 @@ void engineBase::configure()
void engineBase::addRule(const std::string & txt)
{
rules.addRule(fl::Rule::parse(txt, &engine));
rules->addRule(fl::Rule::parse(txt, &engine));
}
struct armyStructure

View File

@ -17,7 +17,7 @@ class engineBase //subclasses create fuzzylite variables with "new" that are not
{
protected:
fl::Engine engine;
fl::RuleBlock rules;
fl::RuleBlock * rules;
virtual void configure();
void addRule(const std::string & txt);
public:

View File

@ -19,44 +19,6 @@ FuzzyHelper * fh;
extern boost::thread_specific_ptr<VCAI> ai;
extern boost::thread_specific_ptr<CCallback> cb;
Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
{
if(vec.empty())
{
logAi->debug("FuzzyHelper found no goals. Returning Goals::Invalid.");
//no possibilities found
return sptr(Goals::Invalid());
}
//a trick to switch between heroes less often - calculatePaths is costly
auto sortByHeroes = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
{
return lhs->hero.h < rhs->hero.h;
};
boost::sort(vec, sortByHeroes);
for(auto g : vec)
{
setPriority(g);
}
auto compareGoals = [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
{
return lhs->priority < rhs->priority;
};
for(auto goal : vec)
{
logAi->trace("FuzzyHelper evaluated goal %s, priority=%.4f", goal->name(), goal->priority);
}
Goals::TSubgoal result = *boost::max_element(vec, compareGoals);
logAi->debug("FuzzyHelper returned goal %s, priority=%.4f", result->name(), result->priority);
return result;
}
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
{
@ -77,134 +39,6 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
}
float FuzzyHelper::evaluate(Goals::VisitTile & g)
{
if(g.parent)
{
g.parent->accept(this);
}
return visitTileEngine.evaluate(g);
}
float FuzzyHelper::evaluate(Goals::BuildBoat & g)
{
const float buildBoatPenalty = 0.25;
if(!g.parent)
{
return 0;
}
return g.parent->accept(this) - buildBoatPenalty;
}
float FuzzyHelper::evaluate(Goals::AdventureSpellCast & g)
{
if(!g.parent)
{
return 0;
}
const CSpell * spell = g.getSpell();
const float spellCastPenalty = (float)g.hero->getSpellCost(spell) / g.hero->mana;
return g.parent->accept(this) - spellCastPenalty;
}
float FuzzyHelper::evaluate(Goals::CompleteQuest & g)
{
// TODO: How to evaluate quest complexity?
const float questPenalty = 0.2;
if(!g.parent)
{
return 0;
}
return g.parent->accept(this) * questPenalty;
}
float FuzzyHelper::evaluate(Goals::VisitObj & g)
{
if(g.parent)
{
g.parent->accept(this);
}
return visitObjEngine.evaluate(g);
}
float FuzzyHelper::evaluate(Goals::VisitHero & g)
{
auto obj = ai->myCb->getObj(ObjectInstanceID(g.objid)); //we assume for now that these goals are similar
if(!obj)
return -100; //hero died in the meantime
else
{
g.setpriority(Goals::VisitTile(obj->visitablePos()).sethero(g.hero).accept(this));
}
return g.priority;
}
float FuzzyHelper::evaluate(Goals::GatherArmy & g)
{
//the more army we need, the more important goal
//the more army we lack, the less important goal
float army = g.hero->getArmyStrength();
float ratio = g.value / std::max(g.value - army, 2000.0f); //2000 is about the value of hero recruited from tavern
return 5 * (ratio / (ratio + 2)); //so 50% army gives 2.5, asymptotic 5
}
float FuzzyHelper::evaluate(Goals::ClearWayTo & g)
{
if (!g.hero.h)
return 0; //lowest priority
return g.whatToDoToAchieve()->accept(this);
}
float FuzzyHelper::evaluate(Goals::BuildThis & g)
{
return g.priority; //TODO
}
float FuzzyHelper::evaluate(Goals::DigAtTile & g)
{
return 0;
}
float FuzzyHelper::evaluate(Goals::CollectRes & g)
{
return g.priority; //handled by ResourceManager
}
float FuzzyHelper::evaluate(Goals::Build & g)
{
return 0;
}
float FuzzyHelper::evaluate(Goals::BuyArmy & g)
{
return g.priority;
}
float FuzzyHelper::evaluate(Goals::Explore & g)
{
return 1;
}
float FuzzyHelper::evaluate(Goals::RecruitHero & g)
{
return 1;
}
float FuzzyHelper::evaluate(Goals::Invalid & g)
{
return -1e10;
}
float FuzzyHelper::evaluate(Goals::AbstractGoal & g)
{
logAi->warn("Cannot evaluate goal %s", g.name());
return g.priority;
}
void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pattern
{
g->setpriority(g->accept(this)); //this enforces returned value is set
}
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor)
{
return evaluateDanger(tile, visitor, ai.get());

View File

@ -16,33 +16,9 @@ class DLL_EXPORT FuzzyHelper
{
public:
TacticalAdvantageEngine tacticalAdvantageEngine;
VisitTileEngine visitTileEngine;
VisitObjEngine visitObjEngine;
float evaluate(Goals::Explore & g);
float evaluate(Goals::RecruitHero & g);
float evaluate(Goals::VisitTile & g);
float evaluate(Goals::VisitObj & g);
float evaluate(Goals::VisitHero & g);
float evaluate(Goals::BuildThis & g);
float evaluate(Goals::DigAtTile & g);
float evaluate(Goals::CollectRes & g);
float evaluate(Goals::Build & g);
float evaluate(Goals::BuyArmy & g);
float evaluate(Goals::BuildBoat & g);
float evaluate(Goals::GatherArmy & g);
float evaluate(Goals::ClearWayTo & g);
float evaluate(Goals::CompleteQuest & g);
float evaluate(Goals::AdventureSpellCast & g);
float evaluate(Goals::Invalid & g);
float evaluate(Goals::AbstractGoal & g);
void setPriority(Goals::TSubgoal & g);
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
ui64 evaluateDanger(const CGObjectInstance * obj, const VCAI * ai);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai, bool checkGuards = true);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);

View File

@ -12,8 +12,6 @@
#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"
@ -115,63 +113,12 @@ 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;
@ -182,11 +129,6 @@ void AbstractGoal::accept(VCAI * ai)
ai->tryRealize(*this);
}
float AbstractGoal::accept(FuzzyHelper * f)
{
return f->evaluate(*this);
}
EvaluationContext::EvaluationContext()
: movementCost(0.0),
manaCost(0),

View File

@ -30,7 +30,6 @@ namespace Goals
class BuildThis;
class DigAtTile;
class CollectRes;
class Build;
class BuyArmy;
class BuildBoat;
class GatherArmy;
@ -43,7 +42,8 @@ namespace Goals
enum EGoals
{
INVALID = -1,
WIN, CONQUER, BUILD, //build needs to get a real reasoning
WIN, CONQUER,
BUILD,
EXPLORE, GATHER_ARMY,
BOOST_HERO,
RECRUIT_HERO,
@ -162,24 +162,14 @@ namespace Goals
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
{

View File

@ -77,8 +77,3 @@ std::string AdventureSpellCast::name() const
{
return "AdventureSpellCast " + spellID.toSpell()->name;
}
std::string AdventureSpellCast::completeMessage() const
{
return "Spell casted successfully " + spellID.toSpell()->name;
}

View File

@ -38,7 +38,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const AdventureSpellCast & other) const override;
};
}

View File

@ -26,23 +26,23 @@ 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());
}
//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)
{
@ -79,8 +79,3 @@ std::string BuildBoat::name() const
{
return "BuildBoat";
}
std::string BuildBoat::completeMessage() const
{
return "Boat have been built at " + shipyard->o->visitablePos().toString();
}

View File

@ -28,10 +28,8 @@ namespace Goals
{
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;
};
}

View File

@ -13,8 +13,6 @@
#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"
@ -35,45 +33,3 @@ std::string BuildThis::name() const
{
return "Build " + buildingInfo.name + "(" + std::to_string(bid) + ") in " + town->name;
}
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");
}

View File

@ -47,12 +47,6 @@ namespace Goals
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;
virtual std::string name() const override;
};

View File

@ -25,21 +25,7 @@ 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;
return iAmElementar();
}

View File

@ -32,10 +32,8 @@ namespace Goals
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;
};
}

View File

@ -51,11 +51,6 @@ namespace Goals
ai->tryRealize(static_cast<T &>(*this)); //casting enforces template instantiation
}
float accept(FuzzyHelper * f) override
{
return f->evaluate(static_cast<T &>(*this)); //casting enforces template instantiation
}
CGoal<T> * clone() const override
{
return new T(static_cast<T const &>(*this)); //casting enforces template instantiation

View File

@ -13,8 +13,6 @@
#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"
@ -35,80 +33,80 @@ 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
//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
};
// //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<const CGObjectInstance *> objs;
for (auto obj : ai->visitableObjs)
{
if (givesResource(obj))
objs.push_back(obj);
}
for (auto h : cb->getHeroesInfo())
{
std::vector<const CGObjectInstance *> ourObjs(objs); //copy common objects
//std::vector<const CGObjectInstance *> objs;
//for (auto obj : ai->visitableObjs)
//{
// if (givesResource(obj))
// objs.push_back(obj);
//}
//for (auto h : cb->getHeroesInfo())
//{
// std::vector<const CGObjectInstance *> 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 : 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));
// for (auto obj : ourObjs)
// {
// auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
vstd::concatenate(ret, waysToGo);
}
}
// vstd::concatenate(ret, waysToGo);
// }
//}
return ret;
}
@ -119,90 +117,78 @@ TSubgoal CollectRes::whatToDoToAchieve()
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
return sptr(Invalid()); //we can always do that
}
TSubgoal CollectRes::whatToDoToTrade()
{
std::vector<const IMarket *> markets;
//std::vector<const IMarket *> markets;
std::vector<const CGObjectInstance *> 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);
}
}
//std::vector<const CGObjectInstance *> 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();
});
//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());
//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 (!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
}
}
}
// 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;
}

View File

@ -34,7 +34,6 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
TSubgoal whatToDoToTrade();
bool fulfillsMe(TSubgoal goal) override; //TODO: Trade
virtual bool operator==(const CollectRes & other) const override;
};
}

View File

@ -82,9 +82,8 @@ TSubgoal CompleteQuest::whatToDoToAchieve()
TGoalVec solutions = getAllPossibleSubgoals();
if(solutions.empty())
throw cannotFulfillGoalException("Can not complete quest " + questToString());
/*
TSubgoal result = fh->chooseSolution(solutions);
logAi->trace(
@ -94,7 +93,7 @@ TSubgoal CompleteQuest::whatToDoToAchieve()
result->objid,
result->hero.validAndSet() ? result->hero->name : "not specified");
return result;
return result;*/
}
std::string CompleteQuest::name() const
@ -102,11 +101,6 @@ std::string CompleteQuest::name() const
return "CompleteQuest";
}
std::string CompleteQuest::completeMessage() const
{
return "Completed quest " + questToString();
}
std::string CompleteQuest::questToString() const
{
if(q.quest->missionType == CQuest::MISSION_NONE)
@ -128,7 +122,7 @@ TGoalVec CompleteQuest::tryCompleteQuest() const
{
if(q.quest->checkQuest(hero))
{
vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
//vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
}
}
@ -228,7 +222,7 @@ TGoalVec CompleteQuest::missionResources() const
{
if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
return ai->ah->howToVisitObj(q.obj);
return solutions;// ai->ah->howToVisitObj(q.obj);
}
else
{
@ -254,7 +248,7 @@ TGoalVec CompleteQuest::missionDestroyObj() const
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
if(!obj)
return ai->ah->howToVisitObj(q.obj);
return solutions;// ai->ah->howToVisitObj(q.obj);
if(obj->ID == Obj::HERO)
{
@ -264,11 +258,11 @@ TGoalVec CompleteQuest::missionDestroyObj() const
{
auto heroToProtect = cb->getHero(obj->id);
solutions.push_back(sptr(GatherArmy().sethero(heroToProtect)));
//solutions.push_back(sptr(GatherArmy().sethero(heroToProtect)));
}
else if(relations == PlayerRelations::ENEMIES)
{
solutions = ai->ah->howToVisitObj(obj);
//solutions = ai->ah->howToVisitObj(obj);
}
}

View File

@ -28,7 +28,6 @@ namespace Goals
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const CompleteQuest & other) const override;
private:

View File

@ -48,8 +48,3 @@ std::string DismissHero::name() const
{
return "DismissHero " + hero.name;
}
std::string DismissHero::completeMessage() const
{
return "Hero dismissed successfully " + hero.name;
}

View File

@ -30,7 +30,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const DismissHero & other) const override;
};
}

View File

@ -36,11 +36,6 @@ std::string ExchangeSwapTownHeroes::name() const
return "Exchange and swap heroes of " + town->name;
}
std::string ExchangeSwapTownHeroes::completeMessage() const
{
return "Exchange and swap heroes of " + town->name + " compelete";
}
bool ExchangeSwapTownHeroes::operator==(const ExchangeSwapTownHeroes & other) const
{
return town == other.town;

View File

@ -35,7 +35,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const ExchangeSwapTownHeroes & other) const override;
};
}

View File

@ -72,27 +72,29 @@ void ExecuteHeroChain::accept(VCAI * ai)
{
auto & node = chainPath.nodes[i];
HeroPtr hero = node.targetHero;
const CGHeroInstance * hero = node.targetHero;
HeroPtr heroPtr = hero;
if(vstd::contains(blockedIndexes, i))
{
blockedIndexes.insert(node.parentIndex);
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN);
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
continue;
}
logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero.name, node.coord.toString());
logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->name, node.coord.toString());
try
{
if(hero->movement)
{
ai->nullkiller->setActive(hero.get(), node.coord);
ai->nullkiller->setActive(hero, node.coord);
if(node.specialAction)
{
if(node.specialAction->canAct(hero.get()))
if(node.specialAction->canAct(hero))
{
auto specialGoal = node.specialAction->whatToDo(hero);
@ -100,12 +102,12 @@ void ExecuteHeroChain::accept(VCAI * ai)
}
else
{
//TODO: decompose
throw cannotFulfillGoalException("Path is nondeterministic.");
}
if(!hero.validAndSet())
if(!heroPtr.validAndSet())
{
logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", hero.name);
logAi->error("Hero %s was lost trying to execute special action. Exit hero chain.", heroPtr.name);
return;
}
@ -113,7 +115,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
if(node.turns == 0 && node.coord != hero->visitablePos())
{
auto targetNode = cb->getPathsInfo(hero.get())->getPathInfo(node.coord);
auto targetNode = cb->getPathsInfo(hero)->getPathInfo(node.coord);
if(targetNode->accessible == CGPathNode::EAccessibility::NOT_SET
|| targetNode->accessible == CGPathNode::EAccessibility::BLOCKED
@ -122,7 +124,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
{
logAi->error(
"Enable to complete chain. Expected hero %s to arive to %s in 0 turns but he can not do this",
hero.name,
hero->name,
node.coord.toString());
return;
@ -137,9 +139,9 @@ void ExecuteHeroChain::accept(VCAI * ai)
}
catch(cannotFulfillGoalException)
{
if(!hero.validAndSet())
if(!heroPtr.validAndSet())
{
logAi->error("Hero %s was lost. Exit hero chain.", hero.name);
logAi->error("Hero %s was lost. Exit hero chain.", heroPtr.name);
return;
}
@ -147,13 +149,13 @@ void ExecuteHeroChain::accept(VCAI * ai)
if(hero->movement > 0)
{
CGPath path;
bool isOk = cb->getPathsInfo(hero.get())->getPath(path, node.coord);
bool isOk = cb->getPathsInfo(hero)->getPath(path, node.coord);
if(isOk && path.nodes.back().turns > 0)
{
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero.name, hero->movement, node.coord.toString());
logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->name, hero->movement, node.coord.toString());
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN);
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
return;
}
}
@ -170,7 +172,7 @@ void ExecuteHeroChain::accept(VCAI * ai)
{
logAi->error(
"Enable to complete chain. Expected hero %s to arive to %s but he is at %s",
hero.name,
hero->name,
node.coord.toString(),
hero->visitablePos().toString());
@ -178,14 +180,14 @@ void ExecuteHeroChain::accept(VCAI * ai)
}
// no exception means we were not able to rich the tile
ai->nullkiller->lockHero(hero.get(), HeroLockedReason::HERO_CHAIN);
ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
blockedIndexes.insert(node.parentIndex);
}
catch(goalFulfilledException)
{
if(!hero)
if(!heroPtr.validAndSet())
{
logAi->debug("Hero %s was killed while attempting to rich %s", hero.name, node.coord.toString());
logAi->debug("Hero %s was killed while attempting to rich %s", heroPtr.name, node.coord.toString());
return;
}
@ -197,8 +199,3 @@ std::string ExecuteHeroChain::name() const
{
return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->name;
}
std::string ExecuteHeroChain::completeMessage() const
{
return "Hero chain completed";
}

View File

@ -30,7 +30,6 @@ namespace Goals
TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const ExecuteHeroChain & other) const override;
const AIPath & getPath() const { return chainPath; }
};

View File

@ -233,14 +233,9 @@ bool Explore::operator==(const Explore & other) const
return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy;
}
std::string Explore::completeMessage() const
{
return "Hero " + hero.get()->name + " completed exploration";
}
TSubgoal Explore::whatToDoToAchieve()
{
return fh->chooseSolution(getAllPossibleSubgoals());
return sptr(Goals::Invalid());
}
TGoalVec Explore::getAllPossibleSubgoals()
@ -361,18 +356,6 @@ TGoalVec Explore::getAllPossibleSubgoals()
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;
}
TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const
{
ExplorationHelper scanResult(h, allowGatherArmy);

View File

@ -1,66 +1,64 @@
/*
* 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
{
struct ExplorationHelper;
class DLL_EXPORT Explore : public CGoal<Explore>
{
private:
bool allowGatherArmy;
public:
Explore(bool allowGatherArmy)
: CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy)
{
priority = 1;
}
Explore()
: Explore(true)
{
}
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;
private:
TSubgoal exploreNearestNeighbour(HeroPtr h) const;
TSubgoal explorationNewPoint(HeroPtr h) const;
TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const;
void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const;
bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const;
void getVisibleNeighbours(
const std::vector<int3> & tiles,
std::vector<int3> & out,
CCallback * cbp,
const TeamState * ts) const;
int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const;
};
}
///*
//* 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
//{
// struct ExplorationHelper;
//
// class DLL_EXPORT Explore : public CGoal<Explore>
// {
// private:
// bool allowGatherArmy;
//
// public:
// Explore(bool allowGatherArmy)
// : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy)
// {
// priority = 1;
// }
//
// Explore()
// : Explore(true)
// {
// }
//
// Explore(HeroPtr h)
// : CGoal(Goals::EXPLORE)
// {
// hero = h;
// priority = 1;
// }
// TGoalVec getAllPossibleSubgoals() override;
// TSubgoal whatToDoToAchieve() override;
// virtual bool operator==(const Explore & other) const override;
//
// private:
// TSubgoal exploreNearestNeighbour(HeroPtr h) const;
// TSubgoal explorationNewPoint(HeroPtr h) const;
// TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const;
// void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const;
// bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const;
//
// void getVisibleNeighbours(
// const std::vector<int3> & tiles,
// std::vector<int3> & out,
// CCallback * cbp,
// const TeamState * ts) const;
//
// int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const;
// };
//}

View File

@ -10,7 +10,6 @@
#include "StdInc.h"
#include "FindObj.h"
#include "VisitObj.h"
#include "Explore.h"
#include "../VCAI.h"
#include "../AIUtility.h"
@ -52,19 +51,4 @@ TSubgoal FindObj::whatToDoToAchieve()
}
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;
}

View File

@ -41,7 +41,6 @@ namespace Goals
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const FindObj & other) const override;
};
}

View File

@ -13,8 +13,6 @@
#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"
@ -62,12 +60,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
}
}
TGoalVec solutions = getAllPossibleSubgoals();
if(solutions.empty())
return sptr(Explore());
return fh->chooseSolution(solutions);
return sptr(Invalid());
}
@ -75,75 +68,74 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
{
TGoalVec solutions;
for(const CGTownInstance * t : cb->getTownsInfo())
{
int count = getCreaturesCount(t->getUpperArmy());
//for(const CGTownInstance * t : cb->getTownsInfo())
//{
// int count = getCreaturesCount(t->getUpperArmy());
if(count >= this->value)
{
if(t->visitingHero)
{
solutions.push_back(sptr(VisitObj(t->id.getNum()).sethero(t->visitingHero.get())));
}
else
{
vstd::concatenate(solutions, ai->ah->howToVisitObj(t));
}
// if(count >= this->value)
// {
// if(t->visitingHero)
// {
// solutions.push_back(sptr(VisitObj(t->id.getNum()).sethero(t->visitingHero.get())));
// }
// else
// {
// vstd::concatenate(solutions, ai->ah->howToVisitObj(t));
// }
continue;
}
// 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;
// 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;
// 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) && ai->ah->freeResources().canAfford(creature->cost)) //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<const CGDwelling *>(obj);
// BuildingID bid(BuildingID::DWELL_FIRST + creature->level - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN);
// if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->cost)) //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));
// }*/
// }
//}
if(!d || obj->ID == Obj::TOWN)
continue;
//for(auto obj : ai->visitableObjs)
//{
// auto d = dynamic_cast<const CGDwelling *>(obj);
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));
}
}
}
}
// 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));
// }
// }
// }
//}
//CreatureID creID = CreatureID(objid);
//vstd::erase_if(solutions, [&](TSubgoal goal)->bool
//{
// return goal->hero && !goal->hero->getSlotFor(creID).validSlot() && !goal->hero->getFreeSlot().validSlot();
//});
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;
}

View File

@ -34,7 +34,6 @@ namespace Goals
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const GatherTroops & other) const override;
private:

View File

@ -12,22 +12,17 @@
#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"
#include "CompleteQuest.h"

View File

@ -13,8 +13,6 @@
#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"
@ -25,14 +23,3 @@ extern boost::thread_specific_ptr<VCAI> 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
}

View File

@ -26,13 +26,6 @@ namespace Goals
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const RecruitHero & other) const override
{
return true;

View File

@ -9,14 +9,11 @@
*/
#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
@ -30,45 +27,3 @@ 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<std::string>(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->visitablePos() == 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;
}

View File

@ -30,13 +30,6 @@ namespace Goals
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;
};
}

View File

@ -13,8 +13,6 @@
#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"
@ -31,65 +29,6 @@ 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<std::string>(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(fh->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(fh->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)
{
@ -102,17 +41,3 @@ VisitObj::VisitObj(int Objid)
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;
}

View File

@ -23,10 +23,6 @@ namespace Goals
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;
};
}

View File

@ -13,8 +13,6 @@
#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"
@ -30,69 +28,3 @@ 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(fh->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<const CGHeroInstance *> 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<const CGHeroInstance *>(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;
}

View File

@ -29,9 +29,6 @@ namespace Goals
tile = Tile;
priority = 5;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override;
virtual bool operator==(const VisitTile & other) const override;
};
}

View File

@ -13,8 +13,6 @@
#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"
@ -62,9 +60,9 @@ TSubgoal Win::whatToDoToAchieve()
if(goal.object)
{
auto obj = cb->getObj(goal.object->id);
if(obj)
/*if(obj)
if(obj->getOwner() == ai->playerID) //we can't capture our own object
return sptr(Conquer());
return sptr(Conquer());*/
return sptr(VisitObj(goal.object->id.getNum()));
@ -122,8 +120,8 @@ TSubgoal Win::whatToDoToAchieve()
} //TODO: use FIND_OBJ
else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks
return sptr(VisitObj(obj->id.getNum()));
else
return sptr(Explore());
/*else
return sptr(Explore());*/
}
break;
}
@ -166,7 +164,7 @@ TSubgoal Win::whatToDoToAchieve()
break;
}
case EventCondition::STANDARD_WIN:
return sptr(Conquer());
return sptr(Invalid());
// Conditions that likely don't need any implementation
case EventCondition::DAYS_PASSED:
@ -182,7 +180,7 @@ TSubgoal Win::whatToDoToAchieve()
case EventCondition::HAVE_BUILDING_0:
case EventCondition::DESTROY_0:
//TODO: support new condition format
return sptr(Conquer());
return sptr(Invalid());
default:
assert(0);
}

View File

@ -64,7 +64,12 @@ boost::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance
auto hero = dynamic_cast<const CGHeroInstance*>(obj);
return getObjectValue(obj->ID, hero->type->heroClass->id);
}
if(obj->ID == Obj::CREATURE_GENERATOR1 || obj->ID == Obj::CREATURE_GENERATOR4)
else if(obj->ID == Obj::PRISON)
{
//special case: in-game prison subID is captured hero ID, but config has one subID with index 0 for normal prison - use that one
return getObjectValue(obj->ID, 0);
}
else if(obj->ID == Obj::CREATURE_GENERATOR1 || obj->ID == Obj::CREATURE_GENERATOR4)
{
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
int aiValue = 0;

View File

@ -14,7 +14,7 @@
namespace AIPathfinding
{
Goals::TSubgoal BattleAction::whatToDo(const HeroPtr & hero) const
Goals::TSubgoal BattleAction::whatToDo(const CGHeroInstance * hero) const
{
return Goals::sptr(Goals::VisitTile(targetTile).sethero(hero));
}

View File

@ -25,6 +25,6 @@ namespace AIPathfinding
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
virtual Goals::TSubgoal whatToDo(const CGHeroInstance * hero) const override;
};
}

View File

@ -17,7 +17,7 @@
namespace AIPathfinding
{
Goals::TSubgoal BuildBoatAction::whatToDo(const HeroPtr & hero) const
Goals::TSubgoal BuildBoatAction::whatToDo(const CGHeroInstance * hero) const
{
return Goals::sptr(Goals::BuildBoat(shipyard));
}
@ -27,7 +27,7 @@ namespace AIPathfinding
return sourceActor->resourceActor;
}
Goals::TSubgoal SummonBoatAction::whatToDo(const HeroPtr & hero) const
Goals::TSubgoal SummonBoatAction::whatToDo(const CGHeroInstance * hero) const
{
return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT));
}

View File

@ -25,7 +25,7 @@ namespace AIPathfinding
class SummonBoatAction : public VirtualBoatAction
{
public:
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
virtual Goals::TSubgoal whatToDo(const CGHeroInstance * hero) const override;
virtual void applyOnDestination(
const CGHeroInstance * hero,
@ -53,7 +53,7 @@ namespace AIPathfinding
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
virtual Goals::TSubgoal whatToDo(const CGHeroInstance * hero) const override;
virtual const ChainActor * getActor(const ChainActor * sourceActor) const override;
};

View File

@ -23,7 +23,7 @@ public:
return true;
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const = 0;
virtual Goals::TSubgoal whatToDo(const CGHeroInstance * hero) const = 0;
virtual void applyOnDestination(
const CGHeroInstance * hero,

View File

@ -16,7 +16,7 @@
using namespace AIPathfinding;
Goals::TSubgoal TownPortalAction::whatToDo(const HeroPtr & hero) const
Goals::TSubgoal TownPortalAction::whatToDo(const CGHeroInstance * hero) const
{
const CGTownInstance * targetTown = target; // const pointer is not allowed in settown

View File

@ -28,6 +28,6 @@ namespace AIPathfinding
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
virtual Goals::TSubgoal whatToDo(const CGHeroInstance * hero) const override;
};
}

View File

@ -32,76 +32,6 @@ void PathfindingManager::setAI(VCAI * AI)
ai = AI;
}
Goals::TGoalVec PathfindingManager::howToVisitTile(const int3 & tile, bool allowGatherArmy) const
{
Goals::TGoalVec result;
auto heroes = cb->getHeroesInfo();
result.reserve(heroes.size());
for(auto hero : heroes)
{
vstd::concatenate(result, howToVisitTile(hero, tile, allowGatherArmy));
}
return result;
}
Goals::TGoalVec PathfindingManager::howToVisitObj(ObjectIdRef obj, bool allowGatherArmy) const
{
Goals::TGoalVec result;
auto heroes = cb->getHeroesInfo();
result.reserve(heroes.size());
for(auto hero : heroes)
{
vstd::concatenate(result, howToVisitObj(hero, obj, allowGatherArmy));
}
return result;
}
Goals::TGoalVec PathfindingManager::howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy) const
{
auto result = findPaths(tile, allowGatherArmy, hero, [&](int3 firstTileToGet) -> Goals::TSubgoal
{
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
});
for(Goals::TSubgoal solution : result)
{
solution->setparent(sptr(Goals::VisitTile(tile).sethero(hero).setevaluationContext(solution->evaluationContext)));
}
return result;
}
Goals::TGoalVec PathfindingManager::howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy) const
{
if(!obj)
{
return Goals::TGoalVec();
}
int3 dest = obj->visitablePos();
auto result = findPaths(dest, allowGatherArmy, hero, [&](int3 firstTileToGet) -> Goals::TSubgoal
{
if(obj->ID.num == Obj::HERO && obj->getOwner() == hero->getOwner())
return sptr(Goals::VisitHero(obj->id.getNum()).sethero(hero).setisAbstract(true));
else
return sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero).setisAbstract(true));
});
for(Goals::TSubgoal solution : result)
{
solution->setparent(sptr(Goals::VisitObj(obj->id.getNum()).sethero(hero).setevaluationContext(solution->evaluationContext)));
}
return result;
}
std::vector<AIPath> PathfindingManager::getPathsToTile(const HeroPtr & hero, const int3 & tile) const
{
auto paths = pathfinder->getPathInfo(tile);
@ -118,152 +48,6 @@ std::vector<AIPath> PathfindingManager::getPathsToTile(const int3 & tile) const
return pathfinder->getPathInfo(tile);
}
Goals::TGoalVec PathfindingManager::findPaths(
crint3 dest,
bool allowGatherArmy,
HeroPtr hero,
const std::function<Goals::TSubgoal(int3)> doVisitTile) const
{
Goals::TGoalVec result;
boost::optional<uint64_t> armyValueRequired;
uint64_t danger;
std::vector<AIPath> chainInfo = pathfinder->getPathInfo(dest);
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Trying to find a way for %s to visit tile %s", hero->name, dest.toString());
#endif
for(auto path : chainInfo)
{
if((hero && hero.get() != path.targetHero) || path.nodes.empty())
continue;
const AIPathNodeInfo & firstNode = path.firstNode();
int3 firstTileToGet = firstNode.coord;
#ifdef VCMI_TRACE_PATHFINDER
std::stringstream str;
str << "Path found ";
for(auto node : path.nodes)
str << node.targetHero->name << "->" << node.coord.toString() << "; ";
logAi->trace(str.str());
#endif
if(ai->isTileNotReserved(hero.get(), firstTileToGet))
{
danger = path.getTotalDanger();
if(isSafeToVisit(hero, path.heroArmy, danger))
{
Goals::TSubgoal solution;
if(path.specialAction)
{
solution = path.specialAction->whatToDo(hero);
}
else
{
solution = dest == firstTileToGet
? doVisitTile(firstTileToGet)
: clearWayTo(firstNode.targetHero, firstTileToGet);
}
if(solution->invalid())
continue;
if(solution->evaluationContext.danger < danger)
solution->evaluationContext.danger = danger;
solution->evaluationContext.movementCost += path.movementCost();
solution->evaluationContext.armyLoss += path.getTotalArmyLoss();
solution->evaluationContext.heroStrength = path.getHeroStrength();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("It's safe for %s to visit tile %s with danger %s, loss %s, army strength %s, goal %s",
hero->name,
dest.toString(),
std::to_string(danger),
std::to_string(path.armyLoss),
std::to_string(path.heroArmy->getArmyStrength()),
solution->name());
#endif
result.push_back(solution);
continue;
}
if(!armyValueRequired || armyValueRequired > danger)
{
armyValueRequired = boost::make_optional(danger);
}
}
}
danger = armyValueRequired.get_value_or(0);
if(allowGatherArmy && danger > 0)
{
//we need to get army in order to conquer that place
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Gather army for %s, value=%s", hero->name, std::to_string(danger));
#endif
result.push_back(sptr(Goals::GatherArmy(danger * SAFE_ATTACK_CONSTANT).sethero(hero).setisAbstract(true)));
}
return result;
}
Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet) const
{
if(isBlockedBorderGate(firstTileToGet))
{
//FIXME: this way we'll not visit gate and activate quest :?
return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(firstTileToGet)->visitableObjects.back()->subID));
}
auto topObj = cb->getTopObj(firstTileToGet);
if(topObj)
{
if(vstd::contains(ai->reservedObjs, topObj) && !vstd::contains(ai->reservedHeroesMap[hero], topObj))
{
return sptr(Goals::Invalid());
}
if(topObj->ID == Obj::HERO && cb->getPlayerRelations(hero->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
{
if(topObj != hero.get(true)) //the hero we want to free
{
logAi->warn("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
}
}
if(topObj->ID == Obj::QUEST_GUARD || topObj->ID == Obj::BORDERGUARD)
{
if(shouldVisit(hero, topObj))
{
//do NOT use VISIT_TILE, as tile with quets guard can't be visited
return sptr(Goals::VisitObj(topObj->id.getNum()).sethero(hero));
}
auto questObj = dynamic_cast<const IQuestObject*>(topObj);
if(questObj)
{
auto questInfo = QuestInfo(questObj->quest, topObj, topObj->visitablePos());
return sptr(Goals::CompleteQuest(questInfo));
}
return sptr(Goals::Invalid());
}
}
return sptr(Goals::VisitTile(firstTileToGet).sethero(hero).setisAbstract(true));
}
void PathfindingManager::updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain)
{
logAi->debug("AIPathfinder has been reseted.");

View File

@ -21,10 +21,6 @@ public:
virtual void setAI(VCAI * AI) = 0;
virtual void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) = 0;
virtual Goals::TGoalVec howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy = true) const = 0;
virtual Goals::TGoalVec howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy = true) const = 0;
virtual Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const = 0;
virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const = 0;
virtual std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const = 0;
virtual std::vector<AIPath> getPathsToTile(const int3 & tile) const = 0;
};
@ -42,10 +38,6 @@ public:
PathfindingManager() = default;
PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI = nullptr); //for tests only
Goals::TGoalVec howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitObj(const HeroPtr & hero, ObjectIdRef obj, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitTile(const int3 & tile, bool allowGatherArmy = true) const override;
Goals::TGoalVec howToVisitObj(ObjectIdRef obj, bool allowGatherArmy = true) const override;
std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
std::vector<AIPath> getPathsToTile(const int3 & tile) const override;
void updatePaths(std::vector<HeroPtr> heroes, bool useHeroChain = false) override;

View File

@ -26,7 +26,7 @@ namespace AIPathfinding
return false;
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override
virtual Goals::TSubgoal whatToDo(const CGHeroInstance * hero) const override
{
return Goals::sptr(Goals::Invalid());
}

View File

@ -1,364 +0,0 @@
/*
* ResourceManager.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 "ResourceManager.h"
#include "Goals/Goals.h"
#include "../../CCallback.h"
#include "../../lib/mapObjects/MapObjects.h"
#define GOLD_RESERVE (10000); //at least we'll be able to reach capitol
ResourceObjective::ResourceObjective(const TResources & Res, Goals::TSubgoal Goal)
: resources(Res), goal(Goal)
{
}
bool ResourceObjective::operator<(const ResourceObjective & ro) const
{
return goal->priority < ro.goal->priority;
}
ResourceManager::ResourceManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
: ai(AI), cb(CB)
{
}
void ResourceManager::init(CPlayerSpecificInfoCallback * CB)
{
cb = CB;
}
void ResourceManager::setAI(VCAI * AI)
{
ai = AI;
}
bool ResourceManager::canAfford(const TResources & cost) const
{
return freeResources().canAfford(cost);
}
TResources ResourceManager::estimateIncome() const
{
TResources ret;
for (const CGTownInstance * t : cb->getTownsInfo())
{
ret += t->dailyIncome();
}
for (const CGObjectInstance * obj : ai->getFlaggedObjects())
{
if (obj->ID == Obj::MINE)
{
switch (obj->subID)
{
case Res::WOOD:
case Res::ORE:
ret[obj->subID] += WOOD_ORE_MINE_PRODUCTION;
break;
case Res::GOLD:
case 7: //abandoned mine -> also gold
ret[Res::GOLD] += GOLD_MINE_PRODUCTION;
break;
default:
ret[obj->subID] += RESOURCE_MINE_PRODUCTION;
break;
}
}
}
return ret;
}
void ResourceManager::reserveResoures(const TResources & res, Goals::TSubgoal goal)
{
if (!goal->invalid())
tryPush(ResourceObjective(res, goal));
else
logAi->warn("Attempt to reserve resources for Invalid goal");
}
Goals::TSubgoal ResourceManager::collectResourcesForOurGoal(ResourceObjective &o) const
{
auto allResources = cb->getResourceAmount();
auto income = estimateIncome();
Res::ERes resourceType = Res::INVALID;
TResource amountToCollect = 0;
typedef std::pair<Res::ERes, TResource> resPair;
std::map<Res::ERes, TResource> missingResources;
//TODO: unit test for complex resource sets
//sum missing resources of given type for ALL reserved objectives
for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
{
//choose specific resources we need for this goal (not 0)
for (auto r = Res::ResourceSet::nziterator(o.resources); r.valid(); r++)
missingResources[r->resType] += it->resources[r->resType]; //goal it costs r units of resType
}
for (auto it = Res::ResourceSet::nziterator(o.resources); it.valid(); it++)
{
missingResources[it->resType] -= allResources[it->resType]; //missing = (what we need) - (what we have)
vstd::amax(missingResources[it->resType], 0); // if we have more resources than reserved, we don't need them
}
vstd::erase_if(missingResources, [=](const resPair & p) -> bool
{
return !(p.second); //in case evaluated to 0 or less
});
if (missingResources.empty()) //FIXME: should be unit-tested out
{
logAi->error("We don't need to collect resources %s for goal %s", o.resources.toString(), o.goal->name());
return o.goal;
}
float goalPriority = 10; //arbitrary, will be divided
for (const resPair & p : missingResources)
{
if (!income[p.first]) //prioritize resources with 0 income
{
resourceType = p.first;
amountToCollect = p.second;
goalPriority /= amountToCollect; //need more resources -> lower priority
break;
}
}
if (resourceType == Res::INVALID) //no needed resources has 0 income,
{
//find the one which takes longest to collect
typedef std::pair<Res::ERes, float> timePair;
std::map<Res::ERes, float> daysToEarn;
for (auto it : missingResources)
daysToEarn[it.first] = (float)missingResources[it.first] / income[it.first];
auto incomeComparer = [&income](const timePair & lhs, const timePair & rhs) -> bool
{
//theoretically income can be negative, but that falls into this comparison
return lhs.second < rhs.second;
};
resourceType = boost::max_element(daysToEarn, incomeComparer)->first;
amountToCollect = missingResources[resourceType];
goalPriority /= daysToEarn[resourceType]; //more days - lower priority
}
if (resourceType == Res::GOLD)
goalPriority *= 1000;
//this is abstract goal and might take soem time to complete
return Goals::sptr(Goals::CollectRes(resourceType, amountToCollect).setisAbstract(true));
}
Goals::TSubgoal ResourceManager::whatToDo() const //suggest any goal
{
if (queue.size())
{
auto o = queue.top();
auto allResources = cb->getResourceAmount(); //we don't consider savings, it's out top-priority goal
if (allResources.canAfford(o.resources))
return o.goal;
else //we can't afford even top-priority goal, need to collect resources
return collectResourcesForOurGoal(o);
}
else
return Goals::sptr(Goals::Invalid()); //nothing else to do
}
Goals::TSubgoal ResourceManager::whatToDo(TResources &res, Goals::TSubgoal goal)
{
logAi->trace("ResourceManager: checking goal %s which requires resources %s", goal->name(), res.toString());
TResources accumulatedResources;
auto allResources = cb->getResourceAmount();
ResourceObjective ro(res, goal);
tryPush(ro);
//check if we can afford all the objectives with higher priority first
for (auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
{
accumulatedResources += it->resources;
logAi->trace(
"ResourceManager: checking goal %s, accumulatedResources=%s, available=%s",
it->goal->name(),
accumulatedResources.toString(),
allResources.toString());
if(!accumulatedResources.canBeAfforded(allResources))
{
//can't afford
break;
}
else //can afford all goals up to this point
{
if(it->goal == goal)
{
logAi->debug("ResourceManager: can afford goal %s", goal->name());
return goal; //can afford immediately
}
}
}
logAi->debug("ResourceManager: can not afford goal %s", goal->name());
return collectResourcesForOurGoal(ro);
}
bool ResourceManager::containsObjective(Goals::TSubgoal goal) const
{
logAi->trace("Entering ResourceManager.containsObjective goal=%s", goal->name());
dumpToLog();
//TODO: unit tests for once
for (auto objective : queue)
{
if (objective.goal == goal)
return true;
}
return false;
}
bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
{
logAi->trace("Entering ResourceManager.notifyGoalCompleted goal=%s", goal->name());
if (goal->invalid())
logAi->warn("Attempt to complete Invalid goal");
std::function<bool(const Goals::TSubgoal &)> equivalentGoalsCheck = [goal](const Goals::TSubgoal & x) -> bool
{
return x == goal || x->fulfillsMe(goal);
};
bool removedGoal = removeOutdatedObjectives(equivalentGoalsCheck);
dumpToLog();
return removedGoal;
}
bool ResourceManager::updateGoal(Goals::TSubgoal goal)
{
//we update priority of goal if it is easier or more difficult to complete
if (goal->invalid())
logAi->warn("Attempt to update Invalid goal");
auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
{
return ro.goal == goal;
});
if (it != queue.end())
{
it->goal->setpriority(goal->priority);
auto handle = queue.s_handle_from_iterator(it);
queue.update(handle); //restore order
return true;
}
else
return false;
}
void ResourceManager::dumpToLog() const
{
for(auto it = queue.ordered_begin(); it != queue.ordered_end(); it++)
{
logAi->trace("ResourceManager contains goal %s which requires resources %s", it->goal->name(), it->resources.toString());
}
}
bool ResourceManager::tryPush(const ResourceObjective & o)
{
auto goal = o.goal;
logAi->trace("ResourceManager: Trying to add goal %s which requires resources %s", goal->name(), o.resources.toString());
dumpToLog();
auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
{
return ro.goal == goal;
});
if (it != queue.end())
{
auto handle = queue.s_handle_from_iterator(it);
vstd::amax(goal->priority, it->goal->priority); //increase priority if case
//update resources with new value
queue.update(handle, ResourceObjective(o.resources, goal)); //restore order
return false;
}
else
{
queue.push(o); //add new objective
logAi->debug("Reserved resources (%s) for %s", o.resources.toString(), goal->name());
return true;
}
}
bool ResourceManager::hasTasksLeft() const
{
return !queue.empty();
}
bool ResourceManager::removeOutdatedObjectives(std::function<bool(const Goals::TSubgoal &)> predicate)
{
bool removedAnything = false;
while(true)
{ //unfortunately we can't use remove_if on heap
auto it = boost::find_if(queue, [&](const ResourceObjective & ro) -> bool
{
return predicate(ro.goal);
});
if(it != queue.end()) //removed at least one
{
logAi->debug("Removing goal %s from ResourceManager.", it->goal->name());
queue.erase(queue.s_handle_from_iterator(it));
removedAnything = true;
}
else
{ //found nothing more to remove
break;
}
}
return removedAnything;
}
TResources ResourceManager::reservedResources() const
{
TResources res;
for (auto it : queue) //substract the value of reserved goals
res += it.resources;
return res;
}
TResources ResourceManager::freeResources() const
{
TResources myRes = cb->getResourceAmount();
myRes -= reservedResources(); //substract the value of reserved goals
for (auto & val : myRes)
vstd::amax(val, 0); //never negative
return myRes;
}
TResource ResourceManager::freeGold() const
{
return freeResources()[Res::GOLD];
}
TResources ResourceManager::allResources() const
{
return cb->getResourceAmount();
}
TResource ResourceManager::allGold() const
{
return cb->getResourceAmount()[Res::GOLD];
}

View File

@ -1,113 +0,0 @@
/*
* ResourceManager.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 "AIUtility.h"
#include "../../lib/GameConstants.h"
#include "../../lib/VCMI_Lib.h"
#include "VCAI.h"
#include <boost/heap/binomial_heap.hpp>
class AIhelper;
class IResourceManager;
struct DLL_EXPORT ResourceObjective
{
ResourceObjective() = default;
ResourceObjective(const TResources &res, Goals::TSubgoal goal);
bool operator < (const ResourceObjective &ro) const;
TResources resources; //how many resoures do we need
Goals::TSubgoal goal; //what for (build, gather army etc...)
//TODO: register?
template<typename Handler> void serializeInternal(Handler & h, const int version)
{
h & resources;
//h & goal; //FIXME: goal serialization is broken
}
};
class DLL_EXPORT IResourceManager //: public: IAbstractManager
{
public:
virtual ~IResourceManager() = default;
virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
virtual void setAI(VCAI * AI) = 0;
virtual TResources reservedResources() const = 0;
virtual TResources freeResources() const = 0;
virtual TResource freeGold() const = 0;
virtual TResources allResources() const = 0;
virtual TResource allGold() const = 0;
virtual Goals::TSubgoal whatToDo() const = 0;//get highest-priority goal
virtual Goals::TSubgoal whatToDo(TResources &res, Goals::TSubgoal goal) = 0;
virtual bool containsObjective(Goals::TSubgoal goal) const = 0;
virtual bool hasTasksLeft() const = 0;
virtual bool removeOutdatedObjectives(std::function<bool(const Goals::TSubgoal &)> predicate) = 0; //remove ResourceObjectives from queue if ResourceObjective->goal meets specific criteria
virtual bool notifyGoalCompleted(Goals::TSubgoal goal) = 0;
};
class DLL_EXPORT ResourceManager : public IResourceManager
{
/*Resource Manager is a smart shopping list for AI to help
Uses priority queue based on CGoal.priority */
friend class VCAI;
friend class AIhelper;
friend struct SetGlobalState;
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
VCAI * ai;
public:
ResourceManager() = default;
ResourceManager(CPlayerSpecificInfoCallback * CB, VCAI * AI = nullptr); //for tests only
bool canAfford(const TResources & cost) const;
TResources reservedResources() const override;
TResources freeResources() const override;
TResource freeGold() const override;
TResources allResources() const override;
TResource allGold() const override;
Goals::TSubgoal whatToDo() const override; //peek highest-priority goal
Goals::TSubgoal whatToDo(TResources & res, Goals::TSubgoal goal) override; //can we afford this goal or need to CollectRes?
bool containsObjective(Goals::TSubgoal goal) const override;
bool hasTasksLeft() const override;
bool removeOutdatedObjectives(std::function<bool(const Goals::TSubgoal &)> predicate) override;
bool notifyGoalCompleted(Goals::TSubgoal goal) override;
protected: //not-const actions only for AI
virtual void reserveResoures(const TResources & res, Goals::TSubgoal goal = Goals::TSubgoal());
virtual bool updateGoal(Goals::TSubgoal goal); //new goal must have same properties but different priority
virtual bool tryPush(const ResourceObjective &o);
//inner processing
virtual TResources estimateIncome() const;
virtual Goals::TSubgoal collectResourcesForOurGoal(ResourceObjective &o) const;
void init(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
private:
TResources saving;
boost::heap::binomial_heap<ResourceObjective> queue;
void dumpToLog() const;
//TODO: register?
template<typename Handler> void serializeInternal(Handler & h, const int version)
{
h & saving;
h & queue;
}
};

View File

@ -76,7 +76,7 @@
<Add option="-Wno-sign-compare" />
<Add option="-Wno-unused-parameter" />
<Add option="-Wno-overloaded-virtual" />
<Add option="-DBOOST_THREAD_USE_LIB" />
<Add option="-DBOOST_ALL_DYN_LINK" />
<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
<Add option="-D_WIN32_WINNT=0x0501" />
<Add option="-D_WIN32" />
@ -93,6 +93,8 @@
<Unit filename="AIUtility.h" />
<Unit filename="AIhelper.cpp" />
<Unit filename="AIhelper.h" />
<Unit filename="ArmyManager.cpp" />
<Unit filename="ArmyManager.h" />
<Unit filename="BuildingManager.cpp" />
<Unit filename="BuildingManager.h" />
<Unit filename="FuzzyEngines.cpp" />

View File

@ -10,8 +10,6 @@
#include "StdInc.h"
#include "VCAI.h"
#include "FuzzyHelper.h"
#include "ResourceManager.h"
#include "BuildingManager.h"
#include "Goals/Goals.h"
#include "../../lib/UnlockGuard.h"
@ -119,9 +117,6 @@ void VCAI::heroMoved(const TryMoveHero & details)
}
}
}
//FIXME: teleports are not correctly visited
unreserveObject(hero, t1);
unreserveObject(hero, t2);
}
else if(details.result == TryMoveHero::EMBARK && hero)
{
@ -246,8 +241,6 @@ void VCAI::heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * vi
if(start && visitedObj) //we can end visit with null object, anyway
{
markObjectVisited(visitedObj);
unreserveObject(visitor, visitedObj);
completeGoal(sptr(Goals::VisitObj(visitedObj->id.getNum()).sethero(visitor))); //we don't need to visit it anymore
//TODO: what if we visited one-time visitable object that was reserved by another hero (shouldn't, but..)
if (visitedObj->ID == Obj::HERO)
{
@ -350,9 +343,6 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
transferFrom2to1(secondHero, firstHero);
}
completeGoal(sptr(Goals::VisitHero(firstHero->id.getNum()))); //TODO: what if we were visited by other hero in the meantime?
completeGoal(sptr(Goals::VisitHero(secondHero->id.getNum())));
answerQuery(query, 0);
});
}
@ -399,9 +389,6 @@ void VCAI::objectRemoved(const CGObjectInstance * obj)
vstd::erase_if_present(visitableObjs, obj);
vstd::erase_if_present(alreadyVisited, obj);
for(auto h : cb->getHeroesInfo())
unreserveObject(h, obj);
std::function<bool(const Goals::TSubgoal &)> checkRemovalValidity = [&](const Goals::TSubgoal & x) -> bool
{
if((x->goalType == Goals::VISIT_OBJ) && (x->objid == obj->id.getNum()))
@ -412,27 +399,6 @@ void VCAI::objectRemoved(const CGObjectInstance * obj)
return false;
};
//clear VCAI / main loop caches
vstd::erase_if(lockedHeroes, [&](const std::pair<HeroPtr, Goals::TSubgoal> & x) -> bool
{
return checkRemovalValidity(x.second);
});
vstd::erase_if(ultimateGoalsFromBasic, [&](const std::pair<Goals::TSubgoal, Goals::TGoalVec> & x) -> bool
{
return checkRemovalValidity(x.first);
});
vstd::erase_if(basicGoals, checkRemovalValidity);
vstd::erase_if(goalsToAdd, checkRemovalValidity);
vstd::erase_if(goalsToRemove, checkRemovalValidity);
for(auto goal : ultimateGoalsFromBasic)
vstd::erase_if(goal.second, checkRemovalValidity);
//clear resource manager goal cache
ah->removeOutdatedObjectives(checkRemovalValidity);
//TODO: Find better way to handle hero boat removal
if(auto hero = dynamic_cast<const CGHeroInstance *>(obj))
{
@ -440,9 +406,6 @@ void VCAI::objectRemoved(const CGObjectInstance * obj)
{
vstd::erase_if_present(visitableObjs, hero->boat);
vstd::erase_if_present(alreadyVisited, hero->boat);
for(auto h : cb->getHeroesInfo())
unreserveObject(h, hero->boat);
}
}
@ -569,9 +532,6 @@ void VCAI::buildChanged(const CGTownInstance * town, BuildingID buildingID, int
{
LOG_TRACE_PARAMS(logAi, "what '%i'", what);
NET_EVENT_HANDLER;
if(town->getOwner() == playerID && what == 1) //built
completeGoal(sptr(Goals::BuildThis(buildingID, town)));
}
void VCAI::heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain)
@ -854,23 +814,8 @@ void VCAI::makeTurn()
}
try
{
if(nullkiller)
{
nullkiller->makeTurn();
}
else
{
//it looks messy here, but it's better to have armed heroes before attempting realizing goals
for(const CGTownInstance * t : cb->getTownsInfo())
moveCreaturesToHero(t);
mainLoop();
/*Below function is also responsible for hero movement via internal wander function. By design it is separate logic for heroes that have nothing to do.
Heroes that were not picked by striveToGoal(sptr(Goals::Win())); recently (so they do not have new goals and cannot continue/reevaluate previously locked goals) will do logic in wander().*/
performTypicalActions();
}
//for debug purpose
for (auto h : cb->getHeroesInfo())
@ -904,194 +849,6 @@ std::vector<HeroPtr> VCAI::getMyHeroes() const
return ret;
}
void VCAI::mainLoop()
{
std::vector<Goals::TSubgoal> elementarGoals; //no duplicates allowed (operator ==)
basicGoals.clear();
validateVisitableObjs();
//get all potential and saved goals
//TODO: not lose
basicGoals.push_back(sptr(Goals::Win()));
for (auto goalPair : lockedHeroes)
{
fh->setPriority(goalPair.second); //re-evaluate, as heroes moved in the meantime
basicGoals.push_back(goalPair.second);
}
if (ah->hasTasksLeft())
basicGoals.push_back(ah->whatToDo());
for (auto quest : myCb->getMyQuests())
{
basicGoals.push_back(sptr(Goals::CompleteQuest(quest)));
}
basicGoals.push_back(sptr(Goals::Build()));
invalidPathHeroes.clear();
while (basicGoals.size())
{
vstd::removeDuplicates(basicGoals); //TODO: container which does this automagically without has would be nice
goalsToAdd.clear();
goalsToRemove.clear();
elementarGoals.clear();
ultimateGoalsFromBasic.clear();
ah->updatePaths(getMyHeroes());
logAi->debug("Main loop: decomposing %i basic goals", basicGoals.size());
for (auto basicGoal : basicGoals)
{
logAi->debug("Main loop: decomposing basic goal %s", basicGoal->name());
auto goalToDecompose = basicGoal;
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
int maxAbstractGoals = 10;
while (!elementarGoal->isElementar && maxAbstractGoals)
{
try
{
elementarGoal = decomposeGoal(goalToDecompose);
}
catch (goalFulfilledException & e)
{
//it is impossible to continue some goals (like exploration, for example)
//complete abstract goal for now, but maybe main goal finds another path
logAi->debug("Goal %s decomposition failed: goal was completed as much as possible", e.goal->name());
completeGoal(e.goal); //put in goalsToRemove
break;
}
catch(cannotFulfillGoalException & e)
{
//it is impossible to continue some goals (like exploration, for example)
//complete abstract goal for now, but maybe main goal finds another path
goalsToRemove.push_back(basicGoal);
logAi->debug("Goal %s decomposition failed: %s", goalToDecompose->name(), e.what());
break;
}
catch (std::exception & e) //decomposition failed, which means we can't decompose entire tree
{
goalsToRemove.push_back(basicGoal);
logAi->debug("Goal %s decomposition failed: %s", basicGoal->name(), e.what());
break;
}
if (elementarGoal->isAbstract) //we can decompose it further
{
goalsToAdd.push_back(elementarGoal);
//decompose further now - this is necesssary if we can't add over 10 goals in the pool
goalToDecompose = elementarGoal;
//there is a risk of infinite abstract goal loop, though it indicates failed logic
maxAbstractGoals--;
}
else if (elementarGoal->isElementar) //should be
{
logAi->debug("Found elementar goal %s", elementarGoal->name());
elementarGoals.push_back(elementarGoal);
ultimateGoalsFromBasic[elementarGoal].push_back(goalToDecompose); //TODO: how about indirect basicGoal?
break;
}
else //should never be here
throw cannotFulfillGoalException("Goal %s is neither abstract nor elementar!" + basicGoal->name());
}
}
logAi->trace("Main loop: selecting best elementar goal");
//now choose one elementar goal to realize
Goals::TGoalVec possibleGoals(elementarGoals.begin(), elementarGoals.end()); //copy to vector
Goals::TSubgoal goalToRealize = sptr(Goals::Invalid());
while (possibleGoals.size())
{
//allow assign goals to heroes with 0 movement, but don't realize them
//maybe there are beter ones left
auto bestGoal = fh->chooseSolution(possibleGoals);
if (bestGoal->hero) //lock this hero to fulfill goal
{
setGoal(bestGoal->hero, bestGoal);
if (!bestGoal->hero->movement || vstd::contains(invalidPathHeroes, bestGoal->hero))
{
if (!vstd::erase_if_present(possibleGoals, bestGoal))
{
logAi->error("erase_if_preset failed? Something very wrong!");
break;
}
continue; //chose next from the list
}
}
goalToRealize = bestGoal; //we found our goal to execute
break;
}
//realize best goal
if (!goalToRealize->invalid())
{
logAi->debug("Trying to realize %s (value %2.3f)", goalToRealize->name(), goalToRealize->priority);
try
{
boost::this_thread::interruption_point();
goalToRealize->accept(this); //visitor pattern
boost::this_thread::interruption_point();
}
catch (boost::thread_interrupted & e)
{
logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread
}
catch (goalFulfilledException & e)
{
//the sub-goal was completed successfully
completeGoal(e.goal);
//local goal was also completed?
completeGoal(goalToRealize);
// remove abstract visit tile if we completed the elementar one
vstd::erase_if_present(goalsToAdd, goalToRealize);
}
catch (std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s, I will stop.", goalToRealize->name());
logAi->debug("The error message was: %s", e.what());
//erase base goal if we failed to execute decomposed goal
for (auto basicGoal : ultimateGoalsFromBasic[goalToRealize])
goalsToRemove.push_back(basicGoal);
// sometimes resource manager contains an elementar goal which is not able to execute anymore and just fails each turn.
ai->ah->notifyGoalCompleted(goalToRealize);
//we failed to realize best goal, but maybe others are still possible?
}
//remove goals we couldn't decompose
for (auto goal : goalsToRemove)
vstd::erase_if_present(basicGoals, goal);
//add abstract goals
boost::sort(goalsToAdd, [](const Goals::TSubgoal & lhs, const Goals::TSubgoal & rhs) -> bool
{
return lhs->priority > rhs->priority; //highest priority at the beginning
});
//max number of goals = 10
int i = 0;
while (basicGoals.size() < 10 && goalsToAdd.size() > i)
{
if (!vstd::contains(basicGoals, goalsToAdd[i])) //don't add duplicates
basicGoals.push_back(goalsToAdd[i]);
i++;
}
}
else //no elementar goals possible
{
logAi->debug("Goal decomposition exhausted");
break;
}
}
}
void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
{
LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
@ -1099,7 +856,6 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
{
case Obj::CREATURE_GENERATOR1:
recruitCreatures(dynamic_cast<const CGDwelling *>(obj), h.get());
checkHeroArmy(h);
break;
case Obj::TOWN:
if(h->visitedTown) //we are inside, not just attacking
@ -1116,7 +872,7 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
ah->update();
}
if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
if(ah->getHeroRole(h) == HeroRole::MAIN && !h->hasSpellbook() && cb->getResourceAmount(Res::GOLD) >= GameConstants::SPELLBOOK_GOLD_COST)
{
if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
@ -1127,7 +883,6 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
makePossibleUpgrades(h.get());
break;
}
completeGoal(sptr(Goals::VisitObj(obj->id.getNum()).sethero(h)));
}
void VCAI::moveCreaturesToHero(const CGTownInstance * t)
@ -1197,10 +952,6 @@ void VCAI::pickBestCreatures(const CArmedInstance * destinationArmy, const CArme
}
//TODO - having now strongest possible army, we may want to think about arranging stacks
auto hero = dynamic_cast<const CGHeroInstance *>(destinationArmy);
if(hero)
checkHeroArmy(hero);
}
void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other)
@ -1313,7 +1064,7 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
int count = d->creatures[i].first;
CreatureID creID = d->creatures[i].second.back();
vstd::amin(count, ah->freeResources() / VLC->creh->creatures[creID]->cost);
vstd::amin(count, cb->getResourceAmount() / VLC->creh->creatures[creID]->cost);
if(count > 0)
cb->recruitCreatures(d, recruiter, creID, count, i);
}
@ -1402,250 +1153,6 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
return true;
}
void VCAI::wander(HeroPtr h)
{
auto visitTownIfAny = [this](HeroPtr h) -> bool
{
if (h->visitedTown)
{
townVisitsThisWeek[h].insert(h->visitedTown);
buildArmyIn(h->visitedTown);
return true;
}
return false;
};
//unclaim objects that are now dangerous for us
auto reservedObjsSetCopy = reservedHeroesMap[h];
for(auto obj : reservedObjsSetCopy)
{
if(!isSafeToVisit(h, obj->visitablePos()))
unreserveObject(h, obj);
}
TimeCheck tc("looking for wander destination");
while(h->movement)
{
validateVisitableObjs();
ah->updatePaths(getMyHeroes());
std::vector<ObjectIdRef> dests;
//also visit our reserved objects - but they are not prioritized to avoid running back and forth
vstd::copy_if(reservedHeroesMap[h], std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
{
return ah->isTileAccessible(h, obj->visitablePos());
});
int pass = 0;
std::vector<boost::optional<float>> distanceLimits =
{
1.0,
2.0,
boost::none
};
while(!dests.size() && pass < distanceLimits.size())
{
auto & distanceLimit = distanceLimits[pass];
logAi->debug("Looking for wander destination pass=%i, cost limit=%f", pass, distanceLimit.get_value_or(-1.0));
vstd::copy_if(visitableObjs, std::back_inserter(dests), [&](ObjectIdRef obj) -> bool
{
return isGoodForVisit(obj, h, distanceLimit);
});
pass++;
}
if(!dests.size())
{
logAi->debug("Looking for town destination");
if(cb->getVisitableObjs(h->visitablePos()).size() > 1)
moveHeroToTile(h->visitablePos(), h); //just in case we're standing on blocked subterranean gate
auto compareReinforcements = [&](const CGTownInstance * lhs, const CGTownInstance * rhs) -> bool
{
const CGHeroInstance * hptr = h.get();
auto r1 = ah->howManyReinforcementsCanGet(hptr, lhs),
r2 = ah->howManyReinforcementsCanGet(hptr, rhs);
if (r1 != r2)
return r1 < r2;
else
return ah->howManyReinforcementsCanBuy(hptr, lhs) < ah->howManyReinforcementsCanBuy(hptr, rhs);
};
std::vector<const CGTownInstance *> townsReachable;
std::vector<const CGTownInstance *> townsNotReachable;
for(const CGTownInstance * t : cb->getTownsInfo())
{
if(!t->visitingHero && !vstd::contains(townVisitsThisWeek[h], t))
{
if(isAccessibleForHero(t->visitablePos(), h))
townsReachable.push_back(t);
else
townsNotReachable.push_back(t);
}
}
if(townsReachable.size()) //travel to town with largest garrison, or empty - better than nothing
{
dests.push_back(*boost::max_element(townsReachable, compareReinforcements));
}
else if(townsNotReachable.size())
{
//TODO pick the truly best
const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements);
logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->name, t->name, t->visitablePos().toString());
int3 pos1 = h->pos;
striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop
//if out hero is stuck, we may need to request another hero to clear the way we see
if(pos1 == h->pos && h == primaryHero()) //hero can't move
{
if(canRecruitAnyHero(t))
recruitHero(t);
}
break;
}
else if(cb->getResourceAmount(Res::GOLD) >= GameConstants::HERO_GOLD_COST)
{
std::vector<const CGTownInstance *> towns = cb->getTownsInfo();
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
{
for(const CGHeroInstance * h : cb->getHeroesInfo())
{
if(!t->getArmyStrength() || ah->howManyReinforcementsCanGet(h, t))
return true;
}
return false;
});
if (towns.size())
{
recruitHero(*boost::max_element(towns, compareArmyStrength));
}
break;
}
else
{
logAi->debug("Nowhere more to go...");
break;
}
}
//end of objs empty
if(dests.size()) //performance improvement
{
Goals::TGoalVec targetObjectGoals;
for(auto destination : dests)
{
vstd::concatenate(targetObjectGoals, ah->howToVisitObj(h, destination, false));
}
if(targetObjectGoals.size())
{
auto bestObjectGoal = fh->chooseSolution(targetObjectGoals);
//wander should not cause heroes to be reserved - they are always considered free
if(bestObjectGoal->goalType == Goals::VISIT_OBJ)
{
auto chosenObject = cb->getObjInstance(ObjectInstanceID(bestObjectGoal->objid));
if(chosenObject != nullptr)
logAi->debug("Of all %d destinations, object %s at pos=%s seems nice", dests.size(), chosenObject->getObjectName(), chosenObject->pos.toString());
}
else
logAi->debug("Trying to realize goal of type %s as part of wandering.", bestObjectGoal->name());
try
{
decomposeGoal(bestObjectGoal)->accept(this);
}
catch(const goalFulfilledException & e)
{
if(e.goal->goalType == Goals::EGoals::VISIT_TILE || e.goal->goalType == Goals::EGoals::VISIT_OBJ)
continue;
throw e;
}
}
else
{
logAi->debug("Nowhere more to go...");
break;
}
visitTownIfAny(h);
}
}
visitTownIfAny(h); //in case hero is just sitting in town
}
void VCAI::setGoal(HeroPtr h, Goals::TSubgoal goal)
{
if(goal->invalid())
{
vstd::erase_if_present(lockedHeroes, h);
}
else
{
lockedHeroes[h] = goal;
goal->setisElementar(false); //Force always evaluate goals before realizing
}
}
void VCAI::evaluateGoal(HeroPtr h)
{
if(vstd::contains(lockedHeroes, h))
fh->setPriority(lockedHeroes[h]);
}
void VCAI::completeGoal(Goals::TSubgoal goal)
{
if (goal->goalType == Goals::WIN) //we can never complete this goal - unless we already won
return;
logAi->debug("Completing goal: %s", goal->name());
//notify Managers
ah->notifyGoalCompleted(goal);
//notify mainLoop()
goalsToRemove.push_back(goal); //will be removed from mainLoop() goals
for (auto basicGoal : basicGoals) //we could luckily fulfill any of our goals
{
if (basicGoal->fulfillsMe(goal))
goalsToRemove.push_back(basicGoal);
}
//unreserve heroes
if(const CGHeroInstance * h = goal->hero.get(true))
{
auto it = lockedHeroes.find(h);
if(it != lockedHeroes.end())
{
if(it->second == goal || it->second->fulfillsMe(goal)) //FIXME this is overspecified, fulfillsMe shoudl be complete
{
logAi->debug(goal->completeMessage());
lockedHeroes.erase(it); //goal fulfilled, free hero
}
}
}
else //complete goal for all heroes maybe?
{
vstd::erase_if(lockedHeroes, [goal](std::pair<HeroPtr, Goals::TSubgoal> p)
{
if(p.second == goal || p.second->fulfillsMe(goal)) //we could have fulfilled goals of other heroes by chance
{
logAi->debug(p.second->completeMessage());
return true;
}
return false;
});
}
}
void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side)
{
NET_EVENT_HANDLER;
@ -1686,19 +1193,6 @@ void VCAI::markObjectVisited(const CGObjectInstance * obj)
alreadyVisited.insert(obj);
}
void VCAI::reserveObject(HeroPtr h, const CGObjectInstance * obj)
{
reservedObjs.insert(obj);
reservedHeroesMap[h].insert(obj);
logAi->debug("reserved object id=%d; address=%p; name=%s", obj->id, obj, obj->getObjectName());
}
void VCAI::unreserveObject(HeroPtr h, const CGObjectInstance * obj)
{
vstd::erase_if_present(reservedObjs, obj); //unreserve objects
vstd::erase_if_present(reservedHeroesMap[h], obj);
}
void VCAI::markHeroUnableToExplore(HeroPtr h)
{
heroesUnableToExplore.insert(h);
@ -2053,25 +1547,15 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
}
if(h) //we could have lost hero after last move
{
completeGoal(sptr(Goals::VisitTile(dst).sethero(h))); //we stepped on some tile, anyway
completeGoal(sptr(Goals::ClearWayTo(dst).sethero(h)));
ret = ret || (dst == h->visitablePos());
if(!ret) //reserve object we are heading towards
{
auto obj = vstd::frontOrNull(cb->getVisitableObjs(dst));
if(obj && obj != *h)
reserveObject(h, obj);
}
if(startHpos == h->visitablePos() && !ret) //we didn't move and didn't reach the target
{
vstd::erase_if_present(lockedHeroes, h); //hero seemingly is confused or has only 95mp which is not enough to move
invalidPathHeroes.insert(h);
throw cannotFulfillGoalException("Invalid path found!");
}
evaluateGoal(h); //new hero position means new game situation
logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret);
}
return ret;
@ -2181,7 +1665,6 @@ void VCAI::tryRealize(Goals::DigAtTile & g)
if(g.hero->diggingStatus() == EDiggingStatus::CAN_DIG)
{
cb->dig(g.hero.get());
completeGoal(sptr(g)); // finished digging
}
else
{
@ -2192,7 +1675,7 @@ void VCAI::tryRealize(Goals::DigAtTile & g)
void VCAI::tryRealize(Goals::Trade & g) //trade
{
if(ah->freeResources()[g.resID] >= g.value) //goal is already fulfilled. Why we need this check, anyway?
if(cb->getResourceAmount((Res::ERes)g.resID) >= g.value) //goal is already fulfilled. Why we need this check, anyway?
throw goalFulfilledException(sptr(g));
int accquiredResources = 0;
@ -2200,7 +1683,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
{
if(const IMarket * m = IMarket::castFrom(obj, false))
{
auto freeRes = ah->freeResources(); //trade only resources which are not reserved
auto freeRes = cb->getResourceAmount(); //trade only resources which are not reserved
for(auto it = Res::ResourceSet::nziterator(freeRes); it.valid(); it++)
{
auto res = it->resType;
@ -2217,7 +1700,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
accquiredResources = toGet * (it->resVal / toGive);
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, accquiredResources, g.resID, obj->getObjectName());
}
if (ah->freeResources()[g.resID] >= g.value)
if (cb->getResourceAmount((Res::ERes)g.resID) >= g.value)
throw goalFulfilledException(sptr(g)); //we traded all we needed
}
@ -2255,7 +1738,7 @@ void VCAI::tryRealize(Goals::BuyArmy & g)
for (int i = 0; valueBought < g.value && i < armyToBuy.size(); i++)
{
auto res = ah->allResources();
auto res = cb->getResourceAmount();
auto & ci = armyToBuy[i];
if(g.objid != -1 && ci.creID != g.objid)
@ -2365,140 +1848,6 @@ void VCAI::endTurn()
logGlobal->info("Player %d (%s) ended turn", playerID, playerID.getStr());
}
void VCAI::striveToGoal(Goals::TSubgoal basicGoal)
{
//TODO: this function is deprecated and should be dropped altogether
auto goalToDecompose = basicGoal;
Goals::TSubgoal elementarGoal = sptr(Goals::Invalid());
int maxAbstractGoals = 10;
while (!elementarGoal->isElementar && maxAbstractGoals)
{
try
{
elementarGoal = decomposeGoal(goalToDecompose);
}
catch (goalFulfilledException & e)
{
//it is impossible to continue some goals (like exploration, for example)
completeGoal(e.goal); //put in goalsToRemove
logAi->debug("Goal %s decomposition failed: goal was completed as much as possible", e.goal->name());
return;
}
catch (std::exception & e)
{
goalsToRemove.push_back(basicGoal);
logAi->debug("Goal %s decomposition failed: %s", basicGoal->name(), e.what());
return;
}
if (elementarGoal->isAbstract) //we can decompose it further
{
goalsToAdd.push_back(elementarGoal);
//decompose further now - this is necesssary if we can't add over 10 goals in the pool
goalToDecompose = elementarGoal;
//there is a risk of infinite abstract goal loop, though it indicates failed logic
maxAbstractGoals--;
}
else if (elementarGoal->isElementar) //should be
{
logAi->debug("Found elementar goal %s", elementarGoal->name());
ultimateGoalsFromBasic[elementarGoal].push_back(goalToDecompose); //TODO: how about indirect basicGoal?
break;
}
else //should never be here
throw cannotFulfillGoalException("Goal %s is neither abstract nor elementar!" + basicGoal->name());
}
//realize best goal
if (!elementarGoal->invalid())
{
logAi->debug("Trying to realize %s (value %2.3f)", elementarGoal->name(), elementarGoal->priority);
try
{
boost::this_thread::interruption_point();
elementarGoal->accept(this); //visitor pattern
boost::this_thread::interruption_point();
}
catch (boost::thread_interrupted & e)
{
logAi->debug("Player %d: Making turn thread received an interruption!", playerID);
throw; //rethrow, we want to truly end this thread
}
catch (goalFulfilledException & e)
{
//the sub-goal was completed successfully
completeGoal(e.goal);
//local goal was also completed
completeGoal(elementarGoal);
}
catch (std::exception & e)
{
logAi->debug("Failed to realize subgoal of type %s, I will stop.", elementarGoal->name());
logAi->debug("The error message was: %s", e.what());
//erase base goal if we failed to execute decomposed goal
for (auto basicGoalToRemove : ultimateGoalsFromBasic[elementarGoal])
goalsToRemove.push_back(basicGoalToRemove);
}
}
}
Goals::TSubgoal VCAI::decomposeGoal(Goals::TSubgoal ultimateGoal)
{
if(ultimateGoal->isElementar)
{
logAi->warn("Trying to decompose elementar goal %s", ultimateGoal->name());
return ultimateGoal;
}
const int searchDepth = 30;
Goals::TSubgoal goal = ultimateGoal;
logAi->debug("Decomposing goal %s", ultimateGoal->name());
int maxGoals = searchDepth; //preventing deadlock for mutually dependent goals
while (maxGoals)
{
boost::this_thread::interruption_point();
goal = goal->whatToDoToAchieve(); //may throw if decomposition fails
--maxGoals;
if (goal == ultimateGoal) //compare objects by value
if (goal->isElementar == ultimateGoal->isElementar)
throw cannotFulfillGoalException((boost::format("Goal dependency loop detected for %s!")
% ultimateGoal->name()).str());
if (goal->isAbstract || goal->isElementar)
return goal;
else
logAi->debug("Considering: %s", goal->name());
}
throw cannotFulfillGoalException("Too many subgoals, don't know what to do");
}
void VCAI::performTypicalActions()
{
for(auto h : getUnblockedHeroes())
{
if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
continue;
logAi->debug("Hero %s started wandering, MP=%d", h->name.c_str(), h->movement);
makePossibleUpgrades(*h);
pickBestArtifacts(*h);
try
{
wander(h);
}
catch(std::exception & e)
{
logAi->debug("Cannot use this hero anymore, received exception: %s", e.what());
continue;
}
}
}
void VCAI::buildArmyIn(const CGTownInstance * t)
{
makePossibleUpgrades(t->visitingHero);
@ -2507,16 +1856,6 @@ void VCAI::buildArmyIn(const CGTownInstance * t)
moveCreaturesToHero(t);
}
void VCAI::checkHeroArmy(HeroPtr h)
{
auto it = lockedHeroes.find(h);
if(it != lockedHeroes.end())
{
if(it->second->goalType == Goals::GATHER_ARMY && it->second->value <= h->getArmyStrength())
completeGoal(sptr(Goals::GatherArmy(it->second->value).sethero(h)));
}
}
void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
{
logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString());
@ -2547,6 +1886,8 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
void VCAI::finish()
{
//we want to lock to avoid multiple threads from calling makingTurn->join() at same time
boost::lock_guard<boost::mutex> multipleCleanupGuard(turnInterruptionMutex);
if(makingTurn)
{
makingTurn->interrupt();
@ -2570,41 +1911,11 @@ void VCAI::lostHero(HeroPtr h)
{
logAi->debug("I lost my hero %s. It's best to forget and move on.", h.name);
vstd::erase_if_present(lockedHeroes, h);
for(auto obj : reservedHeroesMap[h])
{
vstd::erase_if_present(reservedObjs, obj); //unreserve all objects for that hero
}
vstd::erase_if_present(reservedHeroesMap, h);
vstd::erase_if_present(visitedHeroes, h);
for (auto heroVec : visitedHeroes)
{
vstd::erase_if_present(heroVec.second, h);
}
//remove goals with removed hero assigned from main loop
vstd::erase_if(ultimateGoalsFromBasic, [&](const std::pair<Goals::TSubgoal, Goals::TGoalVec> & x) -> bool
{
if(x.first->hero == h)
return true;
else
return false;
});
auto removedHeroGoalPredicate = [&](const Goals::TSubgoal & x) ->bool
{
if(x->hero == h)
return true;
else
return false;
};
vstd::erase_if(basicGoals, removedHeroGoalPredicate);
vstd::erase_if(goalsToAdd, removedHeroGoalPredicate);
vstd::erase_if(goalsToRemove, removedHeroGoalPredicate);
for(auto goal : ultimateGoalsFromBasic)
vstd::erase_if(goal.second, removedHeroGoalPredicate);
}
void VCAI::answerQuery(QueryID queryID, int selection)
@ -2914,7 +2225,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
case Obj::SCHOOL_OF_MAGIC:
case Obj::SCHOOL_OF_WAR:
{
if (ai->ah->freeGold() < 1000)
if (cb->getResourceAmount(Res::GOLD) < 1000)
return false;
break;
}
@ -2924,7 +2235,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
break;
case Obj::TREE_OF_KNOWLEDGE:
{
TResources myRes = ai->ah->freeResources();
TResources myRes = cb->getResourceAmount();
if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
return false;
break;
@ -2939,7 +2250,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
//TODO: only on request
if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
return false;
else if(ai->ah->freeGold() < GameConstants::HERO_GOLD_COST)
else if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
return false;
break;
}

View File

@ -13,9 +13,7 @@
#include "Goals/AbstractGoal.h"
#include "../../lib/AI_Base.h"
#include "../../CCallback.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/GameConstants.h"
#include "../../lib/VCMI_Lib.h"
#include "../../lib/CBuildingHandler.h"
@ -90,12 +88,6 @@ public:
//std::vector<const CGObjectInstance *> visitedThisWeek; //only OPWs
std::map<HeroPtr, std::set<const CGTownInstance *>> townVisitsThisWeek;
//part of mainLoop, but accessible from outisde
std::vector<Goals::TSubgoal> basicGoals;
Goals::TGoalVec goalsToRemove;
Goals::TGoalVec goalsToAdd;
std::map<Goals::TSubgoal, Goals::TGoalVec> ultimateGoalsFromBasic; //theoreticlaly same goal can fulfill multiple basic goals
std::set<HeroPtr> invalidPathHeroes; //FIXME, just a workaround
std::map<HeroPtr, Goals::TSubgoal> lockedHeroes; //TODO: allow non-elementar objectives
std::map<HeroPtr, std::set<const CGObjectInstance *>> reservedHeroesMap; //objects reserved by specific heroes
@ -113,6 +105,9 @@ public:
std::shared_ptr<CCallback> myCb;
std::unique_ptr<boost::thread> makingTurn;
private:
boost::mutex turnInterruptionMutex;
public:
ObjectInstanceID selectedObject;
AIhelper * ah;
@ -199,17 +194,9 @@ public:
void battleEnd(const BattleResult * br) override;
void makeTurn();
void mainLoop();
void performTypicalActions();
void buildArmyIn(const CGTownInstance * t);
void striveToGoal(Goals::TSubgoal ultimateGoal);
Goals::TSubgoal decomposeGoal(Goals::TSubgoal ultimateGoal);
void endTurn();
void wander(HeroPtr h);
void setGoal(HeroPtr h, Goals::TSubgoal goal);
void evaluateGoal(HeroPtr h); //evaluates goal assigned to hero, if any
void completeGoal(Goals::TSubgoal goal); //safely removes goal from reserved hero
void recruitHero(const CGTownInstance * t, bool throwing = false);
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, boost::optional<float> movementCostLimit = boost::none);
@ -230,8 +217,6 @@ public:
void addVisitableObj(const CGObjectInstance * obj);
void markObjectVisited(const CGObjectInstance * obj);
void reserveObject(HeroPtr h, const CGObjectInstance * obj); //TODO: reserve all objects that heroes attempt to visit
void unreserveObject(HeroPtr h, const CGObjectInstance * obj);
void markHeroUnableToExplore(HeroPtr h);
void markHeroAbleToExplore(HeroPtr h);
@ -261,7 +246,6 @@ public:
std::vector<HeroPtr> getUnblockedHeroes() const;
std::vector<HeroPtr> getMyHeroes() const;
HeroPtr primaryHero() const;
void checkHeroArmy(HeroPtr h);
void requestSent(const CPackForServer * pack, int requestID) override;
void answerQuery(QueryID queryID, int selection);

View File

@ -21,7 +21,7 @@
<PropertyGroup Label="Globals">
<ProjectGuid>{276C3DB0-7A6B-4417-8E5C-322B08633AAC}</ProjectGuid>
<RootNamespace>StupidAI</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@ -41,7 +41,7 @@
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
@ -117,7 +117,7 @@
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<Optimization>MaxSpeed</Optimization>
<Optimization>Disabled</Optimization>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;FuzzyLite.lib;%(AdditionalDependencies)</AdditionalDependencies>