1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Nullkiller: copy VCAI

This commit is contained in:
Andrii Danylchenko 2021-05-15 19:22:44 +03:00 committed by Andrii Danylchenko
parent e407d4e547
commit be4f803d4a
95 changed files with 13249 additions and 0 deletions

388
AI/Nullkiller/AIUtility.cpp Normal file
View File

@ -0,0 +1,388 @@
/*
* AIUtility.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 "AIUtility.h"
#include "VCAI.h"
#include "FuzzyHelper.h"
#include "Goals/Goals.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/mapObjects/CBank.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/CQuest.h"
#include "../../lib/mapping/CMapDefines.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
//extern static const int3 dirs[8];
const CGObjectInstance * ObjectIdRef::operator->() const
{
return cb->getObj(id, false);
}
ObjectIdRef::operator const CGObjectInstance *() const
{
return cb->getObj(id, false);
}
ObjectIdRef::operator bool() const
{
return cb->getObj(id, false);
}
ObjectIdRef::ObjectIdRef(ObjectInstanceID _id)
: id(_id)
{
}
ObjectIdRef::ObjectIdRef(const CGObjectInstance * obj)
: id(obj->id)
{
}
bool ObjectIdRef::operator<(const ObjectIdRef & rhs) const
{
return id < rhs.id;
}
HeroPtr::HeroPtr(const CGHeroInstance * H)
{
if(!H)
{
//init from nullptr should equal to default init
*this = HeroPtr();
return;
}
h = H;
name = h->name;
hid = H->id;
// infosCount[ai->playerID][hid]++;
}
HeroPtr::HeroPtr()
{
h = nullptr;
hid = ObjectInstanceID();
}
HeroPtr::~HeroPtr()
{
// if(hid >= 0)
// infosCount[ai->playerID][hid]--;
}
bool HeroPtr::operator<(const HeroPtr & rhs) const
{
return hid < rhs.hid;
}
const CGHeroInstance * HeroPtr::get(bool doWeExpectNull) const
{
//TODO? check if these all assertions every time we get info about hero affect efficiency
//
//behave terribly when attempting unauthorized access to hero that is not ours (or was lost)
assert(doWeExpectNull || h);
if(h)
{
auto obj = cb->getObj(hid);
const bool owned = obj && obj->tempOwner == ai->playerID;
if(doWeExpectNull && !owned)
{
return nullptr;
}
else
{
assert(obj);
assert(owned);
}
}
return h;
}
const CGHeroInstance * HeroPtr::operator->() const
{
return get();
}
bool HeroPtr::validAndSet() const
{
return get(true);
}
const CGHeroInstance * HeroPtr::operator*() const
{
return get();
}
bool HeroPtr::operator==(const HeroPtr & rhs) const
{
return h == rhs.get(true);
}
void foreach_tile_pos(std::function<void(const int3 & pos)> foo)
{
// some micro-optimizations since this function gets called a LOT
// callback pointer is thread-specific and slow to retrieve -> read map size only once
int3 mapSize = cb->getMapSize();
for(int i = 0; i < mapSize.x; i++)
{
for(int j = 0; j < mapSize.y; j++)
{
for(int k = 0; k < mapSize.z; k++)
foo(int3(i, j, k));
}
}
}
void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const int3 & pos)> foo)
{
int3 mapSize = cbp->getMapSize();
for(int i = 0; i < mapSize.x; i++)
{
for(int j = 0; j < mapSize.y; j++)
{
for(int k = 0; k < mapSize.z; k++)
foo(cbp, int3(i, j, k));
}
}
}
void foreach_neighbour(const int3 & pos, std::function<void(const int3 & pos)> foo)
{
CCallback * cbp = cb.get(); // avoid costly retrieval of thread-specific pointer
for(const int3 & dir : int3::getDirs())
{
const int3 n = pos + dir;
if(cbp->isInTheMap(n))
foo(pos + dir);
}
}
void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCallback * cbp, const int3 & pos)> foo)
{
for(const int3 & dir : int3::getDirs())
{
const int3 n = pos + dir;
if(cbp->isInTheMap(n))
foo(cbp, pos + dir);
}
}
bool CDistanceSorter::operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const
{
const CGPathNode * ln = ai->myCb->getPathsInfo(hero)->getPathInfo(lhs->visitablePos());
const CGPathNode * rn = ai->myCb->getPathsInfo(hero)->getPathInfo(rhs->visitablePos());
return ln->cost < rn->cost;
}
bool isSafeToVisit(HeroPtr h, crint3 tile)
{
return isSafeToVisit(h, fh->evaluateDanger(tile, h.get()));
}
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength)
{
const ui64 heroStrength = h->getTotalStrength();
if(dangerStrength)
{
if(heroStrength / SAFE_ATTACK_CONSTANT > dangerStrength)
{
return true;
}
else
{
return false;
}
}
return true; //there's no danger
}
bool isObjectRemovable(const CGObjectInstance * obj)
{
//FIXME: move logic to object property!
switch (obj->ID)
{
case Obj::MONSTER:
case Obj::RESOURCE:
case Obj::CAMPFIRE:
case Obj::TREASURE_CHEST:
case Obj::ARTIFACT:
case Obj::BORDERGUARD:
case Obj::FLOTSAM:
case Obj::PANDORAS_BOX:
case Obj::OCEAN_BOTTLE:
case Obj::SEA_CHEST:
case Obj::SHIPWRECK_SURVIVOR:
case Obj::SPELL_SCROLL:
return true;
break;
default:
return false;
break;
}
}
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
{
// TODO: Such information should be provided by pathfinder
// Tile must be free or with unoccupied boat
if(!t->blocked)
{
return true;
}
else if(!fromWater) // do not try to board when in water sector
{
if(t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
return true;
}
return false;
}
bool isBlockedBorderGate(int3 tileToHit) //TODO: is that function needed? should be handled by pathfinder
{
if(cb->getTile(tileToHit)->topVisitableId() != Obj::BORDER_GATE)
return false;
auto gate = dynamic_cast<const CGKeys *>(cb->getTile(tileToHit)->topVisitableObj());
return !gate->passableFor(ai->playerID);
}
bool isBlockVisitObj(const int3 & pos)
{
if(auto obj = cb->getTopObj(pos))
{
if(obj->blockVisit) //we can't stand on that object
return true;
}
return false;
}
creInfo infoFromDC(const dwellingContent & dc)
{
creInfo ci;
ci.count = dc.first;
ci.creID = dc.second.size() ? dc.second.back() : CreatureID(-1); //should never be accessed
if (ci.creID != -1)
{
ci.cre = VLC->creh->creatures[ci.creID];
ci.level = ci.cre->level; //this is cretaure tier, while tryRealize expects dwelling level. Ignore.
}
else
{
ci.cre = nullptr;
ci.level = 0;
}
return ci;
}
ui64 howManyReinforcementsCanBuy(const CArmedInstance * h, const CGDwelling * t)
{
ui64 aivalue = 0;
TResources availableRes = cb->getResourceAmount();
int freeHeroSlots = GameConstants::ARMY_SIZE - h->stacksCount();
for(auto const dc : t->creatures)
{
creInfo ci = infoFromDC(dc);
if(!ci.count || ci.creID == -1)
continue;
vstd::amin(ci.count, availableRes / ci.cre->cost); //max count we can afford
if(ci.count && ci.creID != -1) //valid creature at this level
{
//can be merged with another stack?
SlotID dst = h->getSlotFor(ci.creID);
if(!h->hasStackAtSlot(dst)) //need another new slot for this stack
{
if(!freeHeroSlots) //no more place for stacks
continue;
else
freeHeroSlots--; //new slot will be occupied
}
//we found matching occupied or free slot
aivalue += ci.count * ci.cre->AIValue;
availableRes -= ci.cre->cost * ci.count;
}
}
return aivalue;
}
ui64 howManyReinforcementsCanGet(const CArmedInstance * h, const CGTownInstance * t)
{
ui64 ret = 0;
int freeHeroSlots = GameConstants::ARMY_SIZE - h->stacksCount();
std::vector<const CStackInstance *> toMove;
for(auto const slot : t->Slots())
{
//can be merged woth another stack?
SlotID dst = h->getSlotFor(slot.second->getCreatureID());
if(h->hasStackAtSlot(dst))
ret += t->getPower(slot.first);
else
toMove.push_back(slot.second);
}
boost::sort(toMove, [](const CStackInstance * lhs, const CStackInstance * rhs)
{
return lhs->getPower() < rhs->getPower();
});
for(auto & stack : boost::adaptors::reverse(toMove))
{
if(freeHeroSlots)
{
ret += stack->getPower();
freeHeroSlots--;
}
else
break;
}
return ret;
}
bool compareHeroStrength(HeroPtr h1, HeroPtr h2)
{
return h1->getTotalStrength() < h2->getTotalStrength();
}
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
{
return a1->getArmyStrength() < a2->getArmyStrength();
}
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
{
auto art1 = a1->artType;
auto art2 = a2->artType;
if(art1->price == art2->price)
return art1->valOfBonuses(Bonus::PRIMARY_SKILL) > art2->valOfBonuses(Bonus::PRIMARY_SKILL);
else if(art1->price > art2->price)
return true;
else
return false;
}

189
AI/Nullkiller/AIUtility.h Normal file
View File

@ -0,0 +1,189 @@
/*
* AIUtility.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/VCMI_Lib.h"
#include "../../lib/CBuildingHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CStopWatch.h"
#include "../../lib/mapObjects/CObjectHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/CPathfinder.h"
class CCallback;
struct creInfo;
typedef const int3 & crint3;
typedef const std::string & crstring;
typedef std::pair<ui32, std::vector<CreatureID>> dwellingContent;
const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
const int ACTUAL_RESOURCE_COUNT = 7;
const int ALLOWED_ROAMING_HEROES = 8;
//implementation-dependent
extern const double SAFE_ATTACK_CONSTANT;
extern const int GOLD_RESERVE;
//provisional class for AI to store a reference to an owned hero object
//checks if it's valid on access, should be used in place of const CGHeroInstance*
struct DLL_EXPORT HeroPtr
{
const CGHeroInstance * h;
ObjectInstanceID hid;
public:
std::string name;
HeroPtr();
HeroPtr(const CGHeroInstance * H);
~HeroPtr();
operator bool() const
{
return validAndSet();
}
bool operator<(const HeroPtr & rhs) const;
const CGHeroInstance * operator->() const;
const CGHeroInstance * operator*() const; //not that consistent with -> but all interfaces use CGHeroInstance*, so it's convenient
bool operator==(const HeroPtr & rhs) const;
bool operator!=(const HeroPtr & rhs) const
{
return !(*this == rhs);
}
const CGHeroInstance * get(bool doWeExpectNull = false) const;
bool validAndSet() const;
template<typename Handler> void serialize(Handler & h, const int version)
{
h & this->h;
h & hid;
h & name;
}
};
enum BattleState
{
NO_BATTLE,
UPCOMING_BATTLE,
ONGOING_BATTLE,
ENDING_BATTLE
};
// AI lives in a dangerous world. CGObjectInstances under pointer may got deleted/hidden.
// This class stores object id, so we can detect when we lose access to the underlying object.
struct ObjectIdRef
{
ObjectInstanceID id;
const CGObjectInstance * operator->() const;
operator const CGObjectInstance *() const;
operator bool() const;
ObjectIdRef(ObjectInstanceID _id);
ObjectIdRef(const CGObjectInstance * obj);
bool operator<(const ObjectIdRef & rhs) const;
template<typename Handler> void serialize(Handler & h, const int version)
{
h & id;
}
};
struct TimeCheck
{
CStopWatch time;
std::string txt;
TimeCheck(crstring TXT)
: txt(TXT)
{
}
~TimeCheck()
{
logAi->trace("Time of %s was %d ms.", txt, time.getDiff());
}
};
//TODO: replace with vstd::
struct AtScopeExit
{
std::function<void()> foo;
AtScopeExit(const std::function<void()> & FOO)
: foo(FOO)
{}
~AtScopeExit()
{
foo();
}
};
class ObjsVector : public std::vector<ObjectIdRef>
{
};
template<int id>
bool objWithID(const CGObjectInstance * obj)
{
return obj->ID == id;
}
struct creInfo
{
int count;
CreatureID creID;
CCreature * cre;
int level;
};
creInfo infoFromDC(const dwellingContent & dc);
void foreach_tile_pos(std::function<void(const int3 & pos)> foo);
void foreach_tile_pos(CCallback * cbp, std::function<void(CCallback * cbp, const int3 & pos)> foo); // avoid costly retrieval of thread-specific pointer
void foreach_neighbour(const int3 & pos, std::function<void(const int3 & pos)> foo);
void foreach_neighbour(CCallback * cbp, const int3 & pos, std::function<void(CCallback * cbp, const int3 & pos)> foo); // avoid costly retrieval of thread-specific pointer
bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater);
bool isBlockedBorderGate(int3 tileToHit);
bool isBlockVisitObj(const int3 & pos);
bool isWeeklyRevisitable(const CGObjectInstance * obj);
bool shouldVisit(HeroPtr h, const CGObjectInstance * obj);
bool isObjectRemovable(const CGObjectInstance * obj); //FIXME FIXME: move logic to object property!
bool isSafeToVisit(HeroPtr h, uint64_t dangerStrength);
bool isSafeToVisit(HeroPtr h, crint3 tile);
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
ui64 howManyReinforcementsCanBuy(const CArmedInstance * h, const CGDwelling * t);
ui64 howManyReinforcementsCanGet(const CArmedInstance * h, const CGTownInstance * t);
class CDistanceSorter
{
const CGHeroInstance * hero;
public:
CDistanceSorter(const CGHeroInstance * hero)
: hero(hero)
{
}
bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs) const;
};

159
AI/Nullkiller/AIhelper.cpp Normal file
View File

@ -0,0 +1,159 @@
/*
* AIhelper.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
*
*/
#include "StdInc.h"
#include "AIhelper.h"
#include "ResourceManager.h"
#include "BuildingManager.h"
AIhelper::AIhelper()
{
resourceManager.reset(new ResourceManager());
buildingManager.reset(new BuildingManager());
pathfindingManager.reset(new PathfindingManager());
}
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);
}
void AIhelper::setAI(VCAI * AI)
{
resourceManager->setAI(AI);
buildingManager->setAI(AI);
pathfindingManager->setAI(AI);
}
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) const
{
return pathfindingManager->howToVisitTile(tile);
}
Goals::TGoalVec AIhelper::howToVisitObj(ObjectIdRef obj) const
{
return pathfindingManager->howToVisitObj(obj);
}
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);
}
void AIhelper::updatePaths(std::vector<HeroPtr> heroes)
{
pathfindingManager->updatePaths(heroes);
}
void AIhelper::updatePaths(const HeroPtr & hero)
{
pathfindingManager->updatePaths(hero);
}

78
AI/Nullkiller/AIhelper.h Normal file
View File

@ -0,0 +1,78 @@
/*
* AIhelper.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
/*
!!! Note: Include THIS file at the end of include list to avoid "undefined base class" error
*/
#include "ResourceManager.h"
#include "BuildingManager.h"
#include "Pathfinding/PathfindingManager.h"
class ResourceManager;
class BuildingManager;
//indirection interface for various modules
class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager, public IPathfindingManager
{
friend class VCAI;
friend struct SetGlobalState; //mess?
std::shared_ptr<ResourceManager> resourceManager;
std::shared_ptr<BuildingManager> buildingManager;
std::shared_ptr<PathfindingManager> pathfindingManager;
//TODO: vector<IAbstractManager>
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) const override;
Goals::TGoalVec howToVisitObj(ObjectIdRef obj) const override;
std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
void updatePaths(std::vector<HeroPtr> heroes) override;
void updatePaths(const HeroPtr & hero) override;
STRONG_INLINE
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const
{
return pathfindingManager->isTileAccessible(hero, tile);
}
private:
bool notifyGoalCompleted(Goals::TSubgoal goal) override;
void init(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
};

View File

@ -0,0 +1,260 @@
/*
* 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);
}
if (tryBuildAnyStructure(t, extraBuildings))
return true;
return false;
}
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

@ -0,0 +1,75 @@
/*
* 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

@ -0,0 +1,127 @@
if(FL_FOUND)
include_directories(${FL_INCLUDE_DIRS})
else()
include_directories(${CMAKE_HOME_DIRECTORY}/AI/FuzzyLite/fuzzylite)
endif()
include_directories(${Boost_INCLUDE_DIRS} ${CMAKE_HOME_DIRECTORY} ${CMAKE_HOME_DIRECTORY}/include ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_HOME_DIRECTORY}/lib)
set(VCAI_SRCS
StdInc.cpp
Pathfinding/AIPathfinderConfig.cpp
Pathfinding/AIPathfinder.cpp
Pathfinding/AINodeStorage.cpp
Pathfinding/PathfindingManager.cpp
Pathfinding/Actions/BattleAction.cpp
Pathfinding/Actions/BoatActions.cpp
Pathfinding/Actions/TownPortalAction.cpp
Pathfinding/Rules/AILayerTransitionRule.cpp
Pathfinding/Rules/AIMovementAfterDestinationRule.cpp
Pathfinding/Rules/AIMovementToDestinationRule.cpp
Pathfinding/Rules/AIPreviousNodeRule.cpp
AIUtility.cpp
AIhelper.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/GatherTroops.cpp
Goals/BuyArmy.cpp
Goals/AdventureSpellCast.cpp
Goals/Win.cpp
Goals/VisitTile.cpp
Goals/VisitObj.cpp
Goals/VisitHero.cpp
Goals/CollectRes.cpp
Goals/Trade.cpp
Goals/RecruitHero.cpp
Goals/Conquer.cpp
Goals/ClearWayTo.cpp
Goals/DigAtTile.cpp
Goals/GetArtOfType.cpp
Goals/FindObj.cpp
Goals/CompleteQuest.cpp
main.cpp
VCAI.cpp
)
set(VCAI_HEADERS
StdInc.h
Pathfinding/AIPathfinderConfig.h
Pathfinding/AIPathfinder.h
Pathfinding/AINodeStorage.h
Pathfinding/PathfindingManager.h
Pathfinding/Actions/ISpecialAction.h
Pathfinding/Actions/BattleAction.h
Pathfinding/Actions/BoatActions.h
Pathfinding/Actions/TownPortalAction.h
Pathfinding/Rules/AILayerTransitionRule.h
Pathfinding/Rules/AIMovementAfterDestinationRule.h
Pathfinding/Rules/AIMovementToDestinationRule.h
Pathfinding/Rules/AIPreviousNodeRule.h
AIUtility.h
AIhelper.h
ResourceManager.h
BuildingManager.h
SectorMap.h
BuildingManager.h
MapObjectsEvaluator.h
FuzzyEngines.h
FuzzyHelper.h
Goals/AbstractGoal.h
Goals/CGoal.h
Goals/Invalid.h
Goals/BuildBoat.h
Goals/Build.h
Goals/BuildThis.h
Goals/Explore.h
Goals/GatherArmy.h
Goals/GatherTroops.h
Goals/BuyArmy.h
Goals/AdventureSpellCast.h
Goals/Win.h
Goals/VisitTile.h
Goals/VisitObj.h
Goals/VisitHero.h
Goals/CollectRes.h
Goals/Trade.h
Goals/RecruitHero.h
Goals/Conquer.h
Goals/ClearWayTo.h
Goals/DigAtTile.h
Goals/GetArtOfType.h
Goals/FindObj.h
Goals/CompleteQuest.h
Goals/Goals.h
VCAI.h
)
assign_source_group(${VCAI_SRCS} ${VCAI_HEADERS})
if(ANDROID) # android compiles ai libs into main lib directly, so we skip this library and just reuse sources list
return()
endif()
add_library(VCAI SHARED ${VCAI_SRCS} ${VCAI_HEADERS})
if(FL_FOUND)
target_link_libraries(VCAI ${FL_LIBRARIES} vcmi)
else()
target_link_libraries(VCAI fl-static vcmi)
endif()
vcmi_set_output_dir(VCAI "AI")
set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})
cotire(VCAI)
install(TARGETS VCAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})

View File

@ -0,0 +1,458 @@
/*
* FuzzyEngines.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 "FuzzyEngines.h"
#include "Goals/Goals.h"
#include "../../lib/mapObjects/MapObjects.h"
#include "VCAI.h"
#include "MapObjectsEvaluator.h"
#define MIN_AI_STRENGTH (0.5f) //lower when combat AI gets smarter
#define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
engineBase::engineBase()
{
engine.addRuleBlock(&rules);
}
void engineBase::configure()
{
engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "Proportional");
logAi->info(engine.toString());
}
void engineBase::addRule(const std::string & txt)
{
rules.addRule(fl::Rule::parse(txt, &engine));
}
struct armyStructure
{
float walkers, shooters, flyers;
ui32 maxSpeed;
};
armyStructure evaluateArmyStructure(const CArmedInstance * army)
{
ui64 totalStrenght = army->getArmyStrength();
double walkersStrenght = 0;
double flyersStrenght = 0;
double shootersStrenght = 0;
ui32 maxSpeed = 0;
static const CSelector selectorSHOOTER = Selector::type(Bonus::SHOOTER);
static const std::string keySHOOTER = "type_"+std::to_string((int32_t)Bonus::SHOOTER);
static const CSelector selectorFLYING = Selector::type(Bonus::FLYING);
static const std::string keyFLYING = "type_"+std::to_string((int32_t)Bonus::FLYING);
static const CSelector selectorSTACKS_SPEED = Selector::type(Bonus::STACKS_SPEED);
static const std::string keySTACKS_SPEED = "type_"+std::to_string((int32_t)Bonus::STACKS_SPEED);
for(auto s : army->Slots())
{
bool walker = true;
const CCreature * creature = s.second->type;
if(creature->hasBonus(selectorSHOOTER, keySHOOTER))
{
shootersStrenght += s.second->getPower();
walker = false;
}
if(creature->hasBonus(selectorFLYING, keyFLYING))
{
flyersStrenght += s.second->getPower();
walker = false;
}
if(walker)
walkersStrenght += s.second->getPower();
vstd::amax(maxSpeed, creature->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED));
}
armyStructure as;
as.walkers = walkersStrenght / totalStrenght;
as.shooters = shootersStrenght / totalStrenght;
as.flyers = flyersStrenght / totalStrenght;
as.maxSpeed = maxSpeed;
assert(as.walkers || as.flyers || as.shooters);
return as;
}
float HeroMovementGoalEngineBase::calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const
{
if(goal.evaluationContext.movementCost > 0)
{
return goal.evaluationContext.movementCost;
}
else
{
auto pathInfo = ai->myCb->getPathsInfo(goal.hero.h)->getPathInfo(goal.tile);
return pathInfo->cost;
}
}
TacticalAdvantageEngine::TacticalAdvantageEngine()
{
try
{
ourShooters = new fl::InputVariable("OurShooters");
ourWalkers = new fl::InputVariable("OurWalkers");
ourFlyers = new fl::InputVariable("OurFlyers");
enemyShooters = new fl::InputVariable("EnemyShooters");
enemyWalkers = new fl::InputVariable("EnemyWalkers");
enemyFlyers = new fl::InputVariable("EnemyFlyers");
//Tactical advantage calculation
std::vector<fl::InputVariable *> helper =
{
ourShooters, ourWalkers, ourFlyers, enemyShooters, enemyWalkers, enemyFlyers
};
for(auto val : helper)
{
engine.addInputVariable(val);
val->addTerm(new fl::Ramp("FEW", 0.6, 0.0));
val->addTerm(new fl::Ramp("MANY", 0.4, 1));
val->setRange(0.0, 1.0);
}
ourSpeed = new fl::InputVariable("OurSpeed");
enemySpeed = new fl::InputVariable("EnemySpeed");
helper = { ourSpeed, enemySpeed };
for(auto val : helper)
{
engine.addInputVariable(val);
val->addTerm(new fl::Ramp("LOW", 6.5, 3));
val->addTerm(new fl::Triangle("MEDIUM", 5.5, 10.5));
val->addTerm(new fl::Ramp("HIGH", 8.5, 16));
val->setRange(0, 25);
}
castleWalls = new fl::InputVariable("CastleWalls");
engine.addInputVariable(castleWalls);
{
fl::Rectangle * none = new fl::Rectangle("NONE", CGTownInstance::NONE, CGTownInstance::NONE + (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f);
castleWalls->addTerm(none);
fl::Trapezoid * medium = new fl::Trapezoid("MEDIUM", (CGTownInstance::FORT - CGTownInstance::NONE) * 0.5f, CGTownInstance::FORT,
CGTownInstance::CITADEL, CGTownInstance::CITADEL + (CGTownInstance::CASTLE - CGTownInstance::CITADEL) * 0.5f);
castleWalls->addTerm(medium);
fl::Ramp * high = new fl::Ramp("HIGH", CGTownInstance::CITADEL - 0.1, CGTownInstance::CASTLE);
castleWalls->addTerm(high);
castleWalls->setRange(CGTownInstance::NONE, CGTownInstance::CASTLE);
}
bankPresent = new fl::InputVariable("Bank");
engine.addInputVariable(bankPresent);
{
fl::Rectangle * termFalse = new fl::Rectangle("FALSE", 0.0, 0.5f);
bankPresent->addTerm(termFalse);
fl::Rectangle * termTrue = new fl::Rectangle("TRUE", 0.5f, 1);
bankPresent->addTerm(termTrue);
bankPresent->setRange(0, 1);
}
threat = new fl::OutputVariable("Threat");
engine.addOutputVariable(threat);
threat->addTerm(new fl::Ramp("LOW", 1, MIN_AI_STRENGTH));
threat->addTerm(new fl::Triangle("MEDIUM", 0.8, 1.2));
threat->addTerm(new fl::Ramp("HIGH", 1, 1.5));
threat->setRange(MIN_AI_STRENGTH, 1.5);
addRule("if OurShooters is MANY and EnemySpeed is LOW then Threat is LOW");
addRule("if OurShooters is MANY and EnemyShooters is FEW then Threat is LOW");
addRule("if OurSpeed is LOW and EnemyShooters is MANY then Threat is HIGH");
addRule("if OurSpeed is HIGH and EnemyShooters is MANY then Threat is LOW");
addRule("if OurWalkers is FEW and EnemyShooters is MANY then Threat is LOW");
addRule("if OurShooters is MANY and EnemySpeed is HIGH then Threat is HIGH");
//just to cover all cases
addRule("if OurShooters is FEW and EnemySpeed is HIGH then Threat is MEDIUM");
addRule("if EnemySpeed is MEDIUM then Threat is MEDIUM");
addRule("if EnemySpeed is LOW and OurShooters is FEW then Threat is MEDIUM");
addRule("if Bank is TRUE and OurShooters is MANY then Threat is HIGH");
addRule("if Bank is TRUE and EnemyShooters is MANY then Threat is LOW");
addRule("if CastleWalls is HIGH and OurWalkers is MANY then Threat is HIGH");
addRule("if CastleWalls is HIGH and OurFlyers is MANY and OurShooters is MANY then Threat is MEDIUM");
addRule("if CastleWalls is MEDIUM and OurShooters is MANY and EnemyWalkers is MANY then Threat is LOW");
}
catch(fl::Exception & pe)
{
logAi->error("initTacticalAdvantage: %s", pe.getWhat());
}
configure();
}
float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy)
{
float output = 1;
/*try //TODO: rework this engine, it tends to produce nonsense output
{
armyStructure ourStructure = evaluateArmyStructure(we);
armyStructure enemyStructure = evaluateArmyStructure(enemy);
ourWalkers->setValue(ourStructure.walkers);
ourShooters->setValue(ourStructure.shooters);
ourFlyers->setValue(ourStructure.flyers);
ourSpeed->setValue(ourStructure.maxSpeed);
enemyWalkers->setValue(enemyStructure.walkers);
enemyShooters->setValue(enemyStructure.shooters);
enemyFlyers->setValue(enemyStructure.flyers);
enemySpeed->setValue(enemyStructure.maxSpeed);
bool bank = dynamic_cast<const CBank *>(enemy);
if(bank)
bankPresent->setValue(1);
else
bankPresent->setValue(0);
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
if(fort)
castleWalls->setValue(fort->fortLevel());
else
castleWalls->setValue(0);
engine.process();
output = threat->getValue();
}
catch(fl::Exception & fe)
{
logAi->error("getTacticalAdvantage: %s ", fe.getWhat());
}
if(output < 0 || (output != output))
{
fl::InputVariable * tab[] = { bankPresent, castleWalls, ourWalkers, ourShooters, ourFlyers, ourSpeed, enemyWalkers, enemyShooters, enemyFlyers, enemySpeed };
std::string names[] = { "bankPresent", "castleWalls", "ourWalkers", "ourShooters", "ourFlyers", "ourSpeed", "enemyWalkers", "enemyShooters", "enemyFlyers", "enemySpeed" };
std::stringstream log("Warning! Fuzzy engine doesn't cover this set of parameters: ");
for(int i = 0; i < boost::size(tab); i++)
log << names[i] << ": " << tab[i]->getValue() << " ";
logAi->error(log.str());
assert(false);
}*/
return output;
}
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec)
HeroMovementGoalEngineBase::HeroMovementGoalEngineBase()
{
try
{
strengthRatio = new fl::InputVariable("strengthRatio"); //hero must be strong enough to defeat guards
heroStrength = new fl::InputVariable("heroStrength"); //we want to use weakest possible hero
turnDistance = new fl::InputVariable("turnDistance"); //we want to use hero who is near
missionImportance = new fl::InputVariable("lockedMissionImportance"); //we may want to preempt hero with low-priority mission
value = new fl::OutputVariable("Value");
value->setMinimum(0);
value->setMaximum(5);
std::vector<fl::InputVariable *> helper = { strengthRatio, heroStrength, turnDistance, missionImportance };
for(auto val : helper)
{
engine.addInputVariable(val);
}
engine.addOutputVariable(value);
strengthRatio->addTerm(new fl::Ramp("LOW", SAFE_ATTACK_CONSTANT, 0));
strengthRatio->addTerm(new fl::Ramp("HIGH", SAFE_ATTACK_CONSTANT, SAFE_ATTACK_CONSTANT * 3));
strengthRatio->setRange(0, SAFE_ATTACK_CONSTANT * 3);
//strength compared to our main hero
heroStrength->addTerm(new fl::Ramp("LOW", 0.5, 0));
heroStrength->addTerm(new fl::Triangle("MEDIUM", 0.2, 0.8));
heroStrength->addTerm(new fl::Ramp("HIGH", 0.5, 1));
heroStrength->setRange(0.0, 1.0);
turnDistance->addTerm(new fl::Ramp("SHORT", 0.5, 0));
turnDistance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8));
turnDistance->addTerm(new fl::Ramp("LONG", 0.5, 10));
turnDistance->setRange(0.0, 10.0);
missionImportance->addTerm(new fl::Ramp("LOW", 2.5, 0));
missionImportance->addTerm(new fl::Triangle("MEDIUM", 2, 3));
missionImportance->addTerm(new fl::Ramp("HIGH", 2.5, 5));
missionImportance->setRange(0.0, 5.0);
//an issue: in 99% cases this outputs center of mass (2.5) regardless of actual input :/
//should be same as "mission Importance" to keep consistency
value->addTerm(new fl::Ramp("LOW", 2.5, 0));
value->addTerm(new fl::Triangle("MEDIUM", 2, 3)); //can't be center of mass :/
value->addTerm(new fl::Ramp("HIGH", 2.5, 5));
value->setRange(0.0, 5.0);
//use unarmed scouts if possible
addRule("if strengthRatio is HIGH and heroStrength is LOW then Value is HIGH");
//we may want to use secondary hero(es) rather than main hero
addRule("if strengthRatio is HIGH and heroStrength is MEDIUM then Value is MEDIUM");
addRule("if strengthRatio is HIGH and heroStrength is HIGH then Value is LOW");
//don't assign targets to heroes who are too weak, but prefer targets of our main hero (in case we need to gather army)
addRule("if strengthRatio is LOW and heroStrength is LOW then Value is LOW");
//attempt to arm secondary heroes is not stupid
addRule("if strengthRatio is LOW and heroStrength is MEDIUM then Value is HIGH");
addRule("if strengthRatio is LOW and heroStrength is HIGH then Value is LOW");
//do not cancel important goals
addRule("if lockedMissionImportance is HIGH then Value is LOW");
addRule("if lockedMissionImportance is MEDIUM then Value is MEDIUM");
addRule("if lockedMissionImportance is LOW then Value is HIGH");
//pick nearby objects if it's easy, avoid long walks
addRule("if turnDistance is SHORT then Value is HIGH");
addRule("if turnDistance is MEDIUM then Value is MEDIUM");
addRule("if turnDistance is LONG then Value is LOW");
}
catch(fl::Exception & fe)
{
logAi->error("HeroMovementGoalEngineBase: %s", fe.getWhat());
}
}
void HeroMovementGoalEngineBase::setSharedFuzzyVariables(Goals::AbstractGoal & goal)
{
float turns = calculateTurnDistanceInputValue(goal);
float missionImportanceData = 0;
if(vstd::contains(ai->lockedHeroes, goal.hero))
{
missionImportanceData = ai->lockedHeroes[goal.hero]->priority;
}
else if(goal.parent)
{
missionImportanceData = goal.parent->priority;
}
float strengthRatioData = 10.0f; //we are much stronger than enemy
ui64 danger = fh->evaluateDanger(goal.tile, goal.hero.h);
if(danger)
strengthRatioData = (fl::scalar)goal.hero.h->getTotalStrength() / danger;
try
{
strengthRatio->setValue(strengthRatioData);
heroStrength->setValue((fl::scalar)goal.hero->getTotalStrength() / ai->primaryHero()->getTotalStrength());
turnDistance->setValue(turns);
missionImportance->setValue(missionImportanceData);
}
catch(fl::Exception & fe)
{
logAi->error("HeroMovementGoalEngineBase::setSharedFuzzyVariables: %s", fe.getWhat());
}
}
VisitObjEngine::VisitObjEngine()
{
try
{
objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI
engine.addInputVariable(objectValue);
//objectValue ranges are based on checking RMG priorities of some objects and checking LOW/MID/HIGH proportions for various values in QtFuzzyLite
objectValue->addTerm(new fl::Ramp("LOW", 3500.0, 0.0));
objectValue->addTerm(new fl::Triangle("MEDIUM", 0.0, 8500.0));
std::vector<fl::Discrete::Pair> multiRamp = { fl::Discrete::Pair(5000.0, 0.0), fl::Discrete::Pair(10000.0, 0.75), fl::Discrete::Pair(20000.0, 1.0) };
objectValue->addTerm(new fl::Discrete("HIGH", multiRamp));
objectValue->setRange(0.0, 20000.0); //relic artifact value is border value by design, even better things are scaled down.
addRule("if objectValue is HIGH then Value is HIGH");
addRule("if objectValue is MEDIUM then Value is MEDIUM");
addRule("if objectValue is LOW then Value is LOW");
}
catch(fl::Exception & fe)
{
logAi->error("FindWanderTarget: %s", fe.getWhat());
}
configure();
}
float VisitObjEngine::evaluate(Goals::VisitObj & goal)
{
if(!goal.hero)
return 0;
auto obj = ai->myCb->getObj(ObjectInstanceID(goal.objid));
if(!obj)
{
logAi->error("Goals::VisitObj objid " + std::to_string(goal.objid) + " no longer visible, probably goal used for something it's not intended");
return -100; // FIXME: Added check when goal was used for hero instead of VisitHero, but crashes are bad anyway
}
boost::optional<int> objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj);
int objValue = 0;
if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map
{
objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000);
}
else
{
MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
logGlobal->error("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
}
setSharedFuzzyVariables(goal);
float output = -1.0f;
try
{
objectValue->setValue(objValue);
engine.process();
output = value->getValue();
}
catch(fl::Exception & fe)
{
logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat());
}
assert(output >= 0.0f);
return output;
}
VisitTileEngine::VisitTileEngine() //so far no VisitTile-specific variables that are not shared with HeroMovementGoalEngineBase
{
configure();
}
float VisitTileEngine::evaluate(Goals::VisitTile & goal)
{
//we assume that hero is already set and we want to choose most suitable one for the mission
if(!goal.hero)
return 0;
//assert(cb->isInTheMap(g.tile));
setSharedFuzzyVariables(goal);
try
{
engine.process();
goal.priority = value->getValue();
}
catch(fl::Exception & fe)
{
logAi->error("evaluate VisitTile: %s", fe.getWhat());
}
assert(goal.priority >= 0);
return goal.priority;
}

View File

@ -0,0 +1,72 @@
/*
* FuzzyEngines.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 <fl/Headers.h>
#include "Goals/AbstractGoal.h"
class CArmedInstance;
class engineBase //subclasses create fuzzylite variables with "new" that are not freed - this is desired as fl::Engine wants to destroy these...
{
protected:
fl::Engine engine;
fl::RuleBlock rules;
virtual void configure();
void addRule(const std::string & txt);
public:
engineBase();
};
class TacticalAdvantageEngine : public engineBase //TODO: rework this engine, it does not work well (example: AI hero with 140 beholders attacked 150 beholders - engine lowered danger 50000 -> 35000)
{
public:
TacticalAdvantageEngine();
float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
private:
fl::InputVariable * ourWalkers, *ourShooters, *ourFlyers, *enemyWalkers, *enemyShooters, *enemyFlyers;
fl::InputVariable * ourSpeed, *enemySpeed;
fl::InputVariable * bankPresent;
fl::InputVariable * castleWalls;
fl::OutputVariable * threat;
};
class HeroMovementGoalEngineBase : public engineBase //in future - maybe derive from some (GoalEngineBase : public engineBase) class for handling non-movement goals with common utility for goal engines
{
public:
HeroMovementGoalEngineBase();
protected:
void setSharedFuzzyVariables(Goals::AbstractGoal & goal);
fl::InputVariable * strengthRatio;
fl::InputVariable * heroStrength;
fl::InputVariable * turnDistance;
fl::InputVariable * missionImportance;
fl::OutputVariable * value;
private:
float calculateTurnDistanceInputValue(const Goals::AbstractGoal & goal) const;
};
class VisitTileEngine : public HeroMovementGoalEngineBase
{
public:
VisitTileEngine();
float evaluate(Goals::VisitTile & goal);
};
class VisitObjEngine : public HeroMovementGoalEngineBase
{
public:
VisitObjEngine();
float evaluate(Goals::VisitObj & goal);
protected:
fl::InputVariable * objectValue;
};

View File

@ -0,0 +1,333 @@
/*
* FuzzyHelper.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 "FuzzyHelper.h"
#include "../../lib/mapObjects/CommonConstructors.h"
#include "Goals/Goals.h"
#include "VCAI.h"
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)
{
//this one is not fuzzy anymore, just calculate weighted average
auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
ui64 totalStrength = 0;
ui8 totalChance = 0;
for(auto config : bankInfo->getPossibleGuards())
{
totalStrength += config.second.totalStrength * config.first;
totalChance += config.first;
}
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
}
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());
}
ui64 FuzzyHelper::evaluateDanger(crint3 tile, const CGHeroInstance * visitor, const VCAI * ai)
{
auto cb = ai->myCb;
const TerrainTile * t = cb->getTile(tile, false);
if(!t) //we can know about guard but can't check its tile (the edge of fow)
return 190000000; //MUCH
ui64 objectDanger = 0;
ui64 guardDanger = 0;
auto visitableObjects = cb->getVisitableObjs(tile);
// in some scenarios hero happens to be "under" the object (eg town). Then we consider ONLY the hero.
if(vstd::contains_if(visitableObjects, objWithID<Obj::HERO>))
{
vstd::erase_if(visitableObjects, [](const CGObjectInstance * obj)
{
return !objWithID<Obj::HERO>(obj);
});
}
if(const CGObjectInstance * dangerousObject = vstd::backOrNull(visitableObjects))
{
objectDanger = evaluateDanger(dangerousObject, ai); //unguarded objects can also be dangerous or unhandled
if(objectDanger)
{
//TODO: don't downcast objects AI shouldn't know about!
auto armedObj = dynamic_cast<const CArmedInstance *>(dangerousObject);
if(armedObj)
{
float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, armedObj);
objectDanger *= tacticalAdvantage; //this line tends to go infinite for allied towns (?)
}
}
if(dangerousObject->ID == Obj::SUBTERRANEAN_GATE)
{
//check guard on the other side of the gate
auto it = ai->knownSubterraneanGates.find(dangerousObject);
if(it != ai->knownSubterraneanGates.end())
{
auto guards = cb->getGuardingCreatures(it->second->visitablePos());
for(auto cre : guards)
{
float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast<const CArmedInstance *>(cre));
vstd::amax(guardDanger, evaluateDanger(cre, ai) * tacticalAdvantage);
}
}
}
}
auto guards = cb->getGuardingCreatures(tile);
for(auto cre : guards)
{
float tacticalAdvantage = tacticalAdvantageEngine.getTacticalAdvantage(visitor, dynamic_cast<const CArmedInstance *>(cre));
vstd::amax(guardDanger, evaluateDanger(cre, ai) * tacticalAdvantage); //we are interested in strongest monster around
}
//TODO mozna odwiedzic blockvis nie ruszajac straznika
return std::max(objectDanger, guardDanger);
}
ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
{
auto cb = ai->myCb;
if(obj->tempOwner < PlayerColor::PLAYER_LIMIT && cb->getPlayerRelations(obj->tempOwner, ai->playerID) != PlayerRelations::ENEMIES) //owned or allied objects don't pose any threat
return 0;
switch(obj->ID)
{
case Obj::HERO:
{
InfoAboutHero iah;
cb->getHeroInfo(obj, iah);
return iah.army.getStrength();
}
case Obj::TOWN:
case Obj::GARRISON:
case Obj::GARRISON2:
{
InfoAboutTown iat;
cb->getTownInfo(obj, iat);
return iat.army.getStrength();
}
case Obj::MONSTER:
{
//TODO!!!!!!!!
const CGCreature * cre = dynamic_cast<const CGCreature *>(obj);
return cre->getArmyStrength();
}
case Obj::CREATURE_GENERATOR1:
case Obj::CREATURE_GENERATOR4:
{
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
return d->getArmyStrength();
}
case Obj::MINE:
case Obj::ABANDONED_MINE:
{
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
return a->getArmyStrength();
}
case Obj::CRYPT: //crypt
case Obj::CREATURE_BANK: //crebank
case Obj::DRAGON_UTOPIA:
case Obj::SHIPWRECK: //shipwreck
case Obj::DERELICT_SHIP: //derelict ship
// case Obj::PYRAMID:
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
case Obj::PYRAMID:
{
if(obj->subID == 0)
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
else
return 0;
}
default:
return 0;
}
}

View File

@ -0,0 +1,49 @@
/*
* FuzzyHelper.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 "FuzzyEngines.h"
class CBank;
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);
ui64 evaluateDanger(crint3 tile, const CGHeroInstance * visitor);
};

View File

@ -0,0 +1,188 @@
/*
* AbstractGoal.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "AbstractGoal.h"
#include "../VCAI.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
TSubgoal Goals::sptr(const AbstractGoal & tmp)
{
TSubgoal ptr;
ptr.reset(tmp.clone());
return ptr;
}
std::string AbstractGoal::name() const //TODO: virtualize
{
std::string desc;
switch(goalType)
{
case INVALID:
return "INVALID";
case WIN:
return "WIN";
case CONQUER:
return "CONQUER";
case BUILD:
return "BUILD";
case EXPLORE:
desc = "EXPLORE";
break;
case GATHER_ARMY:
desc = "GATHER ARMY";
break;
case BUY_ARMY:
return "BUY ARMY";
break;
case BOOST_HERO:
desc = "BOOST_HERO (unsupported)";
break;
case RECRUIT_HERO:
return "RECRUIT HERO";
case BUILD_STRUCTURE:
return "BUILD STRUCTURE";
case COLLECT_RES:
desc = "COLLECT RESOURCE " + GameConstants::RESOURCE_NAMES[resID] + " (" + boost::lexical_cast<std::string>(value) + ")";
break;
case TRADE:
{
auto obj = cb->getObjInstance(ObjectInstanceID(objid));
if (obj)
desc = (boost::format("TRADE %d of %s at %s") % value % GameConstants::RESOURCE_NAMES[resID] % obj->getObjectName()).str();
}
break;
case GATHER_TROOPS:
desc = "GATHER TROOPS";
break;
case VISIT_OBJ:
{
auto obj = cb->getObjInstance(ObjectInstanceID(objid));
if(obj)
desc = "VISIT OBJ " + obj->getObjectName();
}
break;
case FIND_OBJ:
desc = "FIND OBJ " + boost::lexical_cast<std::string>(objid);
break;
case VISIT_HERO:
{
auto obj = cb->getObjInstance(ObjectInstanceID(objid));
if(obj)
desc = "VISIT HERO " + obj->getObjectName();
}
break;
case GET_ART_TYPE:
desc = "GET ARTIFACT OF TYPE " + VLC->arth->artifacts[aid]->Name();
break;
case VISIT_TILE:
desc = "VISIT TILE " + tile.toString();
break;
case CLEAR_WAY_TO:
desc = "CLEAR WAY TO " + tile.toString();
break;
case DIG_AT_TILE:
desc = "DIG AT TILE " + tile.toString();
break;
default:
return boost::lexical_cast<std::string>(goalType);
}
if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal
desc += " (" + hero->name + ")";
return desc;
}
bool AbstractGoal::operator==(const AbstractGoal & g) const
{
return false;
}
bool AbstractGoal::operator<(AbstractGoal & g) //for std::unique
{
//TODO: make sure it gets goals consistent with == operator
if (goalType < g.goalType)
return true;
if (goalType > g.goalType)
return false;
if (hero < g.hero)
return true;
if (hero > g.hero)
return false;
if (tile < g.tile)
return true;
if (g.tile < tile)
return false;
if (objid < g.objid)
return true;
if (objid > g.objid)
return false;
if (town < g.town)
return true;
if (town > g.town)
return false;
if (value < g.value)
return true;
if (value > g.value)
return false;
if (priority < g.priority)
return true;
if (priority > g.priority)
return false;
if (resID < g.resID)
return true;
if (resID > g.resID)
return false;
if (bid < g.bid)
return true;
if (bid > g.bid)
return false;
if (aid < g.aid)
return true;
if (aid > g.aid)
return false;
return false;
}
//TODO: find out why the following are not generated automatically on MVS?
bool TSubgoal::operator==(const TSubgoal & rhs) const
{
return *get() == *rhs.get(); //comparison for Goals is overloaded, so they don't need to be identical to match
}
bool TSubgoal::operator<(const TSubgoal & rhs) const
{
return get() < rhs.get(); //compae by value
}
bool AbstractGoal::invalid() const
{
return goalType == EGoals::INVALID;
}
void AbstractGoal::accept(VCAI * ai)
{
ai->tryRealize(*this);
}
float AbstractGoal::accept(FuzzyHelper * f)
{
return f->evaluate(*this);
}

View File

@ -0,0 +1,195 @@
/*
* AbstractGoal.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../../lib/VCMI_Lib.h"
#include "../../../lib/CBuildingHandler.h"
#include "../../../lib/CCreatureHandler.h"
#include "../../../lib/CTownHandler.h"
#include "../AIUtility.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class AbstractGoal;
class Explore;
class RecruitHero;
class VisitTile;
class VisitObj;
class VisitHero;
class BuildThis;
class DigAtTile;
class CollectRes;
class Build;
class BuyArmy;
class BuildBoat;
class GatherArmy;
class ClearWayTo;
class Invalid;
class Trade;
class CompleteQuest;
class AdventureSpellCast;
enum EGoals
{
INVALID = -1,
WIN, CONQUER, BUILD, //build needs to get a real reasoning
EXPLORE, GATHER_ARMY,
BOOST_HERO,
RECRUIT_HERO,
BUILD_STRUCTURE, //if hero set, then in visited town
COLLECT_RES,
GATHER_TROOPS, // val of creatures with objid
VISIT_OBJ, //visit or defeat or collect the object
FIND_OBJ, //find and visit any obj with objid + resid //TODO: consider universal subid for various types (aid, bid)
VISIT_HERO, //heroes can move around - set goal abstract and track hero every turn
GET_ART_TYPE,
VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable
CLEAR_WAY_TO,
DIG_AT_TILE,//elementar with hero on tile
BUY_ARMY, //at specific town
TRADE, //val resID at object objid
BUILD_BOAT,
COMPLETE_QUEST,
ADVENTURE_SPELL_CAST
};
class DLL_EXPORT TSubgoal : public std::shared_ptr<AbstractGoal>
{
public:
bool operator==(const TSubgoal & rhs) const;
bool operator<(const TSubgoal & rhs) const;
//TODO: serialize?
};
typedef std::vector<TSubgoal> TGoalVec;
//method chaining + clone pattern
#define VSETTER(type, field) virtual AbstractGoal & set ## field(const type &rhs) {field = rhs; return *this;};
#define OSETTER(type, field) CGoal<T> & set ## field(const type &rhs) override { field = rhs; return *this; };
#if 0
#define SETTER
#endif // _DEBUG
enum { LOW_PR = -1 };
DLL_EXPORT TSubgoal sptr(const AbstractGoal & tmp);
struct DLL_EXPORT EvaluationContext
{
float movementCost;
int manaCost;
uint64_t danger;
EvaluationContext()
: movementCost(0.0),
manaCost(0),
danger(0)
{
}
};
class DLL_EXPORT AbstractGoal
{
public:
bool isElementar; VSETTER(bool, isElementar)
bool isAbstract; VSETTER(bool, isAbstract)
float priority; VSETTER(float, priority)
int value; VSETTER(int, value)
int resID; VSETTER(int, resID)
int objid; VSETTER(int, objid)
int aid; VSETTER(int, aid)
int3 tile; VSETTER(int3, tile)
HeroPtr hero; VSETTER(HeroPtr, hero)
const CGTownInstance *town; VSETTER(CGTownInstance *, town)
int bid; VSETTER(int, bid)
TSubgoal parent; VSETTER(TSubgoal, parent)
EvaluationContext evaluationContext; VSETTER(EvaluationContext, evaluationContext)
AbstractGoal(EGoals goal = EGoals::INVALID)
: goalType(goal), evaluationContext()
{
priority = 0;
isElementar = false;
isAbstract = false;
value = 0;
aid = -1;
resID = -1;
objid = -1;
tile = int3(-1, -1, -1);
town = nullptr;
bid = -1;
}
virtual ~AbstractGoal() {}
//FIXME: abstract goal should be abstract, but serializer fails to instantiate subgoals in such case
virtual AbstractGoal * clone() const
{
return const_cast<AbstractGoal *>(this);
}
virtual TGoalVec getAllPossibleSubgoals()
{
return TGoalVec();
}
virtual TSubgoal whatToDoToAchieve()
{
return sptr(AbstractGoal());
}
EGoals goalType;
virtual std::string name() const;
virtual std::string completeMessage() const
{
return "This goal is unspecified!";
}
bool invalid() const;
///Visitor pattern
//TODO: make accept work for std::shared_ptr... somehow
virtual void accept(VCAI * ai); //unhandled goal will report standard error
virtual float accept(FuzzyHelper * f);
virtual bool operator==(const AbstractGoal & g) const;
bool operator<(AbstractGoal & g); //final
virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check
{
return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately
}
bool operator!=(const AbstractGoal & g) const
{
return !(*this == g);
}
template<typename Handler> void serialize(Handler & h, const int version)
{
h & goalType;
h & isElementar;
h & isAbstract;
h & priority;
h & value;
h & resID;
h & objid;
h & aid;
h & tile;
h & hero;
h & town;
h & bid;
}
};
}

View File

@ -0,0 +1,81 @@
/*
* AdventureSpellCast.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 "AdventureSpellCast.h"
#include "../VCAI.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
#include "../../lib/mapping/CMap.h" //for victory conditions
#include "../../lib/CPathfinder.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool AdventureSpellCast::operator==(const AdventureSpellCast & other) const
{
return hero.h == other.hero.h;
}
TSubgoal AdventureSpellCast::whatToDoToAchieve()
{
if(!hero.validAndSet())
throw cannotFulfillGoalException("Invalid hero!");
auto spell = getSpell();
logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name);
if(!spell->isAdventureSpell())
throw cannotFulfillGoalException(spell->name + " is not an adventure spell.");
if(!hero->canCastThisSpell(spell))
throw cannotFulfillGoalException("Hero can not cast " + spell->name);
if(hero->mana < hero->getSpellCost(spell))
throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name);
return iAmElementar();
}
void AdventureSpellCast::accept(VCAI * ai)
{
if(town && spellID == SpellID::TOWN_PORTAL)
{
ai->selectedObject = town->id;
}
auto wait = cb->waitTillRealize;
cb->waitTillRealize = true;
cb->castSpell(hero.h, spellID, tile);
if(town && spellID == SpellID::TOWN_PORTAL)
{
// visit town
ai->moveHeroToTile(town->visitablePos(), hero);
}
cb->waitTillRealize = wait;
throw goalFulfilledException(sptr(*this));
}
std::string AdventureSpellCast::name() const
{
return "AdventureSpellCast " + spellID.toSpell()->name;
}
std::string AdventureSpellCast::completeMessage() const
{
return "Spell casted successfully " + spellID.toSpell()->name;
}

View File

@ -0,0 +1,44 @@
/*
* AdventureSpellCast.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
namespace Goals
{
class DLL_EXPORT AdventureSpellCast : public CGoal<AdventureSpellCast>
{
private:
SpellID spellID;
public:
AdventureSpellCast(HeroPtr hero, SpellID spellID)
: CGoal(Goals::ADVENTURE_SPELL_CAST), spellID(spellID)
{
sethero(hero);
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
const CSpell * getSpell() const
{
return spellID.toSpell();
}
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

@ -0,0 +1,93 @@
/*
* Build.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Build.h"
#include "BuildThis.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
TGoalVec Build::getAllPossibleSubgoals()
{
TGoalVec ret;
for(const CGTownInstance * t : cb->getTownsInfo())
{
//start fresh with every town
ai->ah->getBuildingOptions(t);
auto immediateBuilding = ai->ah->immediateBuilding();
auto expensiveBuilding = ai->ah->expensiveBuilding();
//handling for early town development to save money and focus on income
if(!t->hasBuilt(ai->ah->getMaxPossibleGoldBuilding(t)) && expensiveBuilding.is_initialized())
{
auto potentialBuilding = expensiveBuilding.get();
switch(expensiveBuilding.get().bid)
{
case BuildingID::TOWN_HALL:
case BuildingID::CITY_HALL:
case BuildingID::CAPITOL:
case BuildingID::FORT:
case BuildingID::CITADEL:
case BuildingID::CASTLE:
//If above buildings are next to be bought, but no money... do not buy anything else, try to gather resources for these. Simple but has to suffice for now.
auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(BuildThis(potentialBuilding.bid, t).setpriority(2.25)));
ret.push_back(goal);
return ret;
break;
}
}
if(immediateBuilding.is_initialized())
{
ret.push_back(sptr(BuildThis(immediateBuilding.get().bid, t).setpriority(2))); //prioritize buildings we can build quick
}
else //try build later
{
if(expensiveBuilding.is_initialized())
{
auto potentialBuilding = expensiveBuilding.get(); //gather resources for any we can't afford
auto goal = ai->ah->whatToDo(potentialBuilding.price, sptr(BuildThis(potentialBuilding.bid, t).setpriority(0.5)));
ret.push_back(goal);
}
}
}
if(ret.empty())
throw cannotFulfillGoalException("BUILD has been realized as much as possible.");
else
return ret;
}
TSubgoal Build::whatToDoToAchieve()
{
return fh->chooseSolution(getAllPossibleSubgoals());
}
bool Build::fulfillsMe(TSubgoal goal)
{
if(goal->goalType == BUILD || goal->goalType == BUILD_STRUCTURE)
return (!town || town == goal->town); //building anything will do, in this town if set
else
return false;
}

View File

@ -0,0 +1,37 @@
/*
* Build.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT Build : public CGoal<Build>
{
public:
Build()
: CGoal(Goals::BUILD)
{
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const Build & other) const override
{
return true;
}
};
}

View File

@ -0,0 +1,86 @@
/*
* BuildBoat.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BuildBoat.h"
#include "../VCAI.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
#include "../../lib/mapping/CMap.h" //for victory conditions
#include "../../lib/CPathfinder.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool BuildBoat::operator==(const BuildBoat & other) const
{
return shipyard->o->id == other.shipyard->o->id;
}
TSubgoal BuildBoat::whatToDoToAchieve()
{
if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
{
return fh->chooseSolution(ai->ah->howToVisitObj(shipyard->o));
}
if(shipyard->shipyardStatus() != IShipyard::GOOD)
{
throw cannotFulfillGoalException("Shipyard is busy.");
}
TResources boatCost;
shipyard->getBoatCost(boatCost);
return ai->ah->whatToDo(boatCost, this->iAmElementar());
}
void BuildBoat::accept(VCAI * ai)
{
TResources boatCost;
shipyard->getBoatCost(boatCost);
if(!cb->getResourceAmount().canAfford(boatCost))
{
throw cannotFulfillGoalException("Can not afford boat");
}
if(cb->getPlayerRelations(ai->playerID, shipyard->o->tempOwner) == PlayerRelations::ENEMIES)
{
throw cannotFulfillGoalException("Can not build boat in enemy shipyard");
}
if(shipyard->shipyardStatus() != IShipyard::GOOD)
{
throw cannotFulfillGoalException("Shipyard is busy.");
}
logAi->trace(
"Building boat at shipyard %s located at %s, estimated boat position %s",
shipyard->o->getObjectName(),
shipyard->o->visitablePos().toString(),
shipyard->bestLocation().toString());
cb->buildBoat(shipyard);
throw goalFulfilledException(sptr(*this));
}
std::string BuildBoat::name() const
{
return "BuildBoat";
}
std::string BuildBoat::completeMessage() const
{
return "Boat have been built at " + shipyard->o->visitablePos().toString();
}

View File

@ -0,0 +1,37 @@
/*
* BuildBoat.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
namespace Goals
{
class DLL_EXPORT BuildBoat : public CGoal<BuildBoat>
{
private:
const IShipyard * shipyard;
public:
BuildBoat(const IShipyard * shipyard)
: CGoal(Goals::BUILD_BOAT), shipyard(shipyard)
{
priority = 0;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
void accept(VCAI * ai) override;
std::string name() const override;
std::string completeMessage() const override;
virtual bool operator==(const BuildBoat & other) const override;
};
}

View File

@ -0,0 +1,74 @@
/*
* BuildThis.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BuildThis.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool BuildThis::operator==(const BuildThis & other) const
{
return town == other.town && bid == other.bid;
}
TSubgoal BuildThis::whatToDoToAchieve()
{
auto b = BuildingID(bid);
// find town if not set
if(!town && hero)
town = hero->visitedTown;
if(!town)
{
for(const CGTownInstance * t : cb->getTownsInfo())
{
switch(cb->canBuildStructure(town, b))
{
case EBuildingState::ALLOWED:
town = t;
break; //TODO: look for prerequisites? this is not our reponsibility
default:
continue;
}
}
}
if(town) //we have specific town to build this
{
switch(cb->canBuildStructure(town, b))
{
case EBuildingState::ALLOWED:
case EBuildingState::NO_RESOURCES:
{
auto res = town->town->buildings.at(BuildingID(bid))->resources;
return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
}
break;
default:
throw cannotFulfillGoalException("Not possible to build");
}
}
else
throw cannotFulfillGoalException("Cannot find town to build this");
}

View File

@ -0,0 +1,48 @@
/*
* BuildThis.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT BuildThis : public CGoal<BuildThis>
{
public:
BuildThis() //should be private, but unit test uses it
: CGoal(Goals::BUILD_STRUCTURE)
{
}
BuildThis(BuildingID Bid, const CGTownInstance * tid)
: CGoal(Goals::BUILD_STRUCTURE)
{
bid = Bid;
town = tid;
priority = 1;
}
BuildThis(BuildingID Bid)
: CGoal(Goals::BUILD_STRUCTURE)
{
bid = Bid;
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
//bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const BuildThis & other) const override;
};
}

View File

@ -0,0 +1,45 @@
/*
* BuyArmy.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "BuyArmy.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
#include "../../lib/mapObjects/CGTownInstance.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool BuyArmy::operator==(const BuyArmy & other) const
{
return town == other.town && objid == other.objid;
}
bool BuyArmy::fulfillsMe(TSubgoal goal)
{
//if (hero && hero != goal->hero)
// return false;
return town == goal->town && goal->value >= value; //can always buy more army
}
TSubgoal BuyArmy::whatToDoToAchieve()
{
//TODO: calculate the actual cost of units instead
TResources price;
price[Res::GOLD] = value * 0.4f; //some approximate value
return ai->ah->whatToDo(price, iAmElementar()); //buy right now or gather resources
}
std::string BuyArmy::completeMessage() const
{
return boost::format("Bought army of value %d in town of %s") % value, town->name;
}

View File

@ -0,0 +1,41 @@
/*
* BuyArmy.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT BuyArmy : public CGoal<BuyArmy>
{
private:
BuyArmy()
: CGoal(Goals::BUY_ARMY)
{
}
public:
BuyArmy(const CGTownInstance * Town, int val)
: CGoal(Goals::BUY_ARMY)
{
town = Town; //where to buy this army
value = val; //expressed in AI unit strength
priority = 3;//TODO: evaluate?
}
bool fulfillsMe(TSubgoal goal) override;
TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override;
virtual bool operator==(const BuyArmy & other) const override;
};
}

View File

@ -0,0 +1,89 @@
/*
* CGoal.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "AbstractGoal.h"
#include "../FuzzyHelper.h"
#include "../VCAI.h"
struct HeroPtr;
class VCAI;
namespace Goals
{
template<typename T> class DLL_EXPORT CGoal : public AbstractGoal
{
public:
CGoal<T>(EGoals goal = INVALID) : AbstractGoal(goal)
{
priority = 0;
isElementar = false;
isAbstract = false;
value = 0;
aid = -1;
objid = -1;
resID = -1;
tile = int3(-1, -1, -1);
town = nullptr;
}
OSETTER(bool, isElementar)
OSETTER(bool, isAbstract)
OSETTER(float, priority)
OSETTER(int, value)
OSETTER(int, resID)
OSETTER(int, objid)
OSETTER(int, aid)
OSETTER(int3, tile)
OSETTER(HeroPtr, hero)
OSETTER(CGTownInstance *, town)
OSETTER(int, bid)
void accept(VCAI * ai) override
{
ai->tryRealize(static_cast<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
}
TSubgoal iAmElementar() const
{
TSubgoal ptr;
ptr.reset(clone());
ptr->setisElementar(true);
return ptr;
}
template<typename Handler> void serialize(Handler & h, const int version)
{
h & static_cast<AbstractGoal &>(*this);
//h & goalType & isElementar & isAbstract & priority;
//h & value & resID & objid & aid & tile & hero & town & bid;
}
virtual bool operator==(const AbstractGoal & g) const override
{
if(goalType != g.goalType)
return false;
return (*this) == (static_cast<const T &>(g));
}
virtual bool operator==(const T & other) const = 0;
};
}

View File

@ -0,0 +1,85 @@
/*
* ClearWayTo.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "ClearWayTo.h"
#include "Explore.h"
#include "RecruitHero.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool ClearWayTo::operator==(const ClearWayTo & other) const
{
return other.hero.h == hero.h && other.tile == tile;
}
TSubgoal ClearWayTo::whatToDoToAchieve()
{
assert(cb->isInTheMap(tile)); //set tile
if(!cb->isVisible(tile))
{
logAi->error("Clear way should be used with visible tiles!");
return sptr(Explore());
}
return (fh->chooseSolution(getAllPossibleSubgoals()));
}
bool ClearWayTo::fulfillsMe(TSubgoal goal)
{
if (goal->goalType == VISIT_TILE)
{
if (!hero || hero == goal->hero)
return tile == goal->tile;
}
return false;
}
TGoalVec ClearWayTo::getAllPossibleSubgoals()
{
TGoalVec ret;
std::vector<const CGHeroInstance *> heroes;
if(hero)
heroes.push_back(hero.h);
else
heroes = cb->getHeroesInfo();
for(auto h : heroes)
{
//TODO: handle clearing way to allied heroes that are blocked
//if ((hero && hero->visitablePos() == tile && hero == *h) || //we can't free the way ourselves
// h->visitablePos() == tile) //we are already on that tile! what does it mean?
// continue;
//if our hero is trapped, make sure we request clearing the way from OUR perspective
vstd::concatenate(ret, ai->ah->howToVisitTile(h, tile));
}
if(ret.empty() && ai->canRecruitAnyHero())
ret.push_back(sptr(RecruitHero()));
if(ret.empty())
{
logAi->warn("There is no known way to clear the way to tile %s", tile.toString());
throw goalFulfilledException(sptr(ClearWayTo(tile))); //make sure asigned hero gets unlocked
}
return ret;
}

View File

@ -0,0 +1,45 @@
/*
* ClearWayTo.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT ClearWayTo : public CGoal<ClearWayTo>
{
public:
ClearWayTo()
: CGoal(Goals::CLEAR_WAY_TO)
{
}
ClearWayTo(int3 Tile)
: CGoal(Goals::CLEAR_WAY_TO)
{
tile = Tile;
priority = 5;
}
ClearWayTo(int3 Tile, HeroPtr h)
: CGoal(Goals::CLEAR_WAY_TO)
{
tile = Tile;
hero = h;
priority = 5;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const ClearWayTo & other) const override;
};
}

View File

@ -0,0 +1,208 @@
/*
* CollectRes.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool CollectRes::operator==(const CollectRes & other) const
{
return resID == other.resID;
}
TGoalVec CollectRes::getAllPossibleSubgoals()
{
TGoalVec ret;
auto givesResource = [this](const CGObjectInstance * obj) -> bool
{
//TODO: move this logic to object side
//TODO: remember mithril exists
//TODO: water objects
//TODO: Creature banks
//return false first from once-visitable, before checking if they were even visited
switch (obj->ID.num)
{
case Obj::TREASURE_CHEST:
return resID == Res::GOLD;
break;
case Obj::RESOURCE:
return obj->subID == resID;
break;
case Obj::MINE:
return (obj->subID == resID &&
(cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines
break;
case Obj::CAMPFIRE:
return true; //contains all resources
break;
case Obj::WINDMILL:
switch (resID)
{
case Res::GOLD:
case Res::WOOD:
return false;
}
break;
case Obj::WATER_WHEEL:
if (resID != Res::GOLD)
return false;
break;
case Obj::MYSTICAL_GARDEN:
if ((resID != Res::GOLD) && (resID != Res::GEMS))
return false;
break;
case Obj::LEAN_TO:
case Obj::WAGON:
if (resID != Res::GOLD)
return false;
break;
default:
return false;
break;
}
return !vstd::contains(ai->alreadyVisited, obj); //for weekly / once visitable
};
std::vector<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 : ourObjs)
{
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
vstd::concatenate(ret, waysToGo);
}
}
return ret;
}
TSubgoal CollectRes::whatToDoToAchieve()
{
auto goals = getAllPossibleSubgoals();
auto trade = whatToDoToTrade();
if (!trade->invalid())
goals.push_back(trade);
if (goals.empty())
return sptr(Explore()); //we can always do that
else
return fh->chooseSolution(goals); //TODO: evaluate trading
}
TSubgoal CollectRes::whatToDoToTrade()
{
std::vector<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);
}
}
boost::sort(markets, [](const IMarket * m1, const IMarket * m2) -> bool
{
return m1->getMarketEfficiency() < m2->getMarketEfficiency();
});
markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool
{
if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID))
{
if (!ai->isAccessible(market->o->visitablePos()))
return true;
}
return false;
}), markets.end());
if (!markets.size())
{
for (const CGTownInstance * t : cb->getTownsInfo())
{
if (cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED)
return sptr(BuildThis(BuildingID::MARKETPLACE, t).setpriority(2));
}
}
else
{
const IMarket * m = markets.back();
//attempt trade at back (best prices)
int howManyCanWeBuy = 0;
for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1))
{
if (i == resID)
continue;
int toGive = -1, toReceive = -1;
m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
assert(toGive > 0 && toReceive > 0);
howManyCanWeBuy += toReceive * (ai->ah->freeResources()[i] / toGive);
}
if (howManyCanWeBuy >= value)
{
auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
assert(backObj);
auto objid = m->o->id.getNum();
if (backObj->tempOwner != ai->playerID) //top object not owned
{
return sptr(VisitObj(objid)); //just go there
}
else //either it's our town, or we have hero there
{
return sptr(Trade(resID, value, objid).setisElementar(true)); //we can do this immediately
}
}
}
return sptr(Invalid()); //cannot trade
}
bool CollectRes::fulfillsMe(TSubgoal goal)
{
if (goal->resID == resID)
if (goal->value >= value)
return true;
return false;
}

View File

@ -0,0 +1,40 @@
/*
* CollectRes.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT CollectRes : public CGoal<CollectRes>
{
public:
CollectRes()
: CGoal(Goals::COLLECT_RES)
{
}
CollectRes(int rid, int val)
: CGoal(Goals::COLLECT_RES)
{
resID = rid;
value = val;
priority = 2;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
TSubgoal whatToDoToTrade();
bool fulfillsMe(TSubgoal goal) override; //TODO: Trade
virtual bool operator==(const CollectRes & other) const override;
};
}

View File

@ -0,0 +1,276 @@
/*
* CompleteQuest.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../FuzzyHelper.h"
#include "../AIhelper.h"
#include "../../lib/mapping/CMap.h" //for victory conditions
#include "../../lib/CPathfinder.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool CompleteQuest::operator==(const CompleteQuest & other) const
{
return q.quest->qid == other.q.quest->qid;
}
TGoalVec CompleteQuest::getAllPossibleSubgoals()
{
TGoalVec solutions;
if(q.quest->missionType && q.quest->progress != CQuest::COMPLETE)
{
logAi->debug("Trying to realize quest: %s", questToString());
switch(q.quest->missionType)
{
case CQuest::MISSION_ART:
return missionArt();
case CQuest::MISSION_HERO:
return missionHero();
case CQuest::MISSION_ARMY:
return missionArmy();
case CQuest::MISSION_RESOURCES:
return missionResources();
case CQuest::MISSION_KILL_HERO:
case CQuest::MISSION_KILL_CREATURE:
return missionDestroyObj();
case CQuest::MISSION_PRIMARY_STAT:
return missionIncreasePrimaryStat();
case CQuest::MISSION_LEVEL:
return missionLevel();
case CQuest::MISSION_PLAYER:
if(ai->playerID.getNum() != q.quest->m13489val)
logAi->debug("Can't be player of color %d", q.quest->m13489val);
break;
case CQuest::MISSION_KEYMASTER:
return missionKeymaster();
} //end of switch
}
return TGoalVec();
}
TSubgoal CompleteQuest::whatToDoToAchieve()
{
if(q.quest->missionType == CQuest::MISSION_NONE)
{
throw cannotFulfillGoalException("Can not complete inactive quest");
}
TGoalVec solutions = getAllPossibleSubgoals();
if(solutions.empty())
throw cannotFulfillGoalException("Can not complete quest " + questToString());
TSubgoal result = fh->chooseSolution(solutions);
logAi->trace(
"Returning %s, tile: %s, objid: %d, hero: %s",
result->name(),
result->tile.toString(),
result->objid,
result->hero.validAndSet() ? result->hero->name : "not specified");
return result;
}
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)
return "inactive quest";
MetaString ms;
q.quest->getRolloverText(ms, false);
return ms.toString();
}
TGoalVec CompleteQuest::tryCompleteQuest() const
{
TGoalVec solutions;
auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
for(auto hero : heroes)
{
if(q.quest->checkQuest(hero))
{
vstd::concatenate(solutions, ai->ah->howToVisitObj(hero, ObjectIdRef(q.obj->id)));
}
}
return solutions;
}
TGoalVec CompleteQuest::missionArt() const
{
TGoalVec solutions = tryCompleteQuest();
if(!solutions.empty())
return solutions;
for(auto art : q.quest->m5arts)
{
solutions.push_back(sptr(GetArtOfType(art))); //TODO: transport?
}
return solutions;
}
TGoalVec CompleteQuest::missionHero() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
//rule of a thumb - quest heroes usually are locked in prisons
solutions.push_back(sptr(FindObj(Obj::PRISON)));
}
return solutions;
}
TGoalVec CompleteQuest::missionArmy() const
{
TGoalVec solutions = tryCompleteQuest();
if(!solutions.empty())
return solutions;
for(auto creature : q.quest->m6creatures)
{
solutions.push_back(sptr(GatherTroops(creature.type->idNumber, creature.count)));
}
return solutions;
}
TGoalVec CompleteQuest::missionIncreasePrimaryStat() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
for(int i = 0; i < q.quest->m2stats.size(); ++i)
{
// TODO: library, school and other boost objects
logAi->debug("Don't know how to increase primary stat %d", i);
}
}
return solutions;
}
TGoalVec CompleteQuest::missionLevel() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
logAi->debug("Don't know how to reach hero level %d", q.quest->m13489val);
}
return solutions;
}
TGoalVec CompleteQuest::missionKeymaster() const
{
TGoalVec solutions = tryCompleteQuest();
if(solutions.empty())
{
solutions.push_back(sptr(Goals::FindObj(Obj::KEYMASTER, q.obj->subID)));
}
return solutions;
}
TGoalVec CompleteQuest::missionResources() const
{
TGoalVec solutions;
auto heroes = cb->getHeroesInfo(); //TODO: choose best / free hero from among many possibilities?
if(heroes.size())
{
if(q.quest->checkQuest(heroes.front())) //it doesn't matter which hero it is
{
return ai->ah->howToVisitObj(q.obj);
}
else
{
for(int i = 0; i < q.quest->m7resources.size(); ++i)
{
if(q.quest->m7resources[i])
solutions.push_back(sptr(CollectRes(i, q.quest->m7resources[i])));
}
}
}
else
{
solutions.push_back(sptr(Goals::RecruitHero())); //FIXME: checkQuest requires any hero belonging to player :(
}
return solutions;
}
TGoalVec CompleteQuest::missionDestroyObj() const
{
TGoalVec solutions;
auto obj = cb->getObjByQuestIdentifier(q.quest->m13489val);
if(!obj)
return ai->ah->howToVisitObj(q.obj);
if(obj->ID == Obj::HERO)
{
auto relations = cb->getPlayerRelations(ai->playerID, obj->tempOwner);
if(relations == PlayerRelations::SAME_PLAYER)
{
auto heroToProtect = cb->getHero(obj->id);
solutions.push_back(sptr(GatherArmy().sethero(heroToProtect)));
}
else if(relations == PlayerRelations::ENEMIES)
{
solutions = ai->ah->howToVisitObj(obj);
}
}
return solutions;
}

View File

@ -0,0 +1,46 @@
/*
* CompleteQuest.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
#include "../../../lib/VCMI_Lib.h"
namespace Goals
{
class DLL_EXPORT CompleteQuest : public CGoal<CompleteQuest>
{
private:
const QuestInfo q;
public:
CompleteQuest(const QuestInfo quest)
: CGoal(Goals::COMPLETE_QUEST), q(quest)
{
}
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:
TGoalVec tryCompleteQuest() const;
TGoalVec missionArt() const;
TGoalVec missionHero() const;
TGoalVec missionArmy() const;
TGoalVec missionResources() const;
TGoalVec missionDestroyObj() const;
TGoalVec missionIncreasePrimaryStat() const;
TGoalVec missionLevel() const;
TGoalVec missionKeymaster() const;
std::string questToString() const;
};
}

View File

@ -0,0 +1,90 @@
/*
* Conquer.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool Conquer::operator==(const Conquer & other) const
{
return other.hero.h == hero.h;
}
TSubgoal Conquer::whatToDoToAchieve()
{
logAi->trace("Entering goal CONQUER");
return fh->chooseSolution(getAllPossibleSubgoals());
}
TGoalVec Conquer::getAllPossibleSubgoals()
{
TGoalVec ret;
auto conquerable = [](const CGObjectInstance * obj) -> bool
{
if(cb->getPlayerRelations(ai->playerID, obj->tempOwner) == PlayerRelations::ENEMIES)
{
switch(obj->ID.num)
{
case Obj::TOWN:
case Obj::HERO:
case Obj::CREATURE_GENERATOR1:
case Obj::MINE: //TODO: check ai->knownSubterraneanGates
return true;
}
}
return false;
};
std::vector<const CGObjectInstance *> objs;
for(auto obj : ai->visitableObjs)
{
if(conquerable(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(conquerable(obj))
ourObjs.push_back(obj);
}
for(auto obj : ourObjs)
{
auto waysToGo = ai->ah->howToVisitObj(h, ObjectIdRef(obj));
vstd::concatenate(ret, waysToGo);
}
}
if(!objs.empty() && ai->canRecruitAnyHero()) //probably no point to recruit hero if we see no objects to capture
ret.push_back(sptr(RecruitHero()));
if(ret.empty())
ret.push_back(sptr(Explore())); //we need to find an enemy
return ret;
}

View File

@ -0,0 +1,32 @@
/*
* Conquer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT Conquer : public CGoal<Conquer>
{
public:
Conquer()
: CGoal(Goals::CONQUER)
{
priority = 10;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Conquer & other) const override;
};
}

View File

@ -0,0 +1,39 @@
/*
* DigAtTile.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "DigAtTile.h"
#include "VisitTile.h"
#include "../VCAI.h"
#include "../AIUtility.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool DigAtTile::operator==(const DigAtTile & other) const
{
return other.hero.h == hero.h && other.tile == tile;
}
TSubgoal DigAtTile::whatToDoToAchieve()
{
const CGObjectInstance * firstObj = vstd::frontOrNull(cb->getVisitableObjs(tile));
if(firstObj && firstObj->ID == Obj::HERO && firstObj->tempOwner == ai->playerID) //we have hero at dest
{
const CGHeroInstance * h = dynamic_cast<const CGHeroInstance *>(firstObj);
sethero(h).setisElementar(true);
return sptr(*this);
}
return sptr(VisitTile(tile));
}

View File

@ -0,0 +1,41 @@
/*
* DigAtTile.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT DigAtTile : public CGoal<DigAtTile>
//elementar with hero on tile
{
public:
DigAtTile()
: CGoal(Goals::DIG_AT_TILE)
{
}
DigAtTile(int3 Tile)
: CGoal(Goals::DIG_AT_TILE)
{
tile = Tile;
priority = 20;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const DigAtTile & other) const override;
};
}

View File

@ -0,0 +1,449 @@
/*
* Explore.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
#include "../../../lib/CPlayerState.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
namespace Goals
{
struct ExplorationHelper
{
HeroPtr hero;
int sightRadius;
float bestValue;
TSubgoal bestGoal;
VCAI * aip;
CCallback * cbp;
const TeamState * ts;
int3 ourPos;
bool allowDeadEndCancellation;
bool allowGatherArmy;
ExplorationHelper(HeroPtr h, bool gatherArmy)
{
cbp = cb.get();
aip = ai.get();
hero = h;
ts = cbp->getPlayerTeam(ai->playerID);
sightRadius = hero->getSightRadius();
bestGoal = sptr(Goals::Invalid());
bestValue = 0;
ourPos = h->convertPosition(h->pos, false);
allowDeadEndCancellation = true;
allowGatherArmy = gatherArmy;
}
void scanSector(int scanRadius)
{
for(int x = ourPos.x - scanRadius; x <= ourPos.x + scanRadius; x++)
{
for(int y = ourPos.y - scanRadius; y <= ourPos.y + scanRadius; y++)
{
int3 tile = int3(x, y, ourPos.z);
if(cbp->isInTheMap(tile) && ts->fogOfWarMap[tile.x][tile.y][tile.z])
{
scanTile(tile);
}
}
}
}
void scanMap()
{
int3 mapSize = cbp->getMapSize();
int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y);
std::vector<int3> from;
std::vector<int3> to;
from.reserve(perimeter);
to.reserve(perimeter);
foreach_tile_pos([&](const int3 & pos)
{
if(ts->fogOfWarMap[pos.x][pos.y][pos.z])
{
bool hasInvisibleNeighbor = false;
foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour)
{
if(!ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z])
{
hasInvisibleNeighbor = true;
}
});
if(hasInvisibleNeighbor)
from.push_back(pos);
}
});
logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name);
for(const int3 & tile : from)
{
scanTile(tile);
}
if(!bestGoal->invalid())
{
return;
}
allowDeadEndCancellation = false;
for(int i = 0; i < sightRadius; i++)
{
getVisibleNeighbours(from, to);
vstd::concatenate(from, to);
vstd::removeDuplicates(from);
}
logAi->debug("Exploration scan all possible tiles for hero %s", hero.name);
for(const int3 & tile : from)
{
scanTile(tile);
}
}
void scanTile(const int3 & tile)
{
if(tile == ourPos
|| !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does
return;
int tilesDiscovered = howManyTilesWillBeDiscovered(tile);
if(!tilesDiscovered)
return;
auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy);
for(auto goal : waysToVisit)
{
if(goal->evaluationContext.movementCost <= 0.0) // should not happen
continue;
float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost;
if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much
{
auto obj = cb->getTopObj(tile);
// picking up resources does not yield any exploration at all.
// if it blocks the way to some explorable tile AIPathfinder will take care of it
if(obj && obj->blockVisit)
{
continue;
}
if(isSafeToVisit(hero, tile))
{
bestGoal = goal;
bestValue = ourValue;
}
}
}
}
void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & out) const
{
for(const int3 & tile : tiles)
{
foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour)
{
if(ts->fogOfWarMap[neighbour.x][neighbour.y][neighbour.z])
{
out.push_back(neighbour);
}
});
}
}
int howManyTilesWillBeDiscovered(
const int3 & pos) const
{
int ret = 0;
for(int x = pos.x - sightRadius; x <= pos.x + sightRadius; x++)
{
for(int y = pos.y - sightRadius; y <= pos.y + sightRadius; y++)
{
int3 npos = int3(x, y, pos.z);
if(cbp->isInTheMap(npos)
&& pos.dist2d(npos) - 0.5 < sightRadius
&& !ts->fogOfWarMap[npos.x][npos.y][npos.z])
{
if(allowDeadEndCancellation
&& !hasReachableNeighbor(npos))
{
continue;
}
ret++;
}
}
}
return ret;
}
bool hasReachableNeighbor(const int3 &pos) const
{
for(crint3 dir : int3::getDirs())
{
int3 tile = pos + dir;
if(cbp->isInTheMap(tile))
{
auto isAccessible = aip->ah->isTileAccessible(hero, tile);
if(isAccessible)
return true;
}
}
return false;
}
};
}
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());
}
TGoalVec Explore::getAllPossibleSubgoals()
{
TGoalVec ret;
std::vector<const CGHeroInstance *> heroes;
if(hero)
{
heroes.push_back(hero.h);
}
else
{
//heroes = ai->getUnblockedHeroes();
heroes = cb->getHeroesInfo();
vstd::erase_if(heroes, [](const HeroPtr h)
{
if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer
return true;
if(!ai->isAbleToExplore(h))
return true;
return !h->movement; //saves time, immobile heroes are useless anyway
});
}
//try to use buildings that uncover map
std::vector<const CGObjectInstance *> objs;
for(auto obj : ai->visitableObjs)
{
if(!vstd::contains(ai->alreadyVisited, obj))
{
switch(obj->ID.num)
{
case Obj::REDWOOD_OBSERVATORY:
case Obj::PILLAR_OF_FIRE:
case Obj::CARTOGRAPHER:
objs.push_back(obj);
break;
case Obj::MONOLITH_ONE_WAY_ENTRANCE:
case Obj::MONOLITH_TWO_WAY:
case Obj::SUBTERRANEAN_GATE:
auto tObj = dynamic_cast<const CGTeleport *>(obj);
assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end());
if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability)
objs.push_back(obj);
break;
}
}
else
{
switch(obj->ID.num)
{
case Obj::MONOLITH_TWO_WAY:
case Obj::SUBTERRANEAN_GATE:
auto tObj = dynamic_cast<const CGTeleport *>(obj);
if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability)
break;
for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits)
{
if(!cb->getObj(exit))
{ // Always attempt to visit two-way teleports if one of channel exits is not visible
objs.push_back(obj);
break;
}
}
break;
}
}
}
for(auto h : heroes)
{
for(auto obj : objs) //double loop, performance risk?
{
auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy);
vstd::concatenate(ret, waysToVisitObj);
}
TSubgoal goal = exploreNearestNeighbour(h);
if(!goal->invalid())
{
ret.push_back(goal);
}
}
if(ret.empty())
{
for(auto h : heroes)
{
logAi->trace("Exploration searching for a new point for hero %s", h->name);
TSubgoal goal = explorationNewPoint(h);
if(goal->invalid())
{
ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore
}
else
{
ret.push_back(goal);
}
}
}
//we either don't have hero yet or none of heroes can explore
if((!hero || ret.empty()) && ai->canRecruitAnyHero())
ret.push_back(sptr(RecruitHero()));
if(ret.empty())
{
throw goalFulfilledException(sptr(Explore().sethero(hero)));
}
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);
for(crint3 dir : int3::getDirs())
{
int3 tile = hpos + dir;
if(cb->isInTheMap(tile))
{
scanResult.scanTile(tile);
}
}
return scanResult.bestGoal;
}
TSubgoal Explore::explorationNewPoint(HeroPtr h) const
{
ExplorationHelper scanResult(h, allowGatherArmy);
scanResult.scanSector(10);
if(!scanResult.bestGoal->invalid())
{
return scanResult.bestGoal;
}
scanResult.scanMap();
return scanResult.bestGoal;
}
TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const
{
TimeCheck tc("where to explore");
int3 hpos = h->visitablePos();
//look for nearby objs -> visit them if they're close enough
const int DIST_LIMIT = 3;
const float COST_LIMIT = .2; //todo: fine tune
std::vector<const CGObjectInstance *> nearbyVisitableObjs;
for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map
{
for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y)
{
for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false))
{
if(ai->isGoodForVisit(obj, h, COST_LIMIT))
{
nearbyVisitableObjs.push_back(obj);
}
}
}
}
if(nearbyVisitableObjs.size())
{
vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles
boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get()));
TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false));
if(!pickupNearestObj->invalid())
{
return pickupNearestObj;
}
}
//check if nearby tiles allow us to reveal anything - this is quick
return explorationBestNeighbour(hpos, h);
}

View File

@ -0,0 +1,66 @@
/*
* 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;
};
}

View File

@ -0,0 +1,70 @@
/*
* FindObj.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "FindObj.h"
#include "VisitObj.h"
#include "Explore.h"
#include "../VCAI.h"
#include "../AIUtility.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool FindObj::operator==(const FindObj & other) const
{
return other.hero.h == hero.h && other.objid == objid;
}
TSubgoal FindObj::whatToDoToAchieve()
{
const CGObjectInstance * o = nullptr;
if(resID > -1) //specified
{
for(const CGObjectInstance * obj : ai->visitableObjs)
{
if(obj->ID == objid && obj->subID == resID)
{
o = obj;
break; //TODO: consider multiple objects and choose best
}
}
}
else
{
for(const CGObjectInstance * obj : ai->visitableObjs)
{
if(obj->ID == objid)
{
o = obj;
break; //TODO: consider multiple objects and choose best
}
}
}
if(o && ai->isAccessible(o->pos)) //we don't use isAccessibleForHero as we don't know which hero it is
return sptr(VisitObj(o->id.getNum()));
else
return sptr(Explore());
}
bool FindObj::fulfillsMe(TSubgoal goal)
{
if (goal->goalType == VISIT_TILE) //visiting tile visits object at same time
{
if (!hero || hero == goal->hero)
for (auto obj : cb->getVisitableObjs(goal->tile)) //check if any object on that tile matches criteria
if (obj->visitablePos() == goal->tile) //object could be removed
if (obj->ID == objid && obj->subID == resID) //same type and subtype
return true;
}
return false;
}

View File

@ -0,0 +1,47 @@
/*
* FindObj.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT FindObj : public CGoal<FindObj>
{
public:
FindObj() {} // empty constructor not allowed
FindObj(int ID)
: CGoal(Goals::FIND_OBJ)
{
objid = ID;
resID = -1; //subid unspecified
priority = 1;
}
FindObj(int ID, int subID)
: CGoal(Goals::FIND_OBJ)
{
objid = ID;
resID = subID;
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const FindObj & other) const override;
};
}

View File

@ -0,0 +1,210 @@
/*
* GatherArmy.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool GatherArmy::operator==(const GatherArmy & other) const
{
return other.hero.h == hero.h || town == other.town;
}
std::string GatherArmy::completeMessage() const
{
return "Hero " + hero.get()->name + " gathered army of value " + boost::lexical_cast<std::string>(value);
}
TSubgoal GatherArmy::whatToDoToAchieve()
{
//TODO: find hero if none set
assert(hero.h);
return fh->chooseSolution(getAllPossibleSubgoals()); //find dwelling. use current hero to prevent him from doing nothing.
}
TGoalVec GatherArmy::getAllPossibleSubgoals()
{
//get all possible towns, heroes and dwellings we may use
TGoalVec ret;
if(!hero.validAndSet())
{
return ret;
}
//TODO: include evaluation of monsters gather in calculation
for(auto t : cb->getTownsInfo())
{
auto waysToVisit = ai->ah->howToVisitObj(hero, t);
if(waysToVisit.size())
{
//grab army from town
if(!t->visitingHero && howManyReinforcementsCanGet(hero.get(), t))
{
if(!vstd::contains(ai->townVisitsThisWeek[hero], t))
vstd::concatenate(ret, waysToVisit);
}
//buy army in town
if (!t->visitingHero || t->visitingHero == hero.get(true))
{
std::vector<int> values = {
value,
(int)howManyReinforcementsCanBuy(t->getUpperArmy(), t),
(int)howManyReinforcementsCanBuy(hero.get(), t) };
int val = *std::min_element(values.begin(), values.end());
if (val)
{
auto goal = sptr(BuyArmy(t, val).sethero(hero));
if(!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
ret.push_back(goal);
else
logAi->debug("Can not buy army, because of ai->ah->containsObjective");
}
}
//build dwelling
//TODO: plan building over multiple turns?
//auto bid = ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
//Do not use below code for now, rely on generic Build. Code below needs to know a lot of town/resource context to do more good than harm
/*auto bid = ai->ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 1);
if (bid.is_initialized())
{
auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
if (!ai->ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
ret.push_back(goal);
else
logAi->debug("Can not build a structure, because of ai->ah->containsObjective");
}*/
}
}
auto otherHeroes = cb->getHeroesInfo();
auto heroDummy = hero;
vstd::erase_if(otherHeroes, [heroDummy](const CGHeroInstance * h)
{
if(h == heroDummy.h)
return true;
else if(!ai->isAccessibleForHero(heroDummy->visitablePos(), h, true))
return true;
else if(!ai->canGetArmy(heroDummy.h, h)) //TODO: return actual aiValue
return true;
else if(ai->getGoal(h)->goalType == GATHER_ARMY)
return true;
else
return false;
});
for(auto h : otherHeroes)
{
// Go to the other hero if we are faster
if(!vstd::contains(ai->visitedHeroes[hero], h))
{
vstd::concatenate(ret, ai->ah->howToVisitObj(hero, h, false));
}
// Go to the other hero if we are faster
if(!vstd::contains(ai->visitedHeroes[h], hero))
{
vstd::concatenate(ret, ai->ah->howToVisitObj(h, hero.get(), false));
}
}
std::vector<const CGObjectInstance *> objs;
for(auto obj : ai->visitableObjs)
{
if(obj->ID == Obj::CREATURE_GENERATOR1)
{
auto relationToOwner = cb->getPlayerRelations(obj->getOwner(), ai->playerID);
//Use flagged dwellings only when there are available creatures that we can afford
if(relationToOwner == PlayerRelations::SAME_PLAYER)
{
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero.get(), dwelling));
if(val)
{
for(auto & creLevel : dwelling->creatures)
{
if(creLevel.first)
{
for(auto & creatureID : creLevel.second)
{
auto creature = VLC->creh->creatures[creatureID];
if(ai->ah->freeResources().canAfford(creature->cost))
objs.push_back(obj); //TODO: reserve resources?
}
}
}
}
}
}
}
for(auto h : cb->getHeroesInfo())
{
for(auto obj : objs)
{
//find safe dwelling
if(ai->isGoodForVisit(obj, h))
{
vstd::concatenate(ret, ai->ah->howToVisitObj(h, obj));
}
}
}
if(ai->canRecruitAnyHero() && ai->ah->freeGold() > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game
{
if(auto t = ai->findTownWithTavern())
{
for(auto h : cb->getAvailableHeroes(t)) //we assume that all towns have same set of heroes
{
if(h && h->getTotalStrength() > 500) //do not buy heroes with single creatures for GatherArmy
{
ret.push_back(sptr(RecruitHero()));
break;
}
}
}
}
if(ret.empty())
{
const bool allowGatherArmy = false;
if(hero == ai->primaryHero())
ret.push_back(sptr(Explore(allowGatherArmy)));
else
throw cannotFulfillGoalException("No ways to gather army");
}
return ret;
}

View File

@ -0,0 +1,38 @@
/*
* GatherArmy.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT GatherArmy : public CGoal<GatherArmy>
{
public:
GatherArmy()
: CGoal(Goals::GATHER_ARMY)
{
}
GatherArmy(int val)
: CGoal(Goals::GATHER_ARMY)
{
value = val;
priority = 2.5;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override;
virtual bool operator==(const GatherArmy & other) const override;
};
}

View File

@ -0,0 +1,149 @@
/*
* GatherTroops.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool GatherTroops::operator==(const GatherTroops & other) const
{
return objid == other.objid;
}
int GatherTroops::getCreaturesCount(const CArmedInstance * army)
{
int count = 0;
for(auto stack : army->Slots())
{
if(objid == stack.second->getCreatureID().num)
{
count += stack.second->count;
}
}
return count;
}
TSubgoal GatherTroops::whatToDoToAchieve()
{
logAi->trace("Entering GatherTroops::whatToDoToAchieve");
auto heroes = cb->getHeroesInfo(true);
for(auto hero : heroes)
{
if(getCreaturesCount(hero) >= this->value)
{
logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name);
throw goalFulfilledException(sptr(*this));
}
}
TGoalVec solutions = getAllPossibleSubgoals();
if(solutions.empty())
return sptr(Explore());
return fh->chooseSolution(solutions);
}
TGoalVec GatherTroops::getAllPossibleSubgoals()
{
TGoalVec solutions;
for(const CGTownInstance * t : cb->getTownsInfo())
{
int count = getCreaturesCount(t->getUpperArmy());
if(count >= this->value)
{
if(t->visitingHero)
{
solutions.push_back(sptr(VisitObj(t->id.getNum()).sethero(t->visitingHero.get())));
}
else
{
vstd::concatenate(solutions, ai->ah->howToVisitObj(t));
}
continue;
}
auto creature = VLC->creh->creatures[objid];
if(t->subID == creature->faction) //TODO: how to force AI to build unupgraded creatures? :O
{
auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1);
if(!creatures)
continue;
int upgradeNumber = vstd::find_pos(*creatures, creature->idNumber);
if(upgradeNumber < 0)
continue;
BuildingID bid(BuildingID::DWELL_FIRST + creature->level - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN);
if(t->hasBuilt(bid) && 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);
if(!d || obj->ID == Obj::TOWN)
continue;
for(auto creature : d->creatures)
{
if(creature.first) //there are more than 0 creatures avaliabe
{
for(auto type : creature.second)
{
if(type == objid && ai->ah->freeResources().canAfford(VLC->creh->creatures[type]->cost))
vstd::concatenate(solutions, ai->ah->howToVisitObj(obj));
}
}
}
}
return solutions;
//TODO: exchange troops between heroes
}
bool GatherTroops::fulfillsMe(TSubgoal goal)
{
if (!hero || hero == goal->hero) //we got army for desired hero or any hero
if (goal->objid == objid) //same creature type //TODO: consider upgrades?
if (goal->value >= value) //notify every time we get resources?
return true;
return false;
}

View File

@ -0,0 +1,43 @@
/*
* GatherTroops.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT GatherTroops : public CGoal<GatherTroops>
{
public:
GatherTroops()
: CGoal(Goals::GATHER_TROOPS)
{
priority = 2;
}
GatherTroops(int type, int val)
: CGoal(Goals::GATHER_TROOPS)
{
objid = type;
value = val;
priority = 2;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
virtual bool operator==(const GatherTroops & other) const override;
private:
int getCreaturesCount(const CArmedInstance * army);
};
}

View File

@ -0,0 +1,31 @@
/*
* GetArtOfType.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "GetArtOfType.h"
#include "FindObj.h"
#include "../VCAI.h"
#include "../AIUtility.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool GetArtOfType::operator==(const GetArtOfType & other) const
{
return other.hero.h == hero.h && other.objid == objid;
}
TSubgoal GetArtOfType::whatToDoToAchieve()
{
return sptr(FindObj(Obj::ARTIFACT, aid));
}

View File

@ -0,0 +1,40 @@
/*
* GetArtOfType.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT GetArtOfType : public CGoal<GetArtOfType>
{
public:
GetArtOfType()
: CGoal(Goals::GET_ART_TYPE)
{
}
GetArtOfType(int type)
: CGoal(Goals::GET_ART_TYPE)
{
aid = type;
priority = 2;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const GetArtOfType & other) const override;
};
}

View File

@ -0,0 +1,34 @@
/*
* Goals.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
#include "Invalid.h"
#include "BuildBoat.h"
#include "Build.h"
#include "BuildThis.h"
#include "Conquer.h"
#include "GatherArmy.h"
#include "Win.h"
#include "VisitObj.h"
#include "VisitTile.h"
#include "VisitHero.h"
#include "Explore.h"
#include "BuyArmy.h"
#include "GatherTroops.h"
#include "Trade.h"
#include "CollectRes.h"
#include "RecruitHero.h"
#include "GetArtOfType.h"
#include "ClearWayTo.h"
#include "DigAtTile.h"
#include "FindObj.h"
#include "CompleteQuest.h"
#include "AdventureSpellCast.h"

View File

@ -0,0 +1,41 @@
/*
* Invalid.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
namespace Goals
{
class DLL_EXPORT Invalid : public CGoal<Invalid>
{
public:
Invalid()
: CGoal(Goals::INVALID)
{
priority = -1e10;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override
{
return iAmElementar();
}
virtual bool operator==(const Invalid & other) const override
{
return true;
}
};
}

View File

@ -0,0 +1,38 @@
/*
* RecruitHero.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
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

@ -0,0 +1,41 @@
/*
* RecruitHero.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT RecruitHero : public CGoal<RecruitHero>
{
public:
RecruitHero()
: CGoal(Goals::RECRUIT_HERO)
{
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const RecruitHero & other) const override
{
return true;
}
};
}

View File

@ -0,0 +1,23 @@
/*
* Trade.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Trade.h"
using namespace Goals;
bool Trade::operator==(const Trade & other) const
{
return resID == other.resID;
}
TSubgoal Trade::whatToDoToAchieve()
{
return iAmElementar();
}

View File

@ -0,0 +1,38 @@
/*
* Trade.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT Trade : public CGoal<Trade>
{
public:
Trade()
: CGoal(Goals::TRADE)
{
}
Trade(int rid, int val, int Objid)
: CGoal(Goals::TRADE)
{
resID = rid;
value = val;
objid = Objid;
priority = 3; //trading is instant, but picking resources is free
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Trade & other) const override;
};
}

View File

@ -0,0 +1,74 @@
/*
* VisitHero.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "VisitHero.h"
#include "Explore.h"
#include "Invalid.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool VisitHero::operator==(const VisitHero & other) const
{
return other.hero.h == hero.h && other.objid == objid;
}
std::string VisitHero::completeMessage() const
{
return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<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

@ -0,0 +1,42 @@
/*
* VisitHero.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT VisitHero : public CGoal<VisitHero>
{
public:
VisitHero()
: CGoal(Goals::VISIT_HERO)
{
}
VisitHero(int hid)
: CGoal(Goals::VISIT_HERO)
{
objid = hid;
priority = 4;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
std::string completeMessage() const override;
virtual bool operator==(const VisitHero & other) const override;
};
}

View File

@ -0,0 +1,118 @@
/*
* VisitObj.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool VisitObj::operator==(const VisitObj & other) const
{
return other.hero.h == hero.h && other.objid == objid;
}
std::string VisitObj::completeMessage() const
{
return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<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)
{
objid = Objid;
auto obj = ai->myCb->getObjInstance(ObjectInstanceID(objid));
if(obj)
tile = obj->visitablePos();
else
logAi->error("VisitObj constructed with invalid object instance %d", 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

@ -0,0 +1,32 @@
/*
* VisitObj.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT VisitObj : public CGoal<VisitObj> //this goal was previously known as GetObj
{
public:
VisitObj() = delete; // empty constructor not allowed
VisitObj(int Objid);
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
std::string completeMessage() const override;
virtual bool operator==(const VisitObj & other) const override;
};
}

View File

@ -0,0 +1,98 @@
/*
* VisitTile.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
bool VisitTile::operator==(const VisitTile & other) const
{
return other.hero.h == hero.h && other.tile == tile;
}
std::string VisitTile::completeMessage() const
{
return "Hero " + hero.get()->name + " visited tile " + tile.toString();
}
TSubgoal VisitTile::whatToDoToAchieve()
{
auto ret = fh->chooseSolution(getAllPossibleSubgoals());
if(ret->hero)
{
if(isSafeToVisit(ret->hero, tile) && ai->isAccessibleForHero(tile, ret->hero))
{
ret->setisElementar(true);
return ret;
}
else
{
return sptr(GatherArmy(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

@ -0,0 +1,37 @@
/*
* VisitTile.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT VisitTile : public CGoal<VisitTile>
//tile, in conjunction with hero elementar; assumes tile is reachable
{
public:
VisitTile() {} // empty constructor not allowed
VisitTile(int3 Tile)
: CGoal(Goals::VISIT_TILE)
{
tile = Tile;
priority = 5;
}
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override;
virtual bool operator==(const VisitTile & other) const override;
};
}

191
AI/Nullkiller/Goals/Win.cpp Normal file
View File

@ -0,0 +1,191 @@
/*
* Win.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "Goals.h"
#include "../VCAI.h"
#include "../AIUtility.h"
#include "../AIhelper.h"
#include "../FuzzyHelper.h"
#include "../ResourceManager.h"
#include "../BuildingManager.h"
#include "../../../lib/mapping/CMap.h" //for victory conditions
#include "../../../lib/CPathfinder.h"
#include "../../../lib/StringConstants.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh;
using namespace Goals;
TSubgoal Win::whatToDoToAchieve()
{
auto toBool = [=](const EventCondition &)
{
// TODO: proper implementation
// Right now even already fulfilled goals will be included into generated list
// Proper check should test if event condition is already fulfilled
// Easiest way to do this is to call CGameState::checkForVictory but this function should not be
// used on client side or in AI code
return false;
};
std::vector<EventCondition> goals;
for(const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents)
{
//TODO: try to eliminate human player(s) using loss conditions that have isHuman element
if(event.effect.type == EventEffect::VICTORY)
{
boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals));
}
}
//TODO: instead of returning first encountered goal AI should generate list of possible subgoals
for(const EventCondition & goal : goals)
{
switch(goal.condition)
{
case EventCondition::HAVE_ARTIFACT:
return sptr(GetArtOfType(goal.objectType));
case EventCondition::DESTROY:
{
if(goal.object)
{
auto obj = cb->getObj(goal.object->id);
if(obj)
if(obj->getOwner() == ai->playerID) //we can't capture our own object
return sptr(Conquer());
return sptr(VisitObj(goal.object->id.getNum()));
}
else
{
// TODO: destroy all objects of type goal.objectType
// This situation represents "kill all creatures" condition from H3
break;
}
}
case EventCondition::HAVE_BUILDING:
{
// TODO build other buildings apart from Grail
// goal.objectType = buidingID to build
// goal.object = optional, town in which building should be built
// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions)
if(goal.objectType == BuildingID::GRAIL)
{
if(auto h = ai->getHeroWithGrail())
{
//hero is in a town that can host Grail
if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL))
{
const CGTownInstance * t = h->visitedTown;
return sptr(BuildThis(BuildingID::GRAIL, t).setpriority(10));
}
else
{
auto towns = cb->getTownsInfo();
towns.erase(boost::remove_if(towns,
[](const CGTownInstance * t) -> bool
{
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
}),
towns.end());
boost::sort(towns, CDistanceSorter(h.get()));
if(towns.size())
{
return sptr(VisitTile(towns.front()->visitablePos()).sethero(h));
}
}
}
double ratio = 0;
// maybe make this check a bit more complex? For example:
// 0.75 -> dig randomly within 3 tiles radius
// 0.85 -> radius now 2 tiles
// 0.95 -> 1 tile radius, position is fully known
// AFAIK H3 AI does something like this
int3 grailPos = cb->getGrailPos(&ratio);
if(ratio > 0.99)
{
return sptr(DigAtTile(grailPos));
} //TODO: use FIND_OBJ
else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks
return sptr(VisitObj(obj->id.getNum()));
else
return sptr(Explore());
}
break;
}
case EventCondition::CONTROL:
{
if(goal.object)
{
auto objRelations = cb->getPlayerRelations(ai->playerID, goal.object->tempOwner);
if(objRelations == PlayerRelations::ENEMIES)
{
return sptr(VisitObj(goal.object->id.getNum()));
}
else
{
// TODO: Defance
break;
}
}
else
{
//TODO: control all objects of type "goal.objectType"
// Represents H3 condition "Flag all mines"
break;
}
}
case EventCondition::HAVE_RESOURCES:
//TODO mines? piles? marketplace?
//save?
return sptr(CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value));
case EventCondition::HAVE_CREATURES:
return sptr(GatherTroops(goal.objectType, goal.value));
case EventCondition::TRANSPORT:
{
//TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it
// Represents "transport artifact" condition:
// goal.objectType = type of artifact
// goal.object = destination-town where artifact should be transported
break;
}
case EventCondition::STANDARD_WIN:
return sptr(Conquer());
// Conditions that likely don't need any implementation
case EventCondition::DAYS_PASSED:
break; // goal.value = number of days for condition to trigger
case EventCondition::DAYS_WITHOUT_TOWN:
break; // goal.value = number of days to trigger this
case EventCondition::IS_HUMAN:
break; // Should be only used in calculation of candidates (see toBool lambda)
case EventCondition::CONST_VALUE:
break;
case EventCondition::HAVE_0:
case EventCondition::HAVE_BUILDING_0:
case EventCondition::DESTROY_0:
//TODO: support new condition format
return sptr(Conquer());
default:
assert(0);
}
}
return sptr(Invalid());
}

39
AI/Nullkiller/Goals/Win.h Normal file
View File

@ -0,0 +1,39 @@
/*
* Win.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "CGoal.h"
struct HeroPtr;
class VCAI;
class FuzzyHelper;
namespace Goals
{
class DLL_EXPORT Win : public CGoal<Win>
{
public:
Win()
: CGoal(Goals::WIN)
{
priority = 100;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
virtual bool operator==(const Win & other) const override
{
return true;
}
};
}

View File

@ -0,0 +1,135 @@
#include "StdInc.h"
#include "MapObjectsEvaluator.h"
#include "../../lib/GameConstants.h"
#include "../../lib/VCMI_Lib.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/spells/CSpellHandler.h"
MapObjectsEvaluator & MapObjectsEvaluator::getInstance()
{
static std::unique_ptr<MapObjectsEvaluator> singletonInstance;
if(singletonInstance == nullptr)
singletonInstance.reset(new MapObjectsEvaluator());
return *(singletonInstance.get());
}
MapObjectsEvaluator::MapObjectsEvaluator()
{
for(auto primaryID : VLC->objtypeh->knownObjects())
{
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
{
auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
if(!handler->isStaticObject())
{
if(handler->getAiValue() != boost::none)
{
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get();
}
else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists
{
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get();
}
else //some default handling when aiValue not found, objects that require advanced properties (unavailable from handler) get their value calculated in getObjectValue
{
objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0;
}
}
}
}
}
boost::optional<int> MapObjectsEvaluator::getObjectValue(int primaryID, int secondaryID) const
{
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
auto object = objectDatabase.find(internalIdentifier);
if(object != objectDatabase.end())
return object->second;
logGlobal->trace("Unknown object for AI, ID: " + std::to_string(primaryID) + ", SubID: " + std::to_string(secondaryID));
return boost::optional<int>();
}
boost::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance * obj) const
{
if(obj->ID == Obj::HERO)
{
//special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes
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)
{
auto dwelling = dynamic_cast<const CGDwelling *>(obj);
int aiValue = 0;
for(auto & creLevel : dwelling->creatures)
{
for(auto & creatureID : creLevel.second)
{
auto creature = VLC->creh->creatures[creatureID];
aiValue += (creature->AIValue * creature->growth);
}
}
return aiValue;
}
else if(obj->ID == Obj::ARTIFACT)
{
auto artifactObject = dynamic_cast<const CGArtifact *>(obj);
switch(artifactObject->storedArtifact->artType->aClass)
{
case CArtifact::EartClass::ART_TREASURE:
return 2000;
case CArtifact::EartClass::ART_MINOR:
return 5000;
case CArtifact::EartClass::ART_MAJOR:
return 10000;
case CArtifact::EartClass::ART_RELIC:
return 20000;
case CArtifact::EartClass::ART_SPECIAL:
return 20000;
default:
return 0; //invalid artifact class
}
}
else if(obj->ID == Obj::SPELL_SCROLL)
{
auto scrollObject = dynamic_cast<const CGArtifact *>(obj);
auto spell = scrollObject->storedArtifact->getGivenSpellID().toSpell();
if(spell)
{
switch(spell->getLevel())
{
case 0: return 0; //scroll with creature ability? Let's assume it is useless
case 1: return 1000;
case 2: return 2000;
case 3: return 5000;
case 4: return 10000;
case 5: return 20000;
default: logAi->warn("AI detected spell scroll with spell level %s", spell->getLevel());
}
}
else
logAi->warn("AI found spell scroll with invalid spell ID: %s", scrollObject->storedArtifact->getGivenSpellID());
}
return getObjectValue(obj->ID, obj->subID);
}
void MapObjectsEvaluator::addObjectData(int primaryID, int secondaryID, int value) //by current design it updates value if already in AI database
{
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
objectDatabase[internalIdentifier] = value;
}
void MapObjectsEvaluator::removeObjectData(int primaryID, int secondaryID)
{
CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
vstd::erase_if_present(objectDatabase, internalIdentifier);
}

View File

@ -0,0 +1,26 @@
/*
* MapObjectsEvaluator.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../lib/mapObjects/CObjectClassesHandler.h"
class MapObjectsEvaluator
{
private:
std::map<CompoundMapObjectID, int> objectDatabase; //value for each object type
public:
MapObjectsEvaluator();
static MapObjectsEvaluator & getInstance();
boost::optional<int> getObjectValue(int primaryID, int secondaryID) const;
boost::optional<int> getObjectValue(const CGObjectInstance * obj) const;
void addObjectData(int primaryID, int secondaryID, int value);
void removeObjectData(int primaryID, int secondaryID);
};

View File

@ -0,0 +1,413 @@
/*
* AINodeStorage.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 "AINodeStorage.h"
#include "Actions/TownPortalAction.h"
#include "../Goals/Goals.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
#include "../../../lib/mapObjects/MapObjects.h"
#include "../../../lib/PathfinderUtil.h"
#include "../../../lib/CPlayerState.h"
AINodeStorage::AINodeStorage(const int3 & Sizes)
: sizes(Sizes)
{
nodes.resize(boost::extents[sizes.x][sizes.y][sizes.z][EPathfindingLayer::NUM_LAYERS][NUM_CHAINS]);
dangerEvaluator.reset(new FuzzyHelper());
}
AINodeStorage::~AINodeStorage() = default;
void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
{
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos;
const int3 sizes = gs->getMapSize();
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
const PlayerColor player = hero->tempOwner;
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
const bool useFlying = options.useFlying;
const bool useWaterWalking = options.useWaterWalking;
for(pos.x=0; pos.x < sizes.x; ++pos.x)
{
for(pos.y=0; pos.y < sizes.y; ++pos.y)
{
for(pos.z=0; pos.z < sizes.z; ++pos.z)
{
const TerrainTile * tile = &gs->map->getTile(pos);
switch(tile->terType)
{
case ETerrainType::ROCK:
break;
case ETerrainType::WATER:
resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
if(useFlying)
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
if(useWaterWalking)
resetTile(pos, ELayer::WATER, PathfinderUtil::evaluateAccessibility<ELayer::WATER>(pos, tile, fow, player, gs));
break;
default:
resetTile(pos, ELayer::LAND, PathfinderUtil::evaluateAccessibility<ELayer::LAND>(pos, tile, fow, player, gs));
if(useFlying)
resetTile(pos, ELayer::AIR, PathfinderUtil::evaluateAccessibility<ELayer::AIR>(pos, tile, fow, player, gs));
break;
}
}
}
}
}
const AIPathNode * AINodeStorage::getAINode(const CGPathNode * node) const
{
return static_cast<const AIPathNode *>(node);
}
void AINodeStorage::updateAINode(CGPathNode * node, std::function<void(AIPathNode *)> updater)
{
auto aiNode = static_cast<AIPathNode *>(node);
updater(aiNode);
}
bool AINodeStorage::isBattleNode(const CGPathNode * node) const
{
return (getAINode(node)->chainMask & BATTLE_CHAIN) > 0;
}
boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, const EPathfindingLayer layer, int chainNumber)
{
auto chains = nodes[pos.x][pos.y][pos.z][layer];
for(AIPathNode & node : chains)
{
if(node.chainMask == chainNumber)
{
return &node;
}
if(node.chainMask == 0)
{
node.chainMask = chainNumber;
return &node;
}
}
return boost::none;
}
CGPathNode * AINodeStorage::getInitialNode()
{
auto hpos = hero->getPosition(false);
auto initialNode =
getOrCreateNode(hpos, hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND, NORMAL_CHAIN)
.get();
initialNode->turns = 0;
initialNode->moveRemains = hero->movement;
initialNode->danger = 0;
initialNode->cost = 0.0;
return initialNode;
}
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)
{
for(int i = 0; i < NUM_CHAINS; i++)
{
AIPathNode & heroNode = nodes[coord.x][coord.y][coord.z][layer][i];
heroNode.chainMask = 0;
heroNode.danger = 0;
heroNode.manaCost = 0;
heroNode.specialAction.reset();
heroNode.update(coord, layer, accessibility);
}
}
void AINodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
{
const AIPathNode * srcNode = getAINode(source.node);
updateAINode(destination.node, [&](AIPathNode * dstNode)
{
dstNode->moveRemains = destination.movementLeft;
dstNode->turns = destination.turn;
dstNode->cost = destination.cost;
dstNode->danger = srcNode->danger;
dstNode->action = destination.action;
dstNode->theNodeBefore = srcNode->theNodeBefore;
dstNode->manaCost = srcNode->manaCost;
if(dstNode->specialAction)
{
dstNode->specialAction->applyOnDestination(getHero(), destination, source, dstNode, srcNode);
}
});
}
std::vector<CGPathNode *> AINodeStorage::calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper)
{
std::vector<CGPathNode *> neighbours;
neighbours.reserve(16);
const AIPathNode * srcNode = getAINode(source.node);
auto accessibleNeighbourTiles = pathfinderHelper->getNeighbourTiles(source);
for(auto & neighbour : accessibleNeighbourTiles)
{
for(EPathfindingLayer i = EPathfindingLayer::LAND; i <= EPathfindingLayer::AIR; i.advance(1))
{
auto nextNode = getOrCreateNode(neighbour, i, srcNode->chainMask);
if(!nextNode || nextNode.get()->accessible == CGPathNode::NOT_SET)
continue;
neighbours.push_back(nextNode.get());
}
}
return neighbours;
}
void AINodeStorage::setHero(HeroPtr heroPtr, const VCAI * _ai)
{
hero = heroPtr.get();
cb = _ai->myCb.get();
ai = _ai;
}
std::vector<CGPathNode *> AINodeStorage::calculateTeleportations(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper)
{
std::vector<CGPathNode *> neighbours;
if(source.isNodeObjectVisitable())
{
auto accessibleExits = pathfinderHelper->getTeleportExits(source);
auto srcNode = getAINode(source.node);
for(auto & neighbour : accessibleExits)
{
auto node = getOrCreateNode(neighbour, source.node->layer, srcNode->chainMask);
if(!node)
continue;
neighbours.push_back(node.get());
}
}
if(hero->getPosition(false) == source.coord)
{
calculateTownPortalTeleportations(source, neighbours);
}
return neighbours;
}
void AINodeStorage::calculateTownPortalTeleportations(
const PathNodeInfo & source,
std::vector<CGPathNode *> & neighbours)
{
SpellID spellID = SpellID::TOWN_PORTAL;
const CSpell * townPortal = spellID.toSpell();
auto srcNode = getAINode(source.node);
if(hero->canCastThisSpell(townPortal) && hero->mana >= hero->getSpellCost(townPortal))
{
auto towns = cb->getTownsInfo(false);
vstd::erase_if(towns, [&](const CGTownInstance * t) -> bool
{
return cb->getPlayerRelations(hero->tempOwner, t->tempOwner) == PlayerRelations::ENEMIES;
});
if(!towns.size())
{
return;
}
// TODO: Copy/Paste from TownPortalMechanics
auto skillLevel = hero->getSpellSchoolLevel(townPortal);
auto movementCost = GameConstants::BASE_MOVEMENT_COST * (skillLevel >= 3 ? 2 : 3);
if(hero->movement < movementCost)
{
return;
}
if(skillLevel < SecSkillLevel::ADVANCED)
{
const CGTownInstance * nearestTown = *vstd::minElementByFun(towns, [&](const CGTownInstance * t) -> int
{
return hero->visitablePos().dist2dSQ(t->visitablePos());
});
towns = std::vector<const CGTownInstance *>{ nearestTown };
}
for(const CGTownInstance * targetTown : towns)
{
if(targetTown->visitingHero)
continue;
auto nodeOptional = getOrCreateNode(targetTown->visitablePos(), EPathfindingLayer::LAND, srcNode->chainMask | CAST_CHAIN);
if(nodeOptional)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Adding town portal node at %s", targetTown->name);
#endif
AIPathNode * node = nodeOptional.get();
node->theNodeBefore = source.node;
node->specialAction.reset(new AIPathfinding::TownPortalAction(targetTown));
node->moveRemains = source.node->moveRemains;
neighbours.push_back(node);
}
}
}
}
bool AINodeStorage::hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const
{
auto pos = destination.coord;
auto chains = nodes[pos.x][pos.y][pos.z][EPathfindingLayer::LAND];
auto destinationNode = getAINode(destination.node);
for(const AIPathNode & node : chains)
{
auto sameNode = node.chainMask == destinationNode->chainMask;
if(sameNode || node.action == CGPathNode::ENodeAction::UNKNOWN)
{
continue;
}
if(node.danger <= destinationNode->danger && destinationNode->chainMask == 1 && node.chainMask == 0)
{
if(node.cost < destinationNode->cost)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Block ineficient move %s:->%s, mask=%i, mp diff: %i",
source.coord.toString(),
destination.coord.toString(),
destinationNode->chainMask,
node.moveRemains - destinationNode->moveRemains);
#endif
return true;
}
}
}
return false;
}
bool AINodeStorage::isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const
{
const AIPathNode & node = nodes[pos.x][pos.y][pos.z][layer][0];
return node.action != CGPathNode::ENodeAction::UNKNOWN;
}
std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand) const
{
std::vector<AIPath> paths;
auto chains = nodes[pos.x][pos.y][pos.z][isOnLand ? EPathfindingLayer::LAND : EPathfindingLayer::SAIL];
auto initialPos = hero->visitablePos();
for(const AIPathNode & node : chains)
{
if(node.action == CGPathNode::ENodeAction::UNKNOWN)
{
continue;
}
AIPath path;
const AIPathNode * current = &node;
while(current != nullptr && current->coord != initialPos)
{
AIPathNodeInfo pathNode;
pathNode.cost = current->cost;
pathNode.turns = current->turns;
pathNode.danger = current->danger;
pathNode.coord = current->coord;
path.nodes.push_back(pathNode);
path.specialAction = current->specialAction;
current = getAINode(current->theNodeBefore);
}
path.targetObjectDanger = evaluateDanger(pos);
paths.push_back(path);
}
return paths;
}
AIPath::AIPath()
: nodes({})
{
}
int3 AIPath::firstTileToGet() const
{
if(nodes.size())
{
return nodes.back().coord;
}
return int3(-1, -1, -1);
}
uint64_t AIPath::getPathDanger() const
{
if(nodes.size())
{
return nodes.front().danger;
}
return 0;
}
float AIPath::movementCost() const
{
if(nodes.size())
{
return nodes.front().cost;
}
// TODO: boost:optional?
return 0.0;
}
uint64_t AIPath::getTotalDanger(HeroPtr hero) const
{
uint64_t pathDanger = getPathDanger();
uint64_t danger = pathDanger > targetObjectDanger ? pathDanger : targetObjectDanger;
return danger;
}

View File

@ -0,0 +1,124 @@
/*
* AINodeStorage.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
#include "../../../lib/CPathfinder.h"
#include "../../../lib/mapObjects/CGHeroInstance.h"
#include "../AIUtility.h"
#include "../FuzzyHelper.h"
#include "../Goals/AbstractGoal.h"
#include "Actions/ISpecialAction.h"
struct AIPathNode;
struct AIPathNode : public CGPathNode
{
uint32_t chainMask;
uint64_t danger;
uint32_t manaCost;
std::shared_ptr<const ISpecialAction> specialAction;
};
struct AIPathNodeInfo
{
float cost;
int turns;
int3 coord;
uint64_t danger;
};
struct AIPath
{
std::vector<AIPathNodeInfo> nodes;
std::shared_ptr<const ISpecialAction> specialAction;
uint64_t targetObjectDanger;
AIPath();
/// Gets danger of path excluding danger of visiting the target object like creature bank
uint64_t getPathDanger() const;
/// Gets danger of path including danger of visiting the target object like creature bank
uint64_t getTotalDanger(HeroPtr hero) const;
int3 firstTileToGet() const;
float movementCost() const;
};
class AINodeStorage : public INodeStorage
{
private:
int3 sizes;
/// 1-3 - position on map, 4 - layer (air, water, land), 5 - chain (normal, battle, spellcast and combinations)
boost::multi_array<AIPathNode, 5> nodes;
const CPlayerSpecificInfoCallback * cb;
const VCAI * ai;
const CGHeroInstance * hero;
std::unique_ptr<FuzzyHelper> dangerEvaluator;
STRONG_INLINE
void resetTile(const int3 & tile, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility);
public:
/// more than 1 chain layer allows us to have more than 1 path to each tile so we can chose more optimal one.
static const int NUM_CHAINS = 3;
// chain flags, can be combined
static const int NORMAL_CHAIN = 1;
static const int BATTLE_CHAIN = 2;
static const int CAST_CHAIN = 4;
static const int RESOURCE_CHAIN = 8;
AINodeStorage(const int3 & sizes);
~AINodeStorage();
void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
virtual CGPathNode * getInitialNode() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual std::vector<CGPathNode *> calculateTeleportations(
const PathNodeInfo & source,
const PathfinderConfig * pathfinderConfig,
const CPathfinderHelper * pathfinderHelper) override;
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) override;
const AIPathNode * getAINode(const CGPathNode * node) const;
void updateAINode(CGPathNode * node, std::function<void (AIPathNode *)> updater);
bool isBattleNode(const CGPathNode * node) const;
bool hasBetterChain(const PathNodeInfo & source, CDestinationNodeInfo & destination) const;
boost::optional<AIPathNode *> getOrCreateNode(const int3 & coord, const EPathfindingLayer layer, int chainNumber);
std::vector<AIPath> getChainInfo(const int3 & pos, bool isOnLand) const;
bool isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const;
void setHero(HeroPtr heroPtr, const VCAI * ai);
const CGHeroInstance * getHero() const
{
return hero;
}
uint64_t evaluateDanger(const int3 & tile) const
{
return dangerEvaluator->evaluateDanger(tile, hero, ai);
}
private:
void calculateTownPortalTeleportations(const PathNodeInfo & source, std::vector<CGPathNode *> & neighbours);
};

View File

@ -0,0 +1,140 @@
/*
* AIPathfinder.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 "AIPathfinder.h"
#include "AIPathfinderConfig.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
std::vector<std::shared_ptr<AINodeStorage>> AIPathfinder::storagePool;
std::map<HeroPtr, std::shared_ptr<AINodeStorage>> AIPathfinder::storageMap;
AIPathfinder::AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai)
:cb(cb), ai(ai)
{
}
void AIPathfinder::init()
{
storagePool.clear();
storageMap.clear();
}
bool AIPathfinder::isTileAccessible(const HeroPtr & hero, const int3 & tile) const
{
std::shared_ptr<const AINodeStorage> nodeStorage = getStorage(hero);
return nodeStorage->isTileAccessible(tile, EPathfindingLayer::LAND)
|| nodeStorage->isTileAccessible(tile, EPathfindingLayer::SAIL);
}
std::vector<AIPath> AIPathfinder::getPathInfo(const HeroPtr & hero, const int3 & tile) const
{
std::shared_ptr<const AINodeStorage> nodeStorage = getStorage(hero);
const TerrainTile * tileInfo = cb->getTile(tile, false);
if(!tileInfo)
{
return std::vector<AIPath>();
}
return nodeStorage->getChainInfo(tile, !tileInfo->isWater());
}
void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
{
storageMap.clear();
auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr<AIPathfinding::AIPathfinderConfig> config)
{
logAi->debug("Recalculate paths for %s", hero->name);
cb->calculatePaths(config, hero);
};
std::vector<Task> calculationTasks;
for(HeroPtr hero : heroes)
{
std::shared_ptr<AINodeStorage> nodeStorage;
if(storageMap.size() < storagePool.size())
{
nodeStorage = storagePool.at(storageMap.size());
}
else
{
nodeStorage = std::make_shared<AINodeStorage>(cb->getMapSize());
storagePool.push_back(nodeStorage);
}
storageMap[hero] = nodeStorage;
nodeStorage->setHero(hero, ai);
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage);
calculationTasks.push_back(std::bind(calculatePaths, hero.get(), config));
}
int threadsCount = std::min(
boost::thread::hardware_concurrency(),
(uint32_t)calculationTasks.size());
if(threadsCount <= 1)
{
for(auto task : calculationTasks)
{
task();
}
}
else
{
CThreadHelper helper(&calculationTasks, threadsCount);
helper.run();
}
}
void AIPathfinder::updatePaths(const HeroPtr & hero)
{
std::shared_ptr<AINodeStorage> nodeStorage;
if(!vstd::contains(storageMap, hero))
{
if(storageMap.size() < storagePool.size())
{
nodeStorage = storagePool.at(storageMap.size());
}
else
{
nodeStorage = std::make_shared<AINodeStorage>(cb->getMapSize());
storagePool.push_back(nodeStorage);
}
storageMap[hero] = nodeStorage;
nodeStorage->setHero(hero, ai);
}
else
{
nodeStorage = storageMap.at(hero);
}
logAi->debug("Recalculate paths for %s", hero->name);
auto config = std::make_shared<AIPathfinding::AIPathfinderConfig>(cb, ai, nodeStorage);
cb->calculatePaths(config, hero.get());
}
std::shared_ptr<const AINodeStorage> AIPathfinder::getStorage(const HeroPtr & hero) const
{
return storageMap.at(hero);
}

View File

@ -0,0 +1,33 @@
/*
* AIPathfinder.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 "AINodeStorage.h"
#include "../AIUtility.h"
#include "../VCAI.h"
class AIPathfinder
{
private:
static std::vector<std::shared_ptr<AINodeStorage>> storagePool;
static std::map<HeroPtr, std::shared_ptr<AINodeStorage>> storageMap;
CPlayerSpecificInfoCallback * cb;
VCAI * ai;
std::shared_ptr<const AINodeStorage> getStorage(const HeroPtr & hero) const;
public:
AIPathfinder(CPlayerSpecificInfoCallback * cb, VCAI * ai);
std::vector<AIPath> getPathInfo(const HeroPtr & hero, const int3 & tile) const;
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const;
void updatePaths(std::vector<HeroPtr> heroes);
void updatePaths(const HeroPtr & heroes);
void init();
};

View File

@ -0,0 +1,43 @@
/*
* AIPathfinderConfig.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 "AIPathfinderConfig.h"
#include "Rules/AILayerTransitionRule.h"
#include "Rules/AIMovementAfterDestinationRule.h"
#include "Rules/AIMovementToDestinationRule.h"
#include "Rules/AIPreviousNodeRule.h"
namespace AIPathfinding
{
std::vector<std::shared_ptr<IPathfindingRule>> makeRuleset(
CPlayerSpecificInfoCallback * cb,
VCAI * ai,
std::shared_ptr<AINodeStorage> nodeStorage)
{
std::vector<std::shared_ptr<IPathfindingRule>> rules = {
std::make_shared<AILayerTransitionRule>(cb, ai, nodeStorage),
std::make_shared<DestinationActionRule>(),
std::make_shared<AIMovementToDestinationRule>(nodeStorage),
std::make_shared<MovementCostRule>(),
std::make_shared<AIPreviousNodeRule>(nodeStorage),
std::make_shared<AIMovementAfterDestinationRule>(cb, nodeStorage)
};
return rules;
}
AIPathfinderConfig::AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb,
VCAI * ai,
std::shared_ptr<AINodeStorage> nodeStorage)
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage))
{
}
}

View File

@ -0,0 +1,26 @@
/*
* AIPathfinderConfig.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 "AINodeStorage.h"
#include "../VCAI.h"
namespace AIPathfinding
{
class AIPathfinderConfig : public PathfinderConfig
{
public:
AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb,
VCAI * ai,
std::shared_ptr<AINodeStorage> nodeStorage);
};
}

View File

@ -0,0 +1,21 @@
/*
* BattleAction.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "../../Goals/VisitTile.h"
#include "BattleAction.h"
namespace AIPathfinding
{
Goals::TSubgoal BattleAction::whatToDo(const HeroPtr & hero) const
{
return Goals::sptr(Goals::VisitTile(targetTile).sethero(hero));
}
}

View File

@ -0,0 +1,30 @@
/*
* BattleAction.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 "ISpecialAction.h"
namespace AIPathfinding
{
class BattleAction : public ISpecialAction
{
private:
const int3 targetTile;
public:
BattleAction(const int3 targetTile)
:targetTile(targetTile)
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
};
}

View File

@ -0,0 +1,61 @@
/*
* BoatActions.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "../../Goals/AdventureSpellCast.h"
#include "../../Goals/BuildBoat.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
#include "BoatActions.h"
namespace AIPathfinding
{
Goals::TSubgoal BuildBoatAction::whatToDo(const HeroPtr & hero) const
{
return Goals::sptr(Goals::BuildBoat(shipyard));
}
Goals::TSubgoal SummonBoatAction::whatToDo(const HeroPtr & hero) const
{
return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::SUMMON_BOAT));
}
void SummonBoatAction::applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const
{
dstMode->manaCost = srcNode->manaCost + getManaCost(hero);
dstMode->theNodeBefore = source.node;
}
bool SummonBoatAction::isAffordableBy(const CGHeroInstance * hero, const AIPathNode * source) const
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Hero %s has %d mana and needed %d and already spent %d",
hero->name,
hero->mana,
getManaCost(hero),
source->manaCost);
#endif
return hero->mana >= source->manaCost + getManaCost(hero);
}
uint32_t SummonBoatAction::getManaCost(const CGHeroInstance * hero) const
{
SpellID summonBoat = SpellID::SUMMON_BOAT;
return hero->getSpellCost(summonBoat.toSpell());
}
}

View File

@ -0,0 +1,72 @@
/*
* BoatActions.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 "ISpecialAction.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
namespace AIPathfinding
{
class VirtualBoatAction : public ISpecialAction
{
private:
uint64_t specialChain;
public:
VirtualBoatAction(uint64_t specialChain)
:specialChain(specialChain)
{
}
uint64_t getSpecialChain() const
{
return specialChain;
}
};
class SummonBoatAction : public VirtualBoatAction
{
public:
SummonBoatAction()
:VirtualBoatAction(AINodeStorage::CAST_CHAIN)
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
virtual void applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const override;
bool isAffordableBy(const CGHeroInstance * hero, const AIPathNode * source) const;
private:
uint32_t getManaCost(const CGHeroInstance * hero) const;
};
class BuildBoatAction : public VirtualBoatAction
{
private:
const IShipyard * shipyard;
public:
BuildBoatAction(const IShipyard * shipyard)
:VirtualBoatAction(AINodeStorage::RESOURCE_CHAIN), shipyard(shipyard)
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
};
}

View File

@ -0,0 +1,31 @@
/*
* ISpecialAction.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 "../../Goals/AbstractGoal.h"
class AIPathNode;
class ISpecialAction
{
public:
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const = 0;
virtual void applyOnDestination(
const CGHeroInstance * hero,
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
AIPathNode * dstMode,
const AIPathNode * srcNode) const
{
}
};

View File

@ -0,0 +1,24 @@
/*
* TownPortalAction.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "../../Goals/AdventureSpellCast.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
#include "TownPortalAction.h"
using namespace AIPathfinding;
Goals::TSubgoal TownPortalAction::whatToDo(const HeroPtr & hero) const
{
const CGTownInstance * targetTown = target; // const pointer is not allowed in settown
return Goals::sptr(Goals::AdventureSpellCast(hero, SpellID::TOWN_PORTAL).settown(targetTown).settile(targetTown->visitablePos()));
}

View File

@ -0,0 +1,33 @@
/*
* TownPortalAction.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 "ISpecialAction.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
#include "../../Goals/AdventureSpellCast.h"
namespace AIPathfinding
{
class TownPortalAction : public ISpecialAction
{
private:
const CGTownInstance * target;
public:
TownPortalAction(const CGTownInstance * target)
:target(target)
{
}
virtual Goals::TSubgoal whatToDo(const HeroPtr & hero) const override;
};
}

View File

@ -0,0 +1,247 @@
/*
* PathfindingManager.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 "PathfindingManager.h"
#include "AIPathfinder.h"
#include "AIPathfinderConfig.h"
#include "../Goals/Goals.h"
#include "../../../lib/CGameInfoCallback.h"
#include "../../../lib/mapping/CMap.h"
PathfindingManager::PathfindingManager(CPlayerSpecificInfoCallback * CB, VCAI * AI)
: ai(AI), cb(CB)
{
}
void PathfindingManager::init(CPlayerSpecificInfoCallback * CB)
{
cb = CB;
pathfinder.reset(new AIPathfinder(cb, ai));
pathfinder->init();
}
void PathfindingManager::setAI(VCAI * AI)
{
ai = AI;
}
Goals::TGoalVec PathfindingManager::howToVisitTile(const int3 & tile) const
{
Goals::TGoalVec result;
auto heroes = cb->getHeroesInfo();
result.reserve(heroes.size());
for(auto hero : heroes)
{
vstd::concatenate(result, howToVisitTile(hero, tile));
}
return result;
}
Goals::TGoalVec PathfindingManager::howToVisitObj(ObjectIdRef obj) const
{
Goals::TGoalVec result;
auto heroes = cb->getHeroesInfo();
result.reserve(heroes.size());
for(auto hero : heroes)
{
vstd::concatenate(result, howToVisitObj(hero, obj));
}
return result;
}
Goals::TGoalVec PathfindingManager::howToVisitTile(const HeroPtr & hero, const int3 & tile, bool allowGatherArmy) const
{
auto result = findPath(hero, tile, allowGatherArmy, [&](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 = findPath(hero, dest, allowGatherArmy, [&](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
{
return pathfinder->getPathInfo(hero, tile);
}
Goals::TGoalVec PathfindingManager::findPath(
HeroPtr hero,
crint3 dest,
bool allowGatherArmy,
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(hero, 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)
{
int3 firstTileToGet = path.firstTileToGet();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Path found size=%i, first tile=%s", path.nodes.size(), firstTileToGet.toString());
#endif
if(firstTileToGet.valid() && ai->isTileNotReserved(hero.get(), firstTileToGet))
{
danger = path.getTotalDanger(hero);
if(isSafeToVisit(hero, danger))
{
Goals::TSubgoal solution;
if(path.specialAction)
{
solution = path.specialAction->whatToDo(hero);
}
else
{
solution = dest == firstTileToGet
? doVisitTile(firstTileToGet)
: clearWayTo(hero, firstTileToGet);
}
if(solution->invalid())
continue;
if(solution->evaluationContext.danger < danger)
solution->evaluationContext.danger = danger;
solution->evaluationContext.movementCost += path.movementCost();
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("It's safe for %s to visit tile %s with danger %s, goal %s", hero->name, dest.toString(), std::to_string(danger), 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->error("%s stands in the way of %s", topObj->getObjectName(), hero->getObjectName());
return sptr(Goals::Invalid());
}
}
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)
{
logAi->debug("AIPathfinder has been reseted.");
pathfinder->updatePaths(heroes);
}
void PathfindingManager::updatePaths(const HeroPtr & hero)
{
pathfinder->updatePaths(hero);
}

View File

@ -0,0 +1,70 @@
/*
* PathfindingManager.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 "../VCAI.h"
#include "AINodeStorage.h"
class DLL_EXPORT IPathfindingManager
{
public:
virtual ~IPathfindingManager() = default;
virtual void init(CPlayerSpecificInfoCallback * CB) = 0;
virtual void setAI(VCAI * AI) = 0;
virtual void updatePaths(std::vector<HeroPtr> heroes) = 0;
virtual void updatePaths(const HeroPtr & hero) = 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) const = 0;
virtual Goals::TGoalVec howToVisitObj(ObjectIdRef obj) const = 0;
virtual std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const = 0;
};
class DLL_EXPORT PathfindingManager : public IPathfindingManager
{
friend class AIhelper;
private:
CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
VCAI * ai;
std::unique_ptr<AIPathfinder> pathfinder;
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) const override;
Goals::TGoalVec howToVisitObj(ObjectIdRef obj) const override;
std::vector<AIPath> getPathsToTile(const HeroPtr & hero, const int3 & tile) const override;
void updatePaths(std::vector<HeroPtr> heroes) override;
void updatePaths(const HeroPtr & hero) override;
STRONG_INLINE
bool isTileAccessible(const HeroPtr & hero, const int3 & tile) const
{
return pathfinder->isTileAccessible(hero, tile);
}
private:
void init(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
Goals::TGoalVec findPath(
HeroPtr hero,
crint3 dest,
bool allowGatherArmy,
const std::function<Goals::TSubgoal(int3)> goalFactory) const;
Goals::TSubgoal clearWayTo(HeroPtr hero, int3 firstTileToGet) const;
};

View File

@ -0,0 +1,154 @@
/*
* AILayerTransitionRule.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 "AILayerTransitionRule.h"
namespace AIPathfinding
{
AILayerTransitionRule::AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), ai(ai), nodeStorage(nodeStorage)
{
setup();
}
void AILayerTransitionRule::process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
LayerTransitionRule::process(source, destination, pathfinderConfig, pathfinderHelper);
if(!destination.blocked)
{
return;
}
if(source.node->layer == EPathfindingLayer::LAND && destination.node->layer == EPathfindingLayer::SAIL)
{
std::shared_ptr<const VirtualBoatAction> virtualBoat = findVirtualBoat(destination, source);
if(virtualBoat && tryEmbarkVirtualBoat(destination, source, virtualBoat))
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace("Embarking to virtual boat while moving %s -> %s!", source.coord.toString(), destination.coord.toString());
#endif
}
}
}
void AILayerTransitionRule::setup()
{
std::vector<const IShipyard *> shipyards;
for(const CGTownInstance * t : cb->getTownsInfo())
{
if(t->hasBuilt(BuildingID::SHIPYARD))
shipyards.push_back(t);
}
for(const CGObjectInstance * obj : ai->visitableObjs)
{
if(obj->ID != Obj::TOWN) //towns were handled in the previous loop
{
if(const IShipyard * shipyard = IShipyard::castFrom(obj))
shipyards.push_back(shipyard);
}
}
for(const IShipyard * shipyard : shipyards)
{
if(shipyard->shipyardStatus() == IShipyard::GOOD)
{
int3 boatLocation = shipyard->bestLocation();
virtualBoats[boatLocation] = std::make_shared<BuildBoatAction>(shipyard);
logAi->debug("Virtual boat added at %s", boatLocation.toString());
}
}
auto hero = nodeStorage->getHero();
auto summonBoatSpell = SpellID(SpellID::SUMMON_BOAT).toSpell();
if(hero->canCastThisSpell(summonBoatSpell)
&& hero->getSpellSchoolLevel(summonBoatSpell) >= SecSkillLevel::ADVANCED)
{
// TODO: For lower school level we might need to check the existance of some boat
summonableVirtualBoat.reset(new SummonBoatAction());
}
}
std::shared_ptr<const VirtualBoatAction> AILayerTransitionRule::findVirtualBoat(
CDestinationNodeInfo & destination,
const PathNodeInfo & source) const
{
std::shared_ptr<const VirtualBoatAction> virtualBoat;
if(vstd::contains(virtualBoats, destination.coord))
{
virtualBoat = virtualBoats.at(destination.coord);
}
else if(
summonableVirtualBoat
&& summonableVirtualBoat->isAffordableBy(nodeStorage->getHero(), nodeStorage->getAINode(source.node)))
{
virtualBoat = summonableVirtualBoat;
}
return virtualBoat;
}
bool AILayerTransitionRule::tryEmbarkVirtualBoat(
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
std::shared_ptr<const VirtualBoatAction> virtualBoat) const
{
bool result = false;
nodeStorage->updateAINode(destination.node, [&](AIPathNode * node)
{
auto boatNodeOptional = nodeStorage->getOrCreateNode(
node->coord,
node->layer,
node->chainMask | virtualBoat->getSpecialChain());
if(boatNodeOptional)
{
AIPathNode * boatNode = boatNodeOptional.get();
if(boatNode->action == CGPathNode::UNKNOWN)
{
boatNode->specialAction = virtualBoat;
destination.blocked = false;
destination.action = CGPathNode::ENodeAction::EMBARK;
destination.node = boatNode;
result = true;
}
else
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Special transition node already allocated. Blocked moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
}
}
else
{
logAi->debug(
"Can not allocate special transition node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
}
});
return result;
}
}

View File

@ -0,0 +1,52 @@
/*
* AILayerTransitionRule.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 "../AINodeStorage.h"
#include "../../VCAI.h"
#include "../Actions/BoatActions.h"
#include "../../../../CCallback.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
namespace AIPathfinding
{
class AILayerTransitionRule : public LayerTransitionRule
{
private:
CPlayerSpecificInfoCallback * cb;
VCAI * ai;
std::map<int3, std::shared_ptr<const BuildBoatAction>> virtualBoats;
std::shared_ptr<AINodeStorage> nodeStorage;
std::shared_ptr<const SummonBoatAction> summonableVirtualBoat;
public:
AILayerTransitionRule(CPlayerSpecificInfoCallback * cb, VCAI * ai, std::shared_ptr<AINodeStorage> nodeStorage);
virtual void process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override;
private:
void setup();
std::shared_ptr<const VirtualBoatAction> findVirtualBoat(
CDestinationNodeInfo & destination,
const PathNodeInfo & source) const;
bool tryEmbarkVirtualBoat(
CDestinationNodeInfo & destination,
const PathNodeInfo & source,
std::shared_ptr<const VirtualBoatAction> virtualBoat) const;
};
}

View File

@ -0,0 +1,147 @@
/*
* AIMovementAfterDestinationRule.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 "AIMovementAfterDestinationRule.h"
#include "../Actions/BattleAction.h"
namespace AIPathfinding
{
AIMovementAfterDestinationRule::AIMovementAfterDestinationRule(
CPlayerSpecificInfoCallback * cb,
std::shared_ptr<AINodeStorage> nodeStorage)
:cb(cb), nodeStorage(nodeStorage)
{
}
void AIMovementAfterDestinationRule::process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
if(nodeStorage->hasBetterChain(source, destination))
{
destination.blocked = true;
return;
}
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
if(blocker == BlockingReason::NONE)
return;
if(blocker == BlockingReason::DESTINATION_BLOCKVIS && destination.nodeObject)
{
auto objID = destination.nodeObject->ID;
auto enemyHero = objID == Obj::HERO && destination.objectRelations == PlayerRelations::ENEMIES;
if(!enemyHero && !isObjectRemovable(destination.nodeObject))
{
destination.blocked = true;
}
return;
}
if(blocker == BlockingReason::DESTINATION_VISIT)
{
return;
}
if(blocker == BlockingReason::DESTINATION_GUARDED)
{
auto srcGuardians = cb->getGuardingCreatures(source.coord);
auto destGuardians = cb->getGuardingCreatures(destination.coord);
if(destGuardians.empty())
{
destination.blocked = true;
return;
}
vstd::erase_if(destGuardians, [&](const CGObjectInstance * destGuard) -> bool
{
return vstd::contains(srcGuardians, destGuard);
});
auto guardsAlreadyBypassed = destGuardians.empty() && srcGuardians.size();
if(guardsAlreadyBypassed && nodeStorage->isBattleNode(source.node))
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
const AIPathNode * destNode = nodeStorage->getAINode(destination.node);
auto battleNodeOptional = nodeStorage->getOrCreateNode(
destination.coord,
destination.node->layer,
destNode->chainMask | AINodeStorage::BATTLE_CHAIN);
if(!battleNodeOptional)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Can not allocate battle node while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
destination.blocked = true;
return;
}
AIPathNode * battleNode = battleNodeOptional.get();
if(battleNode->locked)
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Block bypass guard at destination while moving %s -> %s",
source.coord.toString(),
destination.coord.toString());
#endif
destination.blocked = true;
return;
}
auto danger = nodeStorage->evaluateDanger(destination.coord);
destination.node = battleNode;
nodeStorage->commit(destination, source);
if(battleNode->danger < danger)
{
battleNode->danger = danger;
}
battleNode->specialAction = std::make_shared<BattleAction>(destination.coord);
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Begin bypass guard at destination with danger %s while moving %s -> %s",
std::to_string(danger),
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
destination.blocked = true;
}
}

View File

@ -0,0 +1,36 @@
/*
* AIMovementAfterDestinationRule.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 "../AINodeStorage.h"
#include "../../VCAI.h"
#include "../../../../CCallback.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
namespace AIPathfinding
{
class AIMovementAfterDestinationRule : public MovementAfterDestinationRule
{
private:
CPlayerSpecificInfoCallback * cb;
std::shared_ptr<AINodeStorage> nodeStorage;
public:
AIMovementAfterDestinationRule(CPlayerSpecificInfoCallback * cb, std::shared_ptr<AINodeStorage> nodeStorage);
virtual void process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override;
};
}

View File

@ -0,0 +1,51 @@
/*
* AIMovementToDestinationRule.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 "AIMovementToDestinationRule.h"
namespace AIPathfinding
{
AIMovementToDestinationRule::AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage)
: nodeStorage(nodeStorage)
{
}
void AIMovementToDestinationRule::process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
auto blocker = getBlockingReason(source, destination, pathfinderConfig, pathfinderHelper);
if(blocker == BlockingReason::NONE)
return;
if(blocker == BlockingReason::DESTINATION_BLOCKED
&& destination.action == CGPathNode::EMBARK
&& nodeStorage->getAINode(destination.node)->specialAction)
{
return;
}
if(blocker == BlockingReason::SOURCE_GUARDED && nodeStorage->isBattleNode(source.node))
{
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Bypass src guard while moving from %s to %s",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
destination.blocked = true;
}
}

View File

@ -0,0 +1,35 @@
/*
* AIMovementToDestinationRule.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 "../AINodeStorage.h"
#include "../../VCAI.h"
#include "../../../../CCallback.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
namespace AIPathfinding
{
class AIMovementToDestinationRule : public MovementToDestinationRule
{
private:
std::shared_ptr<AINodeStorage> nodeStorage;
public:
AIMovementToDestinationRule(std::shared_ptr<AINodeStorage> nodeStorage);
virtual void process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override;
};
}

View File

@ -0,0 +1,47 @@
/*
* AIPreviousNodeRule.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 "AIPreviousNodeRule.h"
namespace AIPathfinding
{
AIPreviousNodeRule::AIPreviousNodeRule(std::shared_ptr<AINodeStorage> nodeStorage)
: nodeStorage(nodeStorage)
{
}
void AIPreviousNodeRule::process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const
{
if(source.node->action == CGPathNode::ENodeAction::BLOCKING_VISIT || source.node->action == CGPathNode::ENodeAction::VISIT)
{
// we can not directly bypass objects, we need to interact with them first
destination.node->theNodeBefore = source.node;
#ifdef VCMI_TRACE_PATHFINDER
logAi->trace(
"Link src node %s to destination node %s while bypassing visitable obj",
source.coord.toString(),
destination.coord.toString());
#endif
return;
}
auto aiSourceNode = nodeStorage->getAINode(source.node);
if(aiSourceNode->specialAction)
{
// there is some action on source tile which should be performed before we can bypass it
destination.node->theNodeBefore = source.node;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* AIPreviousNodeRule.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 "../AINodeStorage.h"
#include "../../VCAI.h"
#include "../../../../CCallback.h"
#include "../../../../lib/mapping/CMap.h"
#include "../../../../lib/mapObjects/MapObjects.h"
namespace AIPathfinding
{
class AIPreviousNodeRule : public MovementToDestinationRule
{
private:
std::shared_ptr<AINodeStorage> nodeStorage;
public:
AIPreviousNodeRule(std::shared_ptr<AINodeStorage> nodeStorage);
virtual void process(
const PathNodeInfo & source,
CDestinationNodeInfo & destination,
const PathfinderConfig * pathfinderConfig,
CPathfinderHelper * pathfinderHelper) const override;
};
}

View File

@ -0,0 +1,364 @@
/*
* 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

@ -0,0 +1,113 @@
/*
* 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;
}
};

431
AI/Nullkiller/SectorMap.cpp Normal file
View File

@ -0,0 +1,431 @@
/*
* SectorMap.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 "SectorMap.h"
#include "VCAI.h"
#include "../../CCallback.h"
#include "../../lib/mapping/CMap.h"
#include "../../lib/mapObjects/MapObjects.h"
#include "../../lib/CPathfinder.h"
#include "../../lib/CGameState.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
SectorMap::SectorMap()
{
update();
}
SectorMap::SectorMap(HeroPtr h)
{
update();
makeParentBFS(h->visitablePos());
}
bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t)
{
if (t->blocked && !t->visitable)
{
sec = NOT_AVAILABLE;
return true;
}
return false;
}
bool SectorMap::markIfBlocked(TSectorID & sec, crint3 pos)
{
return markIfBlocked(sec, pos, getTile(pos));
}
void SectorMap::update()
{
visibleTiles = cb->getAllVisibleTiles();
auto shape = visibleTiles->shape();
sector.resize(boost::extents[shape[0]][shape[1]][shape[2]]);
clear();
int curSector = 3; //0 is invisible, 1 is not explored
CCallback * cbp = cb.get(); //optimization
foreach_tile_pos([&](crint3 pos)
{
if (retrieveTile(pos) == NOT_CHECKED)
{
if (!markIfBlocked(retrieveTile(pos), pos))
exploreNewSector(pos, curSector++, cbp);
}
});
valid = true;
}
SectorMap::TSectorID & SectorMap::retrieveTileN(SectorMap::TSectorArray & a, const int3 & pos)
{
return a[pos.x][pos.y][pos.z];
}
const SectorMap::TSectorID & SectorMap::retrieveTileN(const SectorMap::TSectorArray & a, const int3 & pos)
{
return a[pos.x][pos.y][pos.z];
}
void SectorMap::clear()
{
//TODO: rotate to [z][x][y]
auto fow = cb->getVisibilityMap();
//TODO: any magic to automate this? will need array->array conversion
//std::transform(fow.begin(), fow.end(), sector.begin(), [](const ui8 &f) -> unsigned short
//{
// return f; //type conversion
//});
auto width = fow.size();
auto height = fow.front().size();
auto depth = fow.front().front().size();
for (size_t x = 0; x < width; x++)
{
for (size_t y = 0; y < height; y++)
{
for (size_t z = 0; z < depth; z++)
sector[x][y][z] = fow[x][y][z];
}
}
valid = false;
}
void SectorMap::exploreNewSector(crint3 pos, int num, CCallback * cbp)
{
Sector & s = infoOnSectors[num];
s.id = num;
s.water = getTile(pos)->isWater();
std::queue<int3> toVisit;
toVisit.push(pos);
while (!toVisit.empty())
{
int3 curPos = toVisit.front();
toVisit.pop();
TSectorID & sec = retrieveTile(curPos);
if (sec == NOT_CHECKED)
{
const TerrainTile * t = getTile(curPos);
if (!markIfBlocked(sec, curPos, t))
{
if (t->isWater() == s.water) //sector is only-water or only-land
{
sec = num;
s.tiles.push_back(curPos);
foreach_neighbour(cbp, curPos, [&](CCallback * cbp, crint3 neighPos)
{
if (retrieveTile(neighPos) == NOT_CHECKED)
{
toVisit.push(neighPos);
//parent[neighPos] = curPos;
}
const TerrainTile * nt = getTile(neighPos);
if (nt && nt->isWater() != s.water && canBeEmbarkmentPoint(nt, s.water))
{
s.embarkmentPoints.push_back(neighPos);
}
});
if (t->visitable)
{
auto obj = t->visitableObjects.front();
if (cb->getObj(obj->id, false)) // FIXME: we have to filter invisible objcts like events, but probably TerrainTile shouldn't be used in SectorMap at all
s.visitableObjs.push_back(obj);
}
}
}
}
}
vstd::removeDuplicates(s.embarkmentPoints);
}
void SectorMap::write(crstring fname)
{
std::ofstream out(fname);
for (int k = 0; k < cb->getMapSize().z; k++)
{
for (int j = 0; j < cb->getMapSize().y; j++)
{
for (int i = 0; i < cb->getMapSize().x; i++)
{
out << (int)sector[i][j][k] << '\t';
}
out << std::endl;
}
out << std::endl;
}
}
/*
this functions returns one target tile or invalid tile. We will use it to poll possible destinations
For ship construction etc, another function (goal?) is needed
*/
int3 SectorMap::firstTileToGet(HeroPtr h, crint3 dst)
{
int3 ret(-1, -1, -1);
int sourceSector = retrieveTile(h->visitablePos());
int destinationSector = retrieveTile(dst);
const Sector * src = &infoOnSectors[sourceSector];
const Sector * dest = &infoOnSectors[destinationSector];
if (sourceSector != destinationSector) //use ships, shipyards etc..
{
if (ai->isAccessibleForHero(dst, h)) //pathfinder can find a way using ships and gates if tile is not blocked by objects
return dst;
std::map<const Sector *, const Sector *> preds;
std::queue<const Sector *> sectorQueue;
sectorQueue.push(src);
while (!sectorQueue.empty())
{
const Sector * s = sectorQueue.front();
sectorQueue.pop();
for (int3 ep : s->embarkmentPoints)
{
Sector * neigh = &infoOnSectors[retrieveTile(ep)];
//preds[s].push_back(neigh);
if (!preds[neigh])
{
preds[neigh] = s;
sectorQueue.push(neigh);
}
}
}
if (!preds[dest])
{
//write("test.txt");
return ret;
//throw cannotFulfillGoalException(boost::str(boost::format("Cannot find connection between sectors %d and %d") % src->id % dst->id));
}
std::vector<const Sector *> toTraverse;
toTraverse.push_back(dest);
while (toTraverse.back() != src)
{
toTraverse.push_back(preds[toTraverse.back()]);
}
if (preds[dest])
{
//TODO: would be nice to find sectors in loop
const Sector * sectorToReach = toTraverse.at(toTraverse.size() - 2);
if (!src->water && sectorToReach->water) //embark
{
//embark on ship -> look for an EP with a boat
auto firstEP = boost::find_if(src->embarkmentPoints, [=](crint3 pos) -> bool
{
const TerrainTile * t = getTile(pos);
if (t && t->visitableObjects.size() == 1 && t->topVisitableId() == Obj::BOAT)
{
if (retrieveTile(pos) == sectorToReach->id)
return true;
}
return false;
});
if (firstEP != src->embarkmentPoints.end())
{
return *firstEP;
}
else
{
//we need to find a shipyard with an access to the desired sector's EP
//TODO what about Summon Boat spell?
std::vector<const IShipyard *> shipyards;
for (const CGTownInstance * t : cb->getTownsInfo())
{
if (t->hasBuilt(BuildingID::SHIPYARD))
shipyards.push_back(t);
}
for (const CGObjectInstance * obj : ai->getFlaggedObjects())
{
if (obj->ID != Obj::TOWN) //towns were handled in the previous loop
{
if (const IShipyard * shipyard = IShipyard::castFrom(obj))
shipyards.push_back(shipyard);
}
}
shipyards.erase(boost::remove_if(shipyards, [=](const IShipyard * shipyard) -> bool
{
return shipyard->shipyardStatus() != 0 || retrieveTile(shipyard->bestLocation()) != sectorToReach->id;
}), shipyards.end());
if (!shipyards.size())
{
//TODO consider possibility of building shipyard in a town
return ret;
//throw cannotFulfillGoalException("There is no known shipyard!");
}
//we have only shipyards that possibly can build ships onto the appropriate EP
auto ownedGoodShipyard = boost::find_if(shipyards, [](const IShipyard * s) -> bool
{
return s->o->tempOwner == ai->playerID;
});
if (ownedGoodShipyard != shipyards.end())
{
const IShipyard * s = *ownedGoodShipyard;
TResources shipCost;
s->getBoatCost(shipCost);
if (cb->getResourceAmount().canAfford(shipCost))
{
int3 ret = s->bestLocation();
cb->buildBoat(s); //TODO: move actions elsewhere
return ret;
}
else
{
//TODO gather res
return ret;
//throw cannotFulfillGoalException("Not enough resources to build a boat");
}
}
else
{
//TODO pick best shipyard to take over
return shipyards.front()->o->visitablePos();
}
}
}
else if (src->water && !sectorToReach->water)
{
//TODO
//disembark
return ret;
}
else //use subterranean gates - not needed since gates are now handled via Pathfinder
{
return ret;
//throw cannotFulfillGoalException("Land-land and water-water inter-sector transitions are not implemented!");
}
}
else
{
return ret;
//throw cannotFulfillGoalException("Inter-sector route detection failed: not connected sectors?");
}
}
else //tiles are in same sector
{
return findFirstVisitableTile(h, dst);
}
}
int3 SectorMap::findFirstVisitableTile(HeroPtr h, crint3 dst)
{
int3 ret(-1, -1, -1);
int3 curtile = dst;
while (curtile != h->visitablePos())
{
auto topObj = cb->getTopObj(curtile);
if (topObj && topObj->ID == Obj::HERO && topObj != h.h)
{
if (cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
{
logAi->warn("Another allied hero stands in our way");
return ret;
}
}
if (ai->myCb->getPathsInfo(h.get())->getPathInfo(curtile)->reachable())
{
return curtile;
}
else
{
auto i = parent.find(curtile);
if (i != parent.end())
{
assert(curtile != i->second);
curtile = i->second;
}
else
{
return ret;
//throw cannotFulfillGoalException("Unreachable tile in sector? Should not happen!");
}
}
}
return ret;
}
void SectorMap::makeParentBFS(crint3 source)
{
parent.clear();
int mySector = retrieveTile(source);
std::queue<int3> toVisit;
toVisit.push(source);
while (!toVisit.empty())
{
int3 curPos = toVisit.front();
toVisit.pop();
TSectorID & sec = retrieveTile(curPos);
assert(sec == mySector); //consider only tiles from the same sector
UNUSED(sec);
foreach_neighbour(curPos, [&](crint3 neighPos)
{
if (retrieveTile(neighPos) == mySector && !vstd::contains(parent, neighPos))
{
if (cb->canMoveBetween(curPos, neighPos))
{
toVisit.push(neighPos);
parent[neighPos] = curPos;
}
}
});
}
}
SectorMap::TSectorID & SectorMap::retrieveTile(crint3 pos)
{
return retrieveTileN(sector, pos);
}
TerrainTile * SectorMap::getTile(crint3 pos) const
{
//out of bounds access should be handled by boost::multi_array
//still we cached this array to avoid any checks
return visibleTiles->operator[](pos.x)[pos.y][pos.z];
}
std::vector<const CGObjectInstance *> SectorMap::getNearbyObjs(HeroPtr h, bool sectorsAround)
{
const Sector * heroSector = &infoOnSectors[retrieveTile(h->visitablePos())];
if (sectorsAround)
{
std::vector<const CGObjectInstance *> ret;
for (auto embarkPoint : heroSector->embarkmentPoints)
{
const Sector * embarkSector = &infoOnSectors[retrieveTile(embarkPoint)];
range::copy(embarkSector->visitableObjs, std::back_inserter(ret));
}
return ret;
}
return heroSector->visitableObjs;
}

70
AI/Nullkiller/SectorMap.h Normal file
View File

@ -0,0 +1,70 @@
/*
* SectorMap.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"
enum
{
NOT_VISIBLE = 0,
NOT_CHECKED = 1,
NOT_AVAILABLE
};
struct SectorMap
{
//a sector is set of tiles that would be mutually reachable if all visitable objs would be passable (incl monsters)
struct Sector
{
int id;
std::vector<int3> tiles;
std::vector<int3> embarkmentPoints; //tiles of other sectors onto which we can (dis)embark
std::vector<const CGObjectInstance *> visitableObjs;
bool water; //all tiles of sector are land or water
Sector()
{
id = -1;
water = false;
}
};
typedef unsigned short TSectorID; //smaller than int to allow -1 value. Max number of sectors 65K should be enough for any proper map.
typedef boost::multi_array<TSectorID, 3> TSectorArray;
bool valid; //some kind of lazy eval
std::map<int3, int3> parent;
TSectorArray sector;
//std::vector<std::vector<std::vector<unsigned char>>> pathfinderSector;
std::map<int, Sector> infoOnSectors;
std::shared_ptr<boost::multi_array<TerrainTile *, 3>> visibleTiles;
SectorMap();
SectorMap(HeroPtr h);
void update();
void clear();
void exploreNewSector(crint3 pos, int num, CCallback * cbp);
void write(crstring fname);
bool markIfBlocked(TSectorID & sec, crint3 pos, const TerrainTile * t);
bool markIfBlocked(TSectorID & sec, crint3 pos);
TSectorID & retrieveTile(crint3 pos);
TSectorID & retrieveTileN(TSectorArray & vectors, const int3 & pos);
const TSectorID & retrieveTileN(const TSectorArray & vectors, const int3 & pos);
TerrainTile * getTile(crint3 pos) const;
std::vector<const CGObjectInstance *> getNearbyObjs(HeroPtr h, bool sectorsAround);
void makeParentBFS(crint3 source);
int3 firstTileToGet(HeroPtr h, crint3 dst); //if h wants to reach tile dst, which tile he should visit to clear the way?
int3 findFirstVisitableTile(HeroPtr h, crint3 dst);
};

1
AI/Nullkiller/StdInc.cpp Normal file
View File

@ -0,0 +1 @@
#include "StdInc.h"

2
AI/Nullkiller/StdInc.h Normal file
View File

@ -0,0 +1,2 @@
#pragma once
#include "../../Global.h"

184
AI/Nullkiller/VCAI.cbp Normal file
View File

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<CodeBlocks_project_file>
<FileVersion major="1" minor="6" />
<Project>
<Option title="VCAI" />
<Option pch_mode="2" />
<Option compiler="gcc" />
<Build>
<Target title="Debug-win32">
<Option platforms="Windows;" />
<Option output="../VCAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
<Option object_output="obj/Debug/x86" />
<Option type="3" />
<Option compiler="gcc" />
<Compiler>
<Add option="-Og" />
<Add option="-g" />
</Compiler>
<Linker>
<Add directory="../" />
<Add directory="$(#boost.lib32)" />
</Linker>
</Target>
<Target title="Release-win32">
<Option platforms="Windows;" />
<Option output="../VCAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
<Option object_output="obj/Release/x86" />
<Option type="3" />
<Option compiler="gcc" />
<Compiler>
<Add option="-fomit-frame-pointer" />
<Add option="-O3" />
</Compiler>
<Linker>
<Add option="-s" />
<Add directory="../" />
<Add directory="$(#boost.lib32)" />
</Linker>
</Target>
<Target title="Debug-win64">
<Option platforms="Windows;" />
<Option output="../VCAI" imp_lib="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).a" def_file="$(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).def" prefix_auto="1" extension_auto="1" />
<Option object_output="obj/Debug/x64" />
<Option type="3" />
<Option compiler="gnu_gcc_compiler_x64" />
<Compiler>
<Add option="-Og" />
<Add option="-g" />
</Compiler>
<Linker>
<Add directory="../" />
<Add directory="$(#boost.lib64)" />
</Linker>
</Target>
</Build>
<Compiler>
<Add option="-pedantic" />
<Add option="-Wextra" />
<Add option="-Wall" />
<Add option="-std=gnu++11" />
<Add option="-fexceptions" />
<Add option="-Wpointer-arith" />
<Add option="-Wno-switch" />
<Add option="-Wno-sign-compare" />
<Add option="-Wno-unused-parameter" />
<Add option="-Wno-overloaded-virtual" />
<Add option="-DBOOST_THREAD_USE_LIB" />
<Add option="-DBOOST_SYSTEM_NO_DEPRECATED" />
<Add option="-D_WIN32_WINNT=0x0501" />
<Add option="-D_WIN32" />
<Add option="-DFL_CPP11" />
<Add directory="$(#boost.include)" />
<Add directory="../../include" />
<Add directory="../FuzzyLite/fuzzylite" />
</Compiler>
<Linker>
<Add option="-lFuzzyLite" />
<Add option="-lboost_system$(#boost.libsuffix)" />
<Add option="-lboost_thread$(#boost.libsuffix)" />
<Add option="-lboost_chrono$(#boost.libsuffix)" />
<Add option="-lVCMI_lib" />
<Add directory="../.." />
</Linker>
<Unit filename="AIUtility.cpp" />
<Unit filename="AIUtility.h" />
<Unit filename="AIhelper.cpp" />
<Unit filename="AIhelper.h" />
<Unit filename="BuildingManager.cpp" />
<Unit filename="BuildingManager.h" />
<Unit filename="FuzzyEngines.cpp" />
<Unit filename="FuzzyEngines.h" />
<Unit filename="FuzzyHelper.cpp" />
<Unit filename="FuzzyHelper.h" />
<Unit filename="Goals/AbstractGoal.cpp" />
<Unit filename="Goals/AbstractGoal.h" />
<Unit filename="Goals/AdventureSpellCast.cpp" />
<Unit filename="Goals/AdventureSpellCast.h" />
<Unit filename="Goals/Build.cpp" />
<Unit filename="Goals/Build.h" />
<Unit filename="Goals/BuildBoat.cpp" />
<Unit filename="Goals/BuildBoat.h" />
<Unit filename="Goals/BuildThis.cpp" />
<Unit filename="Goals/BuildThis.h" />
<Unit filename="Goals/BuyArmy.cpp" />
<Unit filename="Goals/BuyArmy.h" />
<Unit filename="Goals/CGoal.h" />
<Unit filename="Goals/ClearWayTo.cpp" />
<Unit filename="Goals/ClearWayTo.h" />
<Unit filename="Goals/CollectRes.cpp" />
<Unit filename="Goals/CollectRes.h" />
<Unit filename="Goals/CompleteQuest.cpp" />
<Unit filename="Goals/CompleteQuest.h" />
<Unit filename="Goals/Conquer.cpp" />
<Unit filename="Goals/Conquer.h" />
<Unit filename="Goals/DigAtTile.cpp" />
<Unit filename="Goals/DigAtTile.h" />
<Unit filename="Goals/Explore.cpp" />
<Unit filename="Goals/Explore.h" />
<Unit filename="Goals/FindObj.cpp" />
<Unit filename="Goals/FindObj.h" />
<Unit filename="Goals/GatherArmy.cpp" />
<Unit filename="Goals/GatherArmy.h" />
<Unit filename="Goals/GatherTroops.cpp" />
<Unit filename="Goals/GatherTroops.h" />
<Unit filename="Goals/GetArtOfType.cpp" />
<Unit filename="Goals/GetArtOfType.h" />
<Unit filename="Goals/Goals.h" />
<Unit filename="Goals/Invalid.h" />
<Unit filename="Goals/RecruitHero.cpp" />
<Unit filename="Goals/RecruitHero.h" />
<Unit filename="Goals/Trade.cpp" />
<Unit filename="Goals/Trade.h" />
<Unit filename="Goals/VisitHero.cpp" />
<Unit filename="Goals/VisitHero.h" />
<Unit filename="Goals/VisitObj.cpp" />
<Unit filename="Goals/VisitObj.h" />
<Unit filename="Goals/VisitTile.cpp" />
<Unit filename="Goals/VisitTile.h" />
<Unit filename="Goals/Win.cpp" />
<Unit filename="Goals/Win.h" />
<Unit filename="MapObjectsEvaluator.cpp" />
<Unit filename="MapObjectsEvaluator.h" />
<Unit filename="Pathfinding/AINodeStorage.cpp" />
<Unit filename="Pathfinding/AINodeStorage.h" />
<Unit filename="Pathfinding/AIPathfinder.cpp" />
<Unit filename="Pathfinding/AIPathfinder.h" />
<Unit filename="Pathfinding/AIPathfinderConfig.cpp" />
<Unit filename="Pathfinding/AIPathfinderConfig.h" />
<Unit filename="Pathfinding/Actions/BattleAction.cpp" />
<Unit filename="Pathfinding/Actions/BattleAction.h" />
<Unit filename="Pathfinding/Actions/BoatActions.cpp" />
<Unit filename="Pathfinding/Actions/BoatActions.h" />
<Unit filename="Pathfinding/Actions/ISpecialAction.h" />
<Unit filename="Pathfinding/Actions/TownPortalAction.cpp" />
<Unit filename="Pathfinding/Actions/TownPortalAction.h" />
<Unit filename="Pathfinding/PathfindingManager.cpp" />
<Unit filename="Pathfinding/PathfindingManager.h" />
<Unit filename="Pathfinding/Rules/AILayerTransitionRule.cpp" />
<Unit filename="Pathfinding/Rules/AILayerTransitionRule.h" />
<Unit filename="Pathfinding/Rules/AIMovementAfterDestinationRule.cpp" />
<Unit filename="Pathfinding/Rules/AIMovementAfterDestinationRule.h" />
<Unit filename="Pathfinding/Rules/AIMovementToDestinationRule.cpp" />
<Unit filename="Pathfinding/Rules/AIMovementToDestinationRule.h" />
<Unit filename="Pathfinding/Rules/AIPreviousNodeRule.cpp" />
<Unit filename="Pathfinding/Rules/AIPreviousNodeRule.h" />
<Unit filename="ResourceManager.cpp" />
<Unit filename="ResourceManager.h" />
<Unit filename="SectorMap.cpp" />
<Unit filename="SectorMap.h" />
<Unit filename="StdInc.h">
<Option compile="1" />
<Option weight="0" />
</Unit>
<Unit filename="VCAI.cpp" />
<Unit filename="VCAI.h" />
<Unit filename="main.cpp" />
<Extensions>
<code_completion />
<envvars />
<debugger />
<lib_finder disable_auto="1" />
</Extensions>
</Project>
</CodeBlocks_project_file>

2929
AI/Nullkiller/VCAI.cpp Normal file

File diff suppressed because it is too large Load Diff

404
AI/Nullkiller/VCAI.h Normal file
View File

@ -0,0 +1,404 @@
/*
* VCAI.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 "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"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/mapObjects/MiscObjects.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CondSh.h"
#include "Pathfinding/AIPathfinder.h"
struct QuestInfo;
class AIhelper;
class AIStatus
{
boost::mutex mx;
boost::condition_variable cv;
BattleState battle;
std::map<QueryID, std::string> remainingQueries;
std::map<int, QueryID> requestToQueryID; //IDs of answer-requests sent to server => query ids (so we can match answer confirmation from server to the query)
std::vector<const CGObjectInstance *> objectsBeingVisited;
bool ongoingHeroMovement;
bool ongoingChannelProbing; // true if AI currently explore bidirectional teleport channel exits
bool havingTurn;
public:
AIStatus();
~AIStatus();
void setBattle(BattleState BS);
void setMove(bool ongoing);
void setChannelProbing(bool ongoing);
bool channelProbing();
BattleState getBattle();
void addQuery(QueryID ID, std::string description);
void removeQuery(QueryID ID);
int getQueriesCount();
void startedTurn();
void madeTurn();
void waitTillFree();
bool haveTurn();
void attemptedAnsweringQuery(QueryID queryID, int answerRequestID);
void receivedAnswerConfirmation(int answerRequestID, int result);
void heroVisit(const CGObjectInstance * obj, bool started);
template<typename Handler> void serialize(Handler & h, const int version)
{
h & battle;
h & remainingQueries;
h & requestToQueryID;
h & havingTurn;
}
};
class DLL_EXPORT VCAI : public CAdventureAI
{
public:
friend class FuzzyHelper;
friend class ResourceManager;
friend class BuildingManager;
std::map<TeleportChannelID, std::shared_ptr<TeleportChannel>> knownTeleportChannels;
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
ObjectInstanceID destinationTeleport;
int3 destinationTeleportPos;
std::vector<ObjectInstanceID> teleportChannelProbingList; //list of teleport channel exits that not visible and need to be (re-)explored
//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
std::set<HeroPtr> heroesUnableToExplore; //these heroes will not be polled for exploration in current state of game
//sets are faster to search, also do not contain duplicates
std::set<const CGObjectInstance *> visitableObjs;
std::set<const CGObjectInstance *> alreadyVisited;
std::set<const CGObjectInstance *> reservedObjs; //to be visited by specific hero
std::map<HeroPtr, std::set<HeroPtr>> visitedHeroes; //visited this turn //FIXME: this is just bug workaround
AIStatus status;
std::string battlename;
std::shared_ptr<CCallback> myCb;
std::unique_ptr<boost::thread> makingTurn;
ObjectInstanceID selectedObject;
AIhelper * ah;
VCAI();
virtual ~VCAI();
//TODO: use only smart pointers?
void tryRealize(Goals::Explore & g);
void tryRealize(Goals::RecruitHero & g);
void tryRealize(Goals::VisitTile & g);
void tryRealize(Goals::VisitObj & g);
void tryRealize(Goals::VisitHero & g);
void tryRealize(Goals::BuildThis & g);
void tryRealize(Goals::DigAtTile & g);
void tryRealize(Goals::Trade & g);
void tryRealize(Goals::BuyArmy & g);
void tryRealize(Goals::Invalid & g);
void tryRealize(Goals::AbstractGoal & g);
bool isTileNotReserved(const CGHeroInstance * h, int3 t) const; //the tile is not occupied by allied hero and the object is not reserved
std::string getBattleAIName() const override;
void init(std::shared_ptr<CCallback> CB) override;
void yourTurn() override;
void heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID) override; //pskill is gained primary skill, interface has to choose one of given skills and call callback with selection id
void commanderGotLevel(const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO
void showBlockingDialog(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel) override; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
void showGarrisonDialog(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID) override; //all stacks operations between these objects become allowed, interface has to call onEnd when done
void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
void saveGame(BinarySerializer & h, const int version) override; //saving
void loadGame(BinaryDeserializer & h, const int version) override; //loading
void finish() override;
void availableCreaturesChanged(const CGDwelling * town) override;
void heroMoved(const TryMoveHero & details) override;
void heroInGarrisonChange(const CGTownInstance * town) override;
void centerView(int3 pos, int focusTime) override;
void tileHidden(const std::unordered_set<int3, ShashInt3> & pos) override;
void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
void artifactAssembled(const ArtifactLocation & al) override;
void showTavernWindow(const CGObjectInstance * townOrTavern) override;
void showThievesGuildWindow(const CGObjectInstance * obj) override;
void playerBlocked(int reason, bool start) override;
void showPuzzleMap() override;
void showShipyardDialog(const IShipyard * obj) override;
void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
void artifactPut(const ArtifactLocation & al) override;
void artifactRemoved(const ArtifactLocation & al) override;
void artifactDisassembled(const ArtifactLocation & al) override;
void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
void tileRevealed(const std::unordered_set<int3, ShashInt3> & pos) override;
void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
void heroMovePointsChanged(const CGHeroInstance * hero) override;
void garrisonsChanged(ObjectInstanceID id1, ObjectInstanceID id2) override;
void newObject(const CGObjectInstance * obj) override;
void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
void playerBonusChanged(const Bonus & bonus, bool gain) override;
void heroCreated(const CGHeroInstance *) override;
void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
void showInfoDialog(const std::string & text, const std::vector<Component> & components, int soundID) override;
void requestRealized(PackageApplied * pa) override;
void receivedResource() override;
void objectRemoved(const CGObjectInstance * obj) override;
void showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor) override;
void heroManaPointsChanged(const CGHeroInstance * hero) override;
void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
void battleResultsApplied() override;
void objectPropertyChanged(const SetObjectProperty * sop) override;
void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
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);
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, const AIPath & path) const;
//void recruitCreatures(const CGTownInstance * t);
void recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter);
bool canGetArmy(const CGHeroInstance * h, const CGHeroInstance * source); //can we get any better stacks from other hero?
void pickBestCreatures(const CArmedInstance * army, const CArmedInstance * source); //called when we can't find a slot for new stack
void pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * other = nullptr);
void moveCreaturesToHero(const CGTownInstance * t);
void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
bool moveHeroToTile(int3 dst, HeroPtr h);
void buildStructure(const CGTownInstance * t, BuildingID building); //TODO: move to BuildingManager
void lostHero(HeroPtr h); //should remove all references to hero (assigned tasks and so on)
void waitTillFree();
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);
bool isAbleToExplore(HeroPtr h);
void clearPathsInfo();
void validateObject(const CGObjectInstance * obj); //checks if object is still visible and if not, removes references to it
void validateObject(ObjectIdRef obj); //checks if object is still visible and if not, removes references to it
void validateVisitableObjs();
void retrieveVisitableObjs(std::vector<const CGObjectInstance *> & out, bool includeOwned = false) const;
void retrieveVisitableObjs();
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
const CGObjectInstance * lookForArt(int aid) const;
bool isAccessible(const int3 & pos) const;
HeroPtr getHeroWithGrail() const;
const CGObjectInstance * getUnvisitedObj(const std::function<bool(const CGObjectInstance *)> & predicate);
bool isAccessibleForHero(const int3 & pos, HeroPtr h, bool includeAllies = false) const;
//optimization - use one SM for every hero call
const CGTownInstance * findTownWithTavern() const;
bool canRecruitAnyHero(const CGTownInstance * t = NULL) const;
Goals::TSubgoal getGoal(HeroPtr h) const;
bool canAct(HeroPtr h) const;
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);
//special function that can be called ONLY from game events handling thread and will send request ASAP
void requestActionASAP(std::function<void()> whatToDo);
#if 0
//disabled due to issue 2890
template<typename Handler> void registerGoals(Handler & h)
{
//h.template registerType<Goals::AbstractGoal, Goals::BoostHero>();
h.template registerType<Goals::AbstractGoal, Goals::Build>();
h.template registerType<Goals::AbstractGoal, Goals::BuildThis>();
//h.template registerType<Goals::AbstractGoal, Goals::CIssueCommand>();
h.template registerType<Goals::AbstractGoal, Goals::ClearWayTo>();
h.template registerType<Goals::AbstractGoal, Goals::CollectRes>();
h.template registerType<Goals::AbstractGoal, Goals::Conquer>();
h.template registerType<Goals::AbstractGoal, Goals::DigAtTile>();
h.template registerType<Goals::AbstractGoal, Goals::Explore>();
h.template registerType<Goals::AbstractGoal, Goals::FindObj>();
h.template registerType<Goals::AbstractGoal, Goals::GatherArmy>();
h.template registerType<Goals::AbstractGoal, Goals::GatherTroops>();
h.template registerType<Goals::AbstractGoal, Goals::GetArtOfType>();
h.template registerType<Goals::AbstractGoal, Goals::VisitObj>();
h.template registerType<Goals::AbstractGoal, Goals::Invalid>();
//h.template registerType<Goals::AbstractGoal, Goals::NotLose>();
h.template registerType<Goals::AbstractGoal, Goals::RecruitHero>();
h.template registerType<Goals::AbstractGoal, Goals::VisitHero>();
h.template registerType<Goals::AbstractGoal, Goals::VisitTile>();
h.template registerType<Goals::AbstractGoal, Goals::Win>();
}
#endif
template<typename Handler> void serializeInternal(Handler & h, const int version)
{
h & knownTeleportChannels;
h & knownSubterraneanGates;
h & destinationTeleport;
h & townVisitsThisWeek;
#if 0
//disabled due to issue 2890
h & lockedHeroes;
#else
{
ui32 length = 0;
h & length;
if(!h.saving)
{
std::set<ui32> loadedPointers;
lockedHeroes.clear();
for(ui32 index = 0; index < length; index++)
{
HeroPtr ignored1;
h & ignored1;
ui8 flag = 0;
h & flag;
if(flag)
{
ui32 pid = 0xffffffff;
h & pid;
if(!vstd::contains(loadedPointers, pid))
{
loadedPointers.insert(pid);
ui16 typeId = 0;
//this is the problem requires such hack
//we have to explicitly ignore invalid goal class type id
h & typeId;
Goals::AbstractGoal ignored2;
ignored2.serialize(h, version);
}
}
}
}
}
#endif
h & reservedHeroesMap; //FIXME: cannot instantiate abstract class
h & visitableObjs;
h & alreadyVisited;
h & reservedObjs;
if (version < 788 && !h.saving)
{
TResources saving;
h & saving; //mind the ambiguity
}
h & status;
h & battlename;
h & heroesUnableToExplore;
//myCB is restored after load by init call
}
};
class cannotFulfillGoalException : public std::exception
{
std::string msg;
public:
explicit cannotFulfillGoalException(crstring _Message)
: msg(_Message)
{
}
virtual ~cannotFulfillGoalException() throw ()
{
};
const char * what() const throw () override
{
return msg.c_str();
}
};
class goalFulfilledException : public std::exception
{
std::string msg;
public:
Goals::TSubgoal goal;
explicit goalFulfilledException(Goals::TSubgoal Goal)
: goal(Goal)
{
msg = goal->name();
}
virtual ~goalFulfilledException() throw ()
{
};
const char * what() const throw () override
{
return msg.c_str();
}
};
void makePossibleUpgrades(const CArmedInstance * obj);

244
AI/Nullkiller/VCAI.vcxproj Normal file
View File

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="RD|Win32">
<Configuration>RD</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="RD|x64">
<Configuration>RD</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{276C3DB0-7A6B-4417-8E5C-322B08633AAC}</ProjectGuid>
<RootNamespace>StupidAI</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v140_xp</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v140_xp</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PlatformToolset>v140_xp</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\VCMI_global_debug.props" />
<Import Project="..\..\VCMI_global.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\VCMI_global_debug.props" />
<Import Project="..\..\VCMI_global.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\VCMI_global_release.props" />
<Import Project="..\..\VCMI_global.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\VCMI_global_release.props" />
<Import Project="..\..\VCMI_global.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(VCMI_Out)\AI\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(VCMI_Out)\AI\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
<OutDir>$(VCMI_Out)/AI</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
<OutDir>$(VCMI_Out)\AI\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>$(FUZZYLITEDIR)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
<AdditionalOptions>/Zm210 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;FuzzyLite.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\..\libs;..\..;..</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
<AdditionalOptions>/Zm150 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;FuzzyLite.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(VCMI_Out);$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>..\FuzzyLite\fuzzylite</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<Optimization>MaxSpeed</Optimization>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;FuzzyLite.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(VCMI_Out);$(SolutionDir)\AI</AdditionalLibraryDirectories>
<AdditionalOptions>/d2:-notypeopt %(AdditionalOptions)</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
<ClCompile>
<AdditionalIncludeDirectories>
</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
<AdditionalOptions>/Zm150 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;FuzzyLite.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(VCMI_Out);$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="AIhelper.cpp" />
<ClCompile Include="AIUtility.cpp" />
<ClCompile Include="BuildingManager.cpp" />
<ClCompile Include="FuzzyEngines.cpp" />
<ClCompile Include="FuzzyHelper.cpp" />
<ClCompile Include="Goals\AbstractGoal.cpp" />
<ClCompile Include="Goals\AdventureSpellCast.cpp" />
<ClCompile Include="Goals\Build.cpp" />
<ClCompile Include="Goals\BuildBoat.cpp" />
<ClCompile Include="Goals\BuildThis.cpp" />
<ClCompile Include="Goals\BuyArmy.cpp" />
<ClCompile Include="Goals\ClearWayTo.cpp" />
<ClCompile Include="Goals\CollectRes.cpp" />
<ClCompile Include="Goals\CompleteQuest.cpp" />
<ClCompile Include="Goals\Conquer.cpp" />
<ClCompile Include="Goals\DigAtTile.cpp" />
<ClCompile Include="Goals\Explore.cpp" />
<ClCompile Include="Goals\FindObj.cpp" />
<ClCompile Include="Goals\GatherArmy.cpp" />
<ClCompile Include="Goals\GatherTroops.cpp" />
<ClCompile Include="Goals\GetArtOfType.cpp" />
<ClCompile Include="Goals\RecruitHero.cpp" />
<ClCompile Include="Goals\Trade.cpp" />
<ClCompile Include="Goals\VisitHero.cpp" />
<ClCompile Include="Goals\VisitObj.cpp" />
<ClCompile Include="Goals\VisitTile.cpp" />
<ClCompile Include="Goals\Win.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="MapObjectsEvaluator.cpp" />
<ClInclude Include="Pathfinding\Actions\BattleAction.h" />
<ClInclude Include="Pathfinding\Actions\TownPortalAction.h" />
<ClCompile Include="Pathfinding\Actions\BattleAction.cpp" />
<ClCompile Include="Pathfinding\Actions\TownPortalAction.cpp" />
<ClInclude Include="Pathfinding\Actions\BoatActions.h" />
<ClCompile Include="Pathfinding\Actions\BoatActions.cpp" />
<ClCompile Include="Pathfinding\AINodeStorage.cpp" />
<ClCompile Include="Pathfinding\AIPathfinder.cpp" />
<ClCompile Include="Pathfinding\AIPathfinderConfig.cpp" />
<ClCompile Include="Pathfinding\PathfindingManager.cpp" />
<ClCompile Include="Pathfinding\Rules\AILayerTransitionRule.cpp" />
<ClCompile Include="Pathfinding\Rules\AIMovementAfterDestinationRule.cpp" />
<ClCompile Include="Pathfinding\Rules\AIMovementToDestinationRule.cpp" />
<ClCompile Include="Pathfinding\Rules\AIPreviousNodeRule.cpp" />
<ClCompile Include="ResourceManager.cpp" />
<ClCompile Include="SectorMap.cpp" />
<ClCompile Include="StdInc.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='RD|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="VCAI.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="AIhelper.h" />
<ClInclude Include="AIUtility.h" />
<ClInclude Include="BuildingManager.h" />
<ClInclude Include="FuzzyEngines.h" />
<ClInclude Include="FuzzyHelper.h" />
<ClInclude Include="Goals\AbstractGoal.h" />
<ClInclude Include="Goals\AdventureSpellCast.h" />
<ClInclude Include="Goals\Build.h" />
<ClInclude Include="Goals\BuildBoat.h" />
<ClInclude Include="Goals\BuildThis.h" />
<ClInclude Include="Goals\BuyArmy.h" />
<ClInclude Include="Goals\CGoal.h" />
<ClInclude Include="Goals\ClearWayTo.h" />
<ClInclude Include="Goals\CollectRes.h" />
<ClInclude Include="Goals\CompleteQuest.h" />
<ClInclude Include="Goals\Conquer.h" />
<ClInclude Include="Goals\DigAtTile.h" />
<ClInclude Include="Goals\Explore.h" />
<ClInclude Include="Goals\FindObj.h" />
<ClInclude Include="Goals\GatherArmy.h" />
<ClInclude Include="Goals\GatherTroops.h" />
<ClInclude Include="Goals\GetArtOfType.h" />
<ClInclude Include="Goals\Goals.h" />
<ClInclude Include="Goals\Invalid.h" />
<ClInclude Include="Goals\RecruitHero.h" />
<ClInclude Include="Goals\Trade.h" />
<ClInclude Include="Goals\VisitHero.h" />
<ClInclude Include="Goals\VisitObj.h" />
<ClInclude Include="Goals\VisitTile.h" />
<ClInclude Include="Goals\Win.h" />
<ClInclude Include="MapObjectsEvaluator.h" />
<ClInclude Include="Pathfinding\Actions\ISpecialAction.h" />
<ClInclude Include="Pathfinding\AINodeStorage.h" />
<ClInclude Include="Pathfinding\AIPathfinder.h" />
<ClInclude Include="Pathfinding\AIPathfinderConfig.h" />
<ClInclude Include="Pathfinding\PathfindingManager.h" />
<ClInclude Include="Pathfinding\Rules\AILayerTransitionRule.h" />
<ClInclude Include="Pathfinding\Rules\AIMovementAfterDestinationRule.h" />
<ClInclude Include="Pathfinding\Rules\AIMovementToDestinationRule.h" />
<ClInclude Include="Pathfinding\Rules\AIPreviousNodeRule.h" />
<ClInclude Include="ResourceManager.h" />
<ClInclude Include="SectorMap.h" />
<ClInclude Include="StdInc.h" />
<ClInclude Include="VCAI.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,252 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="AIhelper.cpp" />
<ClCompile Include="AIUtility.cpp" />
<ClCompile Include="BuildingManager.cpp" />
<ClCompile Include="FuzzyEngines.cpp" />
<ClCompile Include="FuzzyHelper.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="MapObjectsEvaluator.cpp" />
<ClCompile Include="ResourceManager.cpp" />
<ClCompile Include="SectorMap.cpp" />
<ClCompile Include="StdInc.cpp" />
<ClCompile Include="VCAI.cpp" />
<ClCompile Include="Pathfinding\AINodeStorage.cpp">
<Filter>Pathfinding</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\AIPathfinder.cpp">
<Filter>Pathfinding</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\AIPathfinderConfig.cpp">
<Filter>Pathfinding</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\PathfindingManager.cpp">
<Filter>Pathfinding</Filter>
</ClCompile>
<ClCompile Include="Goals\AbstractGoal.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\Build.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\BuildBoat.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\BuildThis.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\BuyArmy.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\ClearWayTo.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\CollectRes.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\Conquer.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\DigAtTile.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\Explore.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\FindObj.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\GatherArmy.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\GatherTroops.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\GetArtOfType.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\RecruitHero.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\Trade.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\VisitHero.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\VisitObj.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\VisitTile.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\Win.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\CompleteQuest.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Goals\AdventureSpellCast.cpp">
<Filter>Goals</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Actions\BoatActions.cpp">
<Filter>Pathfinding\Actions</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Actions\BattleAction.cpp">
<Filter>Pathfinding\Actions</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Actions\TownPortalAction.cpp">
<Filter>Pathfinding\Actions</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Rules\AILayerTransitionRule.cpp">
<Filter>Pathfinding\Rules</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Rules\AIMovementAfterDestinationRule.cpp">
<Filter>Pathfinding\Rules</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Rules\AIMovementToDestinationRule.cpp">
<Filter>Pathfinding\Rules</Filter>
</ClCompile>
<ClCompile Include="Pathfinding\Rules\AIPreviousNodeRule.cpp">
<Filter>Pathfinding\Rules</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AIhelper.h" />
<ClInclude Include="AIUtility.h" />
<ClInclude Include="BuildingManager.h" />
<ClInclude Include="FuzzyEngines.h" />
<ClInclude Include="FuzzyHelper.h" />
<ClInclude Include="MapObjectsEvaluator.h" />
<ClInclude Include="ResourceManager.h" />
<ClInclude Include="SectorMap.h" />
<ClInclude Include="StdInc.h" />
<ClInclude Include="VCAI.h" />
<ClInclude Include="Pathfinding\AINodeStorage.h">
<Filter>Pathfinding</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\AIPathfinder.h">
<Filter>Pathfinding</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\AIPathfinderConfig.h">
<Filter>Pathfinding</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\PathfindingManager.h">
<Filter>Pathfinding</Filter>
</ClInclude>
<ClInclude Include="Goals\AbstractGoal.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Build.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\BuildBoat.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\BuildThis.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\BuyArmy.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\CGoal.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\ClearWayTo.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\CollectRes.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Conquer.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\DigAtTile.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Explore.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\FindObj.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\GatherArmy.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\GatherTroops.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\GetArtOfType.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Goals.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Invalid.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\RecruitHero.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Trade.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\VisitHero.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\VisitObj.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\VisitTile.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\Win.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\CompleteQuest.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Goals\AdventureSpellCast.h">
<Filter>Goals</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Actions\ISpecialAction.h">
<Filter>Pathfinding\Actions</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Actions\BoatActions.h">
<Filter>Pathfinding\Actions</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Actions\BattleAction.h">
<Filter>Pathfinding\Actions</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Actions\TownPortalAction.h">
<Filter>Pathfinding\Actions</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Rules\AILayerTransitionRule.h">
<Filter>Pathfinding\Rules</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Rules\AIMovementAfterDestinationRule.h">
<Filter>Pathfinding\Rules</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Rules\AIMovementToDestinationRule.h">
<Filter>Pathfinding\Rules</Filter>
</ClInclude>
<ClInclude Include="Pathfinding\Rules\AIPreviousNodeRule.h">
<Filter>Pathfinding\Rules</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Pathfinding">
<UniqueIdentifier>{f0ef4866-37a3-4a10-a6bf-34460fcefab5}</UniqueIdentifier>
</Filter>
<Filter Include="Goals">
<UniqueIdentifier>{f97140a0-eee3-456f-b586-4b13265c01da}</UniqueIdentifier>
</Filter>
<Filter Include="Pathfinding\Rules">
<UniqueIdentifier>{beabfdb9-2e76-4daa-8d1a-81086387f319}</UniqueIdentifier>
</Filter>
<Filter Include="Pathfinding\Actions">
<UniqueIdentifier>{3ebb4852-a986-447a-b5cc-20992df76f0c}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

32
AI/Nullkiller/main.cpp Normal file
View File

@ -0,0 +1,32 @@
/*
* main.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 "VCAI.h"
#ifdef __GNUC__
#define strcpy_s(a, b, c) strncpy(a, c, b)
#endif
static const char * g_cszAiName = "VCAI";
extern "C" DLL_EXPORT int GetGlobalAiVersion()
{
return AI_INTERFACE_VER;
}
extern "C" DLL_EXPORT void GetAiName(char * name)
{
strcpy_s(name, strlen(g_cszAiName) + 1, g_cszAiName);
}
extern "C" DLL_EXPORT void GetNewAI(std::shared_ptr<CGlobalAI> & out)
{
out = std::make_shared<VCAI>();
}