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

I have no idea what I'm doing

This commit is contained in:
DJWarmonger 2018-07-26 12:06:55 +02:00
parent 75f8c8b29a
commit 273802c92c
28 changed files with 1887 additions and 431 deletions

View File

@ -126,6 +126,11 @@ 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
@ -475,6 +480,50 @@ void getVisibleNeighbours(const std::vector<int3> & tiles, std::vector<int3> & o
}
}
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(HeroPtr h, const CGTownInstance * t)
{
ui64 aivalue = 0;
int freeHeroSlots = GameConstants::ARMY_SIZE - h->stacksCount();
for (auto const dc : t->creatures)
{
creInfo ci = infoFromDC(dc);
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;
}
}
return aivalue;
}
ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance * t)
{
ui64 ret = 0;

View File

@ -20,9 +20,11 @@
#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;
@ -35,7 +37,7 @@ 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 HeroPtr
struct DLL_EXPORT HeroPtr
{
const CGHeroInstance * h;
ObjectInstanceID hid;
@ -56,6 +58,7 @@ public:
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;
const CGHeroInstance * get(bool doWeExpectNull = false) const;
bool validAndSet() const;
@ -137,6 +140,15 @@ 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);
@ -160,6 +172,7 @@ bool compareMovement(HeroPtr lhs, HeroPtr rhs);
bool compareHeroStrength(HeroPtr h1, HeroPtr h2);
bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2);
bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2);
ui64 howManyReinforcementsCanBuy(HeroPtr h, const CGTownInstance * t);
ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance * t);
int3 whereToExplore(HeroPtr h);
@ -173,4 +186,4 @@ public:
{
}
bool operator()(const CGObjectInstance * lhs, const CGObjectInstance * rhs);
};
};

84
AI/VCAI/AIhelper.cpp Normal file
View File

@ -0,0 +1,84 @@
/*
* 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"
boost::thread_specific_ptr<AIhelper> ah;
AIhelper::AIhelper()
{
resourceManager.reset(new ResourceManager);
}
AIhelper::~AIhelper()
{
}
bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
{
return resourceManager->notifyGoalCompleted(goal);
}
void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
{
resourceManager->setCB(CB);
}
void AIhelper::setAI(VCAI * AI)
{
resourceManager->setAI(AI);
}
Goals::TSubgoal AIhelper::whatToDo(TResources & res, Goals::TSubgoal goal)
{
return resourceManager->whatToDo(res, goal);
}
Goals::TSubgoal AIhelper::whatToDo() const
{
return resourceManager->whatToDo();
}
bool AIhelper::hasTasksLeft() const
{
return resourceManager->hasTasksLeft();
}
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();
}

52
AI/VCAI/AIhelper.h Normal file
View File

@ -0,0 +1,52 @@
/*
* 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"
class ResourceManager;
//indirection interface for various modules
class DLL_EXPORT AIhelper : public IResourceManager
{
friend class VCAI;
friend struct SetGlobalState; //mess?
//members are thread_specific. AIhelper is global
std::shared_ptr<ResourceManager> resourceManager;
std::shared_ptr<VCAI> ai;
public:
AIhelper();
~AIhelper();
//TODO: consider common interface with Resource Manager?
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; //peek highest-priority goal
bool hasTasksLeft() const override;
private:
bool notifyGoalCompleted(Goals::TSubgoal goal);
void setCB(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
};

View File

@ -322,7 +322,7 @@ float FuzzyHelper::evaluate(Goals::Explore & g)
}
float FuzzyHelper::evaluate(Goals::RecruitHero & g)
{
return 1; //just try to recruit hero as one of options
return 1;
}
FuzzyHelper::EvalVisitTile::~EvalVisitTile()
{
@ -518,7 +518,7 @@ float FuzzyHelper::evaluate(Goals::ClearWayTo & g)
float FuzzyHelper::evaluate(Goals::BuildThis & g)
{
return 1;
return g.priority; //TODO
}
float FuzzyHelper::evaluate(Goals::DigAtTile & g)
{
@ -526,12 +526,16 @@ float FuzzyHelper::evaluate(Goals::DigAtTile & g)
}
float FuzzyHelper::evaluate(Goals::CollectRes & g)
{
return 0;
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::Invalid & g)
{
return -1e10;
@ -541,7 +545,7 @@ float FuzzyHelper::evaluate(Goals::AbstractGoal & g)
logAi->warn("Cannot evaluate goal %s", g.name());
return g.priority;
}
void FuzzyHelper::setPriority(Goals::TSubgoal & g)
void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pattern
{
g->setpriority(g->accept(this)); //this enforces returned value is set
}

View File

@ -73,6 +73,7 @@ public:
float evaluate(Goals::DigAtTile & g);
float evaluate(Goals::CollectRes & g);
float evaluate(Goals::Build & g);
float evaluate(Goals::BuyArmy & g);
float evaluate(Goals::GatherArmy & g);
float evaluate(Goals::ClearWayTo & g);
float evaluate(Goals::Invalid & g);

View File

@ -11,18 +11,23 @@
#include "Goals.h"
#include "VCAI.h"
#include "Fuzzy.h"
#include "ResourceManager.h"
#include "../../lib/mapping/CMap.h" //for victory conditions
#include "../../lib/CPathfinder.h"
#include "StringConstants.h"
#include "AIhelper.h"
extern boost::thread_specific_ptr<CCallback> cb;
extern boost::thread_specific_ptr<VCAI> ai;
extern FuzzyHelper * fh; //TODO: this logic should be moved inside VCAI
extern boost::thread_specific_ptr<AIhelper> ah;
extern FuzzyHelper * fh;
using namespace Goals;
TSubgoal Goals::sptr(const AbstractGoal & tmp)
{
std::shared_ptr<AbstractGoal> ptr;
TSubgoal ptr;
ptr.reset(tmp.clone());
return ptr;
}
@ -48,6 +53,9 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
case GATHER_ARMY:
desc = "GATHER ARMY";
break;
case BUY_ARMY:
return "BUY ARMY";
break;
case BOOST_HERO:
desc = "BOOST_HERO (unsupported)";
break;
@ -56,7 +64,7 @@ std::string Goals::AbstractGoal::name() const //TODO: virtualize
case BUILD_STRUCTURE:
return "BUILD STRUCTURE";
case COLLECT_RES:
desc = "COLLECT RESOURCE";
desc = "COLLECT RESOURCE " + GameConstants::RESOURCE_NAMES[resID] + " (" + boost::lexical_cast<std::string>(value) + ")";
break;
case GATHER_TROOPS:
desc = "GATHER TROOPS";
@ -114,21 +122,25 @@ bool Goals::AbstractGoal::operator==(AbstractGoal & g)
case INVALID:
case WIN:
case DO_NOT_LOSE:
case RECRUIT_HERO: //recruit any hero, as yet
case RECRUIT_HERO: //overloaded
return true;
break;
//assigned to hero, no parameters
case CONQUER:
case EXPLORE:
case GATHER_ARMY: //actual value is indifferent
case BOOST_HERO:
return g.hero.h == hero.h; //how comes HeroPtrs are equal for different heroes?
break;
case GATHER_ARMY: //actual value is indifferent
return (g.hero.h == hero.h || town == g.town); //TODO: gather army for town maybe?
break;
//assigned hero and tile
case VISIT_TILE:
case CLEAR_WAY_TO:
case DIG_AT_TILE:
return (g.hero.h == hero.h && g.tile == tile);
break;
@ -137,16 +149,20 @@ bool Goals::AbstractGoal::operator==(AbstractGoal & g)
case FIND_OBJ: //TODO: use subtype?
case VISIT_HERO:
case GET_ART_TYPE:
case DIG_AT_TILE:
return (g.hero.h == hero.h && g.objid == objid);
break;
case BUILD_STRUCTURE:
return (town == g.town && bid == g.bid); //build specific structure in specific town
break;
//no check atm
case COLLECT_RES:
return (resID == g.resID); //every hero may collect resources
break;
case GATHER_TROOPS:
case ISSUE_COMMAND:
case BUILD: //TODO: should be decomposed to build specific structures
case BUILD_STRUCTURE:
default:
return false;
}
@ -156,28 +172,56 @@ bool Goals::AbstractGoal::operator==(AbstractGoal & g)
namespace Goals
{
template<>
void CGoal<Win>::accept(VCAI * ai)
{
ai->tryRealize(static_cast<Win &>(*this));
}
template<>
void CGoal<Win>::accept(VCAI * ai)
{
ai->tryRealize(static_cast<Win &>(*this));
}
template<>
void CGoal<Build>::accept(VCAI * ai)
{
ai->tryRealize(static_cast<Build &>(*this));
}
template<>
float CGoal<Win>::accept(FuzzyHelper * f)
{
return f->evaluate(static_cast<Win &>(*this));
}
template<>
void CGoal<Build>::accept(VCAI * ai)
{
ai->tryRealize(static_cast<Build &>(*this));
}
template<>
float CGoal<Win>::accept(FuzzyHelper * f)
{
return f->evaluate(static_cast<Win &>(*this));
}
template<>
float CGoal<Build>::accept(FuzzyHelper * f)
{
return f->evaluate(static_cast<Build &>(*this));
}
template<>
float CGoal<Build>::accept(FuzzyHelper * f)
{
return f->evaluate(static_cast<Build &>(*this));
}
bool TSubgoal::operator==(const TSubgoal & rhs) const
{
return *get() == *rhs.get(); //comparison for Goals is overloaded, so they don't need to be identical to match
}
bool BuyArmy::operator==(BuyArmy & g)
{
//if (hero && hero != g.hero)
// return false;
return town == g.town;
}
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 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") % boost::lexical_cast<std::string>(value), town->name;
}
}
//TSubgoal AbstractGoal::whatToDoToAchieve()
@ -251,7 +295,7 @@ TSubgoal Win::whatToDoToAchieve()
if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL))
{
const CGTownInstance * t = h->visitedTown;
return sptr(Goals::BuildThis(BuildingID::GRAIL, t));
return sptr(Goals::BuildThis(BuildingID::GRAIL, t).setpriority(10));
}
else
{
@ -371,6 +415,19 @@ TSubgoal FindObj::whatToDoToAchieve()
return sptr(Goals::Explore());
}
bool Goals::FindObj::fulfillsMe(TSubgoal goal)
{
if (goal->goalType == Goals::VISIT_TILE) //visiting tile visits object at same time
{
if (!hero || hero == goal->hero)
for (auto obj : cb->getVisitableObjs(goal->tile)) //check if any object on that tile matches criteria
if (obj->visitablePos() == goal->tile) //object could be removed
if (obj->ID == objid && obj->subID == resID) //same type and subtype
return true;
}
return false;
}
std::string GetObj::completeMessage() const
{
return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
@ -403,13 +460,15 @@ TSubgoal GetObj::whatToDoToAchieve()
bool GetObj::fulfillsMe(TSubgoal goal)
{
if(goal->goalType == Goals::VISIT_TILE)
if(goal->goalType == Goals::VISIT_TILE) //visiting tile visits object at same time
{
auto obj = cb->getObj(ObjectInstanceID(objid));
if(obj && obj->visitablePos() == goal->tile) //object could be removed
return true;
if (!hero || hero == goal->hero)
{
auto obj = cb->getObj(ObjectInstanceID(objid));
if (obj && obj->visitablePos() == goal->tile) //object could be removed
return true;
}
}
return false;
}
@ -441,17 +500,18 @@ TSubgoal VisitHero::whatToDoToAchieve()
bool VisitHero::fulfillsMe(TSubgoal goal)
{
if(goal->goalType != Goals::VISIT_TILE)
//TODO: VisitObj shoudl not be used for heroes, but...
if(goal->goalType == Goals::VISIT_TILE)
{
return false;
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;
}
auto obj = cb->getObj(ObjectInstanceID(objid));
if(!obj)
{
logAi->error("Hero %s: VisitHero::fulfillsMe at %s: object %d not found", hero.name, goal->tile.toString(), objid);
return false;
}
return obj->visitablePos() == goal->tile;
return false;
}
TSubgoal GetArtOfType::whatToDoToAchieve()
@ -474,6 +534,14 @@ TSubgoal ClearWayTo::whatToDoToAchieve()
return (fh->chooseSolution(getAllPossibleSubgoals()));
}
bool Goals::ClearWayTo::fulfillsMe(TSubgoal goal)
{
if (goal->goalType == Goals::VISIT_TILE)
if (!hero || hero == goal->hero)
return tile == goal->tile;
return false;
}
TGoalVec ClearWayTo::getAllPossibleSubgoals()
{
TGoalVec ret;
@ -702,17 +770,21 @@ bool Explore::fulfillsMe(TSubgoal goal)
return false;
}
TSubgoal RecruitHero::whatToDoToAchieve()
{
const CGTownInstance * t = ai->findTownWithTavern();
if(!t)
return sptr(Goals::BuildThis(BuildingID::TAVERN));
return sptr(Goals::BuildThis(BuildingID::TAVERN).setpriority(2));
if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
return sptr(Goals::CollectRes(Res::GOLD, GameConstants::HERO_GOLD_COST));
TResources res;
res[Res::GOLD] = GameConstants::HERO_GOLD_COST;
return ah->whatToDo(res, iAmElementar()); //either buy immediately, or collect res
}
return iAmElementar();
bool Goals::RecruitHero::operator==(RecruitHero & g)
{
//TODO: check town and hero
return true; //for now, recruiting any hero will do
}
std::string VisitTile::completeMessage() const
@ -798,25 +870,158 @@ TSubgoal DigAtTile::whatToDoToAchieve()
TSubgoal BuildThis::whatToDoToAchieve()
{
//TODO check res
//look for town
//prerequisites?
return iAmElementar();
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
{
auto res = town->town->buildings.at(BuildingID(bid))->resources;
return ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
}
else
throw cannotFulfillGoalException("Cannot find town to build this");
}
TGoalVec Goals::CollectRes::getAllPossibleSubgoals()
{
TGoalVec ret;
auto givesResource = [this](const CGObjectInstance * obj) -> bool
{
//TODO: move this logic to object side
//TODO: remember mithril exists
//TODO: water objects
//TODO: Creature banks
//return false first from once-visitable, before checking if they were even visited
switch (obj->ID.num)
{
case Obj::TREASURE_CHEST:
return resID == Res::GOLD;
break;
case Obj::RESOURCE:
return obj->subID == resID;
break;
case Obj::MINE:
return (obj->subID == resID &&
(cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines
break;
case Obj::CAMPFIRE:
return true; //contains all resources
break;
case Obj::WINDMILL:
switch (resID)
{
case Res::GOLD:
case Res::WOOD:
return false;
}
break;
case Obj::WATER_WHEEL:
if (resID != Res::GOLD)
return false;
break;
case Obj::MYSTICAL_GARDEN:
if ((resID != Res::GOLD) && (resID != Res::GEMS))
return false;
break;
case Obj::LEAN_TO:
case Obj::WAGON:
if (resID != Res::GOLD)
return false;
break;
default:
return false;
break;
}
return !vstd::contains(ai->alreadyVisited, obj); //for weekly / once visitable
};
std::vector<const CGObjectInstance *> objs;
for (auto obj : ai->visitableObjs)
{
if (givesResource(obj))
objs.push_back(obj);
}
for (auto h : cb->getHeroesInfo())
{
auto sm = ai->getCachedSectorMap(h);
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)
{
int3 dest = obj->visitablePos();
auto t = sm->firstTileToGet(h, dest); //we assume that no more than one tile on the way is guarded
if (t.valid()) //we know any path at all
{
if (ai->isTileNotReserved(h, t)) //no other hero wants to conquer that tile
{
if (isSafeToVisit(h, dest))
{
if (dest != t) //there is something blocking our way
ret.push_back(sptr(Goals::ClearWayTo(dest, h).setisAbstract(true)));
else
{
ret.push_back(sptr(Goals::VisitTile(dest).sethero(h).setisAbstract(true)));
}
}
else //we need to get army in order to pick that object
ret.push_back(sptr(Goals::GatherArmy(evaluateDanger(dest, h) * SAFE_ATTACK_CONSTANT).sethero(h).setisAbstract(true)));
}
}
}
}
return ret;
}
TSubgoal CollectRes::whatToDoToAchieve()
{
auto goals = getAllPossibleSubgoals();
auto trade = whatToDoToTrade();
if (!trade->invalid())
goals.push_back(trade);
if (goals.empty())
return sptr(Goals::Explore()); //we can always do that
else
return fh->chooseSolution(goals); //TODO: evaluate trading
}
TSubgoal Goals::CollectRes::whatToDoToTrade()
{
std::vector<const IMarket *> markets;
std::vector<const CGObjectInstance *> visObjs;
ai->retrieveVisitableObjs(visObjs, true);
for(const CGObjectInstance * obj : visObjs)
for (const CGObjectInstance * obj : visObjs)
{
if(const IMarket * m = IMarket::castFrom(obj, false))
if (const IMarket * m = IMarket::castFrom(obj, false))
{
if(obj->ID == Obj::TOWN && obj->tempOwner == ai->playerID && m->allowsTrade(EMarketMode::RESOURCE_RESOURCE))
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) //TODO a moze po prostu test na pozwalanie handlu?
else if (obj->ID == Obj::TRADING_POST)
markets.push_back(m);
}
}
@ -828,20 +1033,20 @@ TSubgoal CollectRes::whatToDoToAchieve()
markets.erase(boost::remove_if(markets, [](const IMarket * market) -> bool
{
if(!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID))
if (!(market->o->ID == Obj::TOWN && market->o->tempOwner == ai->playerID))
{
if(!ai->isAccessible(market->o->visitablePos()))
if (!ai->isAccessible(market->o->visitablePos()))
return true;
}
return false;
}), markets.end());
if(!markets.size())
if (!markets.size())
{
for(const CGTownInstance * t : cb->getTownsInfo())
for (const CGTownInstance * t : cb->getTownsInfo())
{
if(cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED)
return sptr(Goals::BuildThis(BuildingID::MARKETPLACE, t));
if (cb->canBuildStructure(t, BuildingID::MARKETPLACE) == EBuildingState::ALLOWED)
return sptr(Goals::BuildThis(BuildingID::MARKETPLACE, t).setpriority(2));
}
}
else
@ -849,9 +1054,9 @@ TSubgoal CollectRes::whatToDoToAchieve()
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))
for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1))
{
if(i == resID)
if (i == resID)
continue;
int toGive = -1, toReceive = -1;
m->getOffer(i, resID, toGive, toReceive, EMarketMode::RESOURCE_RESOURCE);
@ -859,21 +1064,36 @@ TSubgoal CollectRes::whatToDoToAchieve()
howManyCanWeBuy += toReceive * (cb->getResourceAmount(i) / toGive);
}
if(howManyCanWeBuy + cb->getResourceAmount(static_cast<Res::ERes>(resID)) >= value)
if (howManyCanWeBuy >= value)
{
auto backObj = cb->getTopObj(m->o->visitablePos()); //it'll be a hero if we have one there; otherwise marketplace
assert(backObj);
if(backObj->tempOwner != ai->playerID)
auto objid = m->o->id.getNum();
if (backObj->tempOwner != ai->playerID)
{
return sptr(Goals::GetObj(m->o->id.getNum()));
return sptr(Goals::GetObj(objid));
}
else
{
return sptr(Goals::GetObj(m->o->id.getNum()).setisElementar(true));
if (m->o->ID == Obj::TOWN) //just trade remotely using town objid
return sptr(setobjid(objid).setisElementar(true));
else //just go there
return sptr(Goals::GetObj(objid).setisElementar(true));
}
}
}
return sptr(setisElementar(true)); //all the conditions for trade are met
return sptr(Goals::Invalid()); //cannot trade
//TODO: separate goal to execute trade?
//return sptr(setisElementar(true)); //not sure why we are here
}
bool CollectRes::fulfillsMe(TSubgoal goal)
{
if (goal->resID == resID)
if (goal->value >= value)
return true;
return false;
}
TSubgoal GatherTroops::whatToDoToAchieve()
@ -899,7 +1119,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
}
else
{
return sptr(Goals::BuildThis(bid, t));
return sptr(Goals::BuildThis(bid, t).setpriority(priority));
}
}
}
@ -915,7 +1135,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
{
for(auto type : creature.second)
{
if(type == objid && ai->freeResources().canAfford(VLC->creh->creatures[type]->cost))
if(type == objid && ah->freeResources().canAfford(VLC->creh->creatures[type]->cost))
dwellings.push_back(d);
}
}
@ -962,6 +1182,15 @@ TSubgoal GatherTroops::whatToDoToAchieve()
//TODO: exchange troops between heroes
}
bool Goals::GatherTroops::fulfillsMe(TSubgoal goal)
{
if (!hero || hero == goal->hero) //we got army for desired hero or any hero
if (goal->objid == objid) //same creature type //TODO: consider upgrades?
if (goal->value >= value) //notify every time we get resources?
return true;
return false;
}
TSubgoal Conquer::whatToDoToAchieve()
{
return fh->chooseSolution(getAllPossibleSubgoals());
@ -1050,6 +1279,14 @@ TSubgoal Build::whatToDoToAchieve()
return iAmElementar();
}
bool Goals::Build::fulfillsMe(TSubgoal goal)
{
if (goal->goalType == Goals::BUILD || goal->goalType == Goals::BUILD_STRUCTURE)
return (!town || town == goal->town); //building anything will do, in this town if set
else
return false;
}
TSubgoal Invalid::whatToDoToAchieve()
{
return iAmElementar();
@ -1082,14 +1319,25 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
auto pos = t->visitablePos();
if(ai->isAccessibleForHero(pos, hero))
{
//grab army from town
if(!t->visitingHero && howManyReinforcementsCanGet(hero, t))
{
if(!vstd::contains(ai->townVisitsThisWeek[hero], t))
ret.push_back(sptr(Goals::VisitTile(pos).sethero(hero)));
}
//buy army in town
if (!t->visitingHero || t->visitingHero != hero.get(true))
{
ui32 val = std::min<ui32>(value, howManyReinforcementsCanBuy(hero, t));
if (val)
ret.push_back(sptr(Goals::BuyArmy(t, val).sethero(hero)));
}
//build dwelling
auto bid = ai->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
if(bid != BuildingID::NONE)
ret.push_back(sptr(BuildThis(bid, t)));
if (bid != BuildingID::NONE)
{
ret.push_back(sptr(BuildThis(bid, t).setpriority(priority)));
}
}
}
@ -1134,7 +1382,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
for(auto & creatureID : creLevel.second)
{
auto creature = VLC->creh->creatures[creatureID];
if(ai->freeResources().canAfford(creature->cost))
if(ah->freeResources().canAfford(creature->cost))
objs.push_back(obj);
}
}
@ -1154,7 +1402,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
}
}
if(ai->canRecruitAnyHero() && ai->freeResources()[Res::GOLD] > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game
if(ai->canRecruitAnyHero() && ah->freeGold() > GameConstants::HERO_GOLD_COST) //this is not stupid in early phase of game
{
if(auto t = ai->findTownWithTavern())
{

View File

@ -23,14 +23,21 @@ namespace Goals
{
class AbstractGoal;
class VisitTile;
typedef std::shared_ptr<Goals::AbstractGoal> TSubgoal;
class DLL_EXPORT TSubgoal : public std::shared_ptr<Goals::AbstractGoal>
{
public:
bool operator==(const TSubgoal & rhs) const;
//TODO: serialize?
};
typedef std::vector<TSubgoal> TGoalVec;
enum EGoals
{
INVALID = -1,
WIN, DO_NOT_LOSE, CONQUER, BUILD, //build needs to get a real reasoning
EXPLORE, GATHER_ARMY, BOOST_HERO,
EXPLORE, GATHER_ARMY,
BOOST_HERO,
RECRUIT_HERO,
BUILD_STRUCTURE, //if hero set, then in visited town
COLLECT_RES,
@ -48,7 +55,8 @@ enum EGoals
VISIT_TILE, //tile, in conjunction with hero elementar; assumes tile is reachable
CLEAR_WAY_TO,
DIG_AT_TILE //elementar with hero on tile
DIG_AT_TILE,//elementar with hero on tile
BUY_ARMY //at specific town
};
//method chaining + clone pattern
@ -61,9 +69,9 @@ enum EGoals
enum {LOW_PR = -1};
TSubgoal sptr(const AbstractGoal & tmp);
DLL_EXPORT TSubgoal sptr(const AbstractGoal & tmp);
class AbstractGoal
class DLL_EXPORT AbstractGoal
{
public:
bool isElementar; VSETTER(bool, isElementar)
@ -129,7 +137,7 @@ public:
virtual bool operator==(AbstractGoal & g);
virtual bool fulfillsMe(Goals::TSubgoal goal) //TODO: multimethod instead of type check
{
return false;
return false; //use this method to check if goal is fulfilled by another (not equal) goal, operator == is handled spearately
}
template<typename Handler> void serialize(Handler & h, const int version)
@ -149,7 +157,7 @@ public:
}
};
template<typename T> class CGoal : public AbstractGoal
template<typename T> class DLL_EXPORT CGoal : public AbstractGoal
{
public:
CGoal<T>(EGoals goal = INVALID) : AbstractGoal(goal)
@ -186,8 +194,8 @@ public:
}
TSubgoal iAmElementar()
{
setisElementar(true);
std::shared_ptr<AbstractGoal> ptr;
setisElementar(true); //FIXME: it's not const-correct, maybe we shoudl only set returned clone?
TSubgoal ptr;
ptr.reset(clone());
return ptr;
}
@ -199,7 +207,7 @@ public:
}
};
class Invalid : public CGoal<Invalid>
class DLL_EXPORT Invalid : public CGoal<Invalid>
{
public:
Invalid()
@ -214,7 +222,7 @@ public:
TSubgoal whatToDoToAchieve() override;
};
class Win : public CGoal<Win>
class DLL_EXPORT Win : public CGoal<Win>
{
public:
Win()
@ -229,7 +237,7 @@ public:
TSubgoal whatToDoToAchieve() override;
};
class NotLose : public CGoal<NotLose>
class DLL_EXPORT NotLose : public CGoal<NotLose>
{
public:
NotLose()
@ -244,7 +252,7 @@ public:
//TSubgoal whatToDoToAchieve() override;
};
class Conquer : public CGoal<Conquer>
class DLL_EXPORT Conquer : public CGoal<Conquer>
{
public:
Conquer()
@ -256,7 +264,7 @@ public:
TSubgoal whatToDoToAchieve() override;
};
class Build : public CGoal<Build>
class DLL_EXPORT Build : public CGoal<Build>
{
public:
Build()
@ -269,9 +277,10 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
};
class Explore : public CGoal<Explore>
class DLL_EXPORT Explore : public CGoal<Explore>
{
public:
Explore()
@ -291,7 +300,7 @@ public:
bool fulfillsMe(TSubgoal goal) override;
};
class GatherArmy : public CGoal<GatherArmy>
class DLL_EXPORT GatherArmy : public CGoal<GatherArmy>
{
public:
GatherArmy()
@ -309,7 +318,28 @@ public:
std::string completeMessage() const override;
};
class BoostHero : public CGoal<BoostHero>
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 = 2;//TODO: evaluate?
}
bool operator==(BuyArmy & g);
bool fulfillsMe(TSubgoal goal) override;
TSubgoal whatToDoToAchieve() override;
std::string completeMessage() const override;
};
class DLL_EXPORT BoostHero : public CGoal<BoostHero>
{
public:
BoostHero()
@ -324,7 +354,7 @@ public:
//TSubgoal whatToDoToAchieve() override {return sptr(Invalid());};
};
class RecruitHero : public CGoal<RecruitHero>
class DLL_EXPORT RecruitHero : public CGoal<RecruitHero>
{
public:
RecruitHero()
@ -337,37 +367,37 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool operator==(RecruitHero & g);
};
class BuildThis : public CGoal<BuildThis>
class DLL_EXPORT BuildThis : public CGoal<BuildThis>
{
public:
BuildThis()
BuildThis() //should be private, but unit test uses it
: CGoal(Goals::BUILD_STRUCTURE)
{
//FIXME: should be not allowed (private)
}
{}
BuildThis(BuildingID Bid, const CGTownInstance * tid)
: CGoal(Goals::BUILD_STRUCTURE)
{
bid = Bid;
town = tid;
priority = 5;
priority = 1;
}
BuildThis(BuildingID Bid)
: CGoal(Goals::BUILD_STRUCTURE)
{
bid = Bid;
priority = 5;
priority = 1;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
//bool fulfillsMe(TSubgoal goal) override;
};
class CollectRes : public CGoal<CollectRes>
class DLL_EXPORT CollectRes : public CGoal<CollectRes>
{
public:
CollectRes()
@ -381,14 +411,13 @@ public:
value = val;
priority = 2;
}
TGoalVec getAllPossibleSubgoals() override
{
return TGoalVec();
};
TGoalVec getAllPossibleSubgoals() override;
TSubgoal whatToDoToAchieve() override;
TSubgoal whatToDoToTrade();
bool fulfillsMe(TSubgoal goal) override;
};
class GatherTroops : public CGoal<GatherTroops>
class DLL_EXPORT GatherTroops : public CGoal<GatherTroops>
{
public:
GatherTroops()
@ -408,9 +437,10 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
};
class GetObj : public CGoal<GetObj>
class DLL_EXPORT GetObj : public CGoal<GetObj>
{
public:
GetObj() {} // empty constructor not allowed
@ -434,7 +464,7 @@ public:
std::string completeMessage() const override;
};
class FindObj : public CGoal<FindObj>
class DLL_EXPORT FindObj : public CGoal<FindObj>
{
public:
FindObj() {} // empty constructor not allowed
@ -443,6 +473,7 @@ public:
: CGoal(Goals::FIND_OBJ)
{
objid = ID;
resID = -1; //subid unspecified
priority = 1;
}
FindObj(int ID, int subID)
@ -457,9 +488,10 @@ public:
return TGoalVec();
}
TSubgoal whatToDoToAchieve() override;
bool fulfillsMe(TSubgoal goal) override;
};
class VisitHero : public CGoal<VisitHero>
class DLL_EXPORT VisitHero : public CGoal<VisitHero>
{
public:
VisitHero()
@ -485,7 +517,7 @@ public:
std::string completeMessage() const override;
};
class GetArtOfType : public CGoal<GetArtOfType>
class DLL_EXPORT GetArtOfType : public CGoal<GetArtOfType>
{
public:
GetArtOfType()
@ -505,7 +537,7 @@ public:
TSubgoal whatToDoToAchieve() override;
};
class VisitTile : public CGoal<VisitTile>
class DLL_EXPORT VisitTile : public CGoal<VisitTile>
//tile, in conjunction with hero elementar; assumes tile is reachable
{
public:
@ -526,7 +558,7 @@ public:
std::string completeMessage() const override;
};
class ClearWayTo : public CGoal<ClearWayTo>
class DLL_EXPORT ClearWayTo : public CGoal<ClearWayTo>
{
public:
ClearWayTo()
@ -552,9 +584,10 @@ public:
{
return g.goalType == goalType && g.tile == tile;
}
bool fulfillsMe(TSubgoal goal) override;
};
class DigAtTile : public CGoal<DigAtTile>
class DLL_EXPORT DigAtTile : public CGoal<DigAtTile>
//elementar with hero on tile
{
public:
@ -579,7 +612,7 @@ public:
}
};
class CIssueCommand : public CGoal<CIssueCommand>
class DLL_EXPORT CIssueCommand : public CGoal<CIssueCommand>
{
std::function<bool()> command;

312
AI/VCAI/ResourceManager.cpp Normal file
View File

@ -0,0 +1,312 @@
/*
* 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 "../../CCallback.h"
#include "../../lib/mapObjects/MapObjects.h"
#define GOLD_RESERVE (10000); //at least we'll be able to reach capitol
ResourceObjective::ResourceObjective(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::setCB(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(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;
return Goals::sptr(Goals::CollectRes(resourceType, amountToCollect));
}
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)
{
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;
if (!accumulatedResources.canBeAfforded(allResources)) //can't afford
return collectResourcesForOurGoal(ro);
else //can afford all goals up to this point
{
if (it->goal == goal)
return goal; //can afford immediately
}
}
return collectResourcesForOurGoal(ro); //fallback, ever needed?
}
bool ResourceManager::notifyGoalCompleted(Goals::TSubgoal goal)
{
if (goal->invalid())
logAi->warn("Attempt to complete Invalid goal");
bool removedGoal = false;
while (true)
{ //unfortunatelly we can't use remove_if on heap
auto it = boost::find_if(queue, [goal](const ResourceObjective & ro) -> bool
{
return ro.goal == goal || ro.goal->fulfillsMe (goal);
});
if (it != queue.end()) //removed at least one
{
queue.erase(queue.s_handle_from_iterator(it));
logAi->debug("Removed goal %s from ResourceManager.", it->goal->name());
removedGoal = true;
}
else //found nothing more to remove
return removedGoal;
}
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;
}
bool ResourceManager::tryPush(ResourceObjective & o)
{
auto goal = o.goal;
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();
}
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();
auto towns = cb->getTownsInfo();
if (towns.size()) //we don't save for Capitol if there are no towns
{
if (std::none_of(towns.begin(), towns.end(), [](const CGTownInstance * x) -> bool
{
return x->builtBuildings.find(BuildingID::CAPITOL) != x->builtBuildings.end();
}))
{
myRes[Res::GOLD] -= GOLD_RESERVE;
//what if capitol is blocked from building in all possessed towns (set in map editor)?
}
}
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];
}

109
AI/VCAI/ResourceManager.h Normal file
View File

@ -0,0 +1,109 @@
/*
* 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 "Goals.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(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 IResourceManager //: public: IAbstractManager
{
public:
virtual ~IResourceManager() = default;
virtual void setCB(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 hasTasksLeft() const = 0;
private:
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); //can we afford this goal or need to CollectRes?
bool hasTasksLeft() const override;
protected: //not-const actions only for AI
virtual void reserveResoures(TResources &res, Goals::TSubgoal goal = Goals::TSubgoal());
virtual bool notifyGoalCompleted(Goals::TSubgoal goal);
virtual bool updateGoal(Goals::TSubgoal goal); //new goal must have same properties but different priority
virtual bool tryPush(ResourceObjective &o);
//inner processing
virtual TResources estimateIncome() const;
virtual Goals::TSubgoal collectResourcesForOurGoal(ResourceObjective &o) const;
void setCB(CPlayerSpecificInfoCallback * CB) override;
void setAI(VCAI * AI) override;
private:
TResources saving;
boost::heap::binomial_heap<ResourceObjective> queue;
//TODO: register?
template<typename Handler> void serializeInternal(Handler & h, const int version)
{
h & saving;
h & queue;
}
};

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "VCAI.h"
#include "Fuzzy.h"
#include "ResourceManager.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/mapObjects/MapObjects.h"
@ -22,16 +23,18 @@
#include "../../lib/serializer/BinarySerializer.h"
#include "../../lib/serializer/BinaryDeserializer.h"
#include "AIhelper.h"
extern FuzzyHelper * fh;
class CGVisitableOPW;
const double SAFE_ATTACK_CONSTANT = 1.5;
const int GOLD_RESERVE = 10000; //when buying creatures we want to keep at least this much gold (10000 so at least we'll be able to reach capitol)
//one thread may be turn of AI and another will be handling a side effect for AI2
boost::thread_specific_ptr<CCallback> cb;
boost::thread_specific_ptr<VCAI> ai;
extern boost::thread_specific_ptr<AIhelper> ah;
//std::map<int, std::map<int, int> > HeroView::infosCount;
@ -45,9 +48,15 @@ struct SetGlobalState
ai.reset(AI);
cb.reset(AI->myCb.get());
if (!ah.get())
ah.reset(new AIhelper());
ah->setAI(AI); //does this make any sense?
ah->setCB(cb.get());
}
~SetGlobalState()
{
//TODO: how to handle rm? shouldn't be called after ai is destroyed, hopefully
//TODO: to ensure that, make rm unique_ptr
ai.release();
cb.release();
}
@ -537,6 +546,8 @@ void VCAI::objectPropertyChanged(const SetObjectProperty * sop)
void VCAI::buildChanged(const CGTownInstance * town, BuildingID buildingID, int what)
{
LOG_TRACE_PARAMS(logAi, "what '%i'", what);
if (town->getOwner() == playerID && what == 1) //built
completeGoal(sptr(Goals::BuildThis(buildingID, town)));
NET_EVENT_HANDLER;
}
@ -564,7 +575,10 @@ void VCAI::init(std::shared_ptr<CCallback> CB)
LOG_TRACE(logAi);
myCb = CB;
cbc = CB;
NET_EVENT_HANDLER;
ah.reset(new AIhelper());
NET_EVENT_HANDLER; //sets ah->rm->cb
playerID = *myCb->getMyColor();
myCb->waitTillRealize = true;
myCb->unlockGsWhenWaiting = true;
@ -772,8 +786,6 @@ void VCAI::makeTurn()
It is not supposed to work this way in final version of VCAI. It consists of few actions/loops done in particular order, hard parts are explained below with focus on explaining hero management logic*/
void VCAI::makeTurnInternal()
{
saving = 0;
//it looks messy here, but it's better to have armed heroes before attempting realizing goals
for(const CGTownInstance * t : cb->getTownsInfo())
moveCreaturesToHero(t);
@ -814,11 +826,14 @@ void VCAI::makeTurnInternal()
/*below line performs goal decomposition, result of the function is ONE goal for ONE hero to realize.*/
striveToGoal(sptr(Goals::Win()));
//TODO: add ResourceManager goals to the pool and process them all at once
if (ah->hasTasksLeft())
striveToGoal(ah->whatToDo());
/*Explanation of below loop: At the time of writing this - goals get decomposited either to GatherArmy or Visit Tile.
Visit tile that is about visiting object gets processed at beginning of MakeTurnInternal without re-evaluation.
Rest of goals that got started via striveToGoal(sptr(Goals::Win())); in previous turns and not finished get continued here.
Also they are subject for re-evaluation to see if there is better goal to start (still talking only about heroes that got goals started by via striveToGoal(sptr(Goals::Win())); in previous turns.*/
//finally, continue our abstract long-term goals
int oldMovement = 0;
int newMovement = 0;
@ -863,6 +878,7 @@ void VCAI::makeTurnInternal()
striveToQuest(quest);
}
//TODO: striveToGoal
striveToGoal(sptr(Goals::Build())); //TODO: smarter building management
/*Below function is also responsible for hero movement via internal wander function. By design it is separate logic for heroes that have nothing to do.
@ -914,7 +930,7 @@ void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
if(h->visitedTown) //we are inside, not just attacking
{
townVisitsThisWeek[h].insert(h->visitedTown);
if(!h->hasSpellbook() && cb->getResourceAmount(Res::GOLD) >= GameConstants::SPELLBOOK_GOLD_COST + saving[Res::GOLD])
if(!h->hasSpellbook() && ah->freeGold() >= GameConstants::SPELLBOOK_GOLD_COST)
{
if(h->visitedTown->hasBuilt(BuildingID::MAGES_GUILD_1))
cb->buyArtifact(h.get(), ArtifactID::SPELLBOOK);
@ -1150,6 +1166,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruiter)
{
//now used only for visited dwellings / towns, not BuyArmy goal
for(int i = 0; i < d->creatures.size(); i++)
{
if(!d->creatures[i].second.size())
@ -1157,17 +1174,14 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
int count = d->creatures[i].first;
CreatureID creID = d->creatures[i].second.back();
// const CCreature *c = VLC->creh->creatures[creID];
// if(containsSavedRes(c->cost))
// continue;
vstd::amin(count, freeResources() / VLC->creh->creatures[creID]->cost);
vstd::amin(count, ah->freeResources() / VLC->creh->creatures[creID]->cost);
if(count > 0)
cb->recruitCreatures(d, recruiter, creID, count, i);
}
}
bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays) const
bool VCAI::tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays)
{
if(maxDays == 0)
{
@ -1199,52 +1213,52 @@ bool VCAI::tryBuildStructure(const CGTownInstance * t, BuildingID building, unsi
if(maxDays && toBuild.size() > maxDays)
return false;
TResources currentRes = cb->getResourceAmount();
//TODO: calculate if we have enough resources to build it in maxDays
//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)
if (canBuild == EBuildingState::ALLOWED)
{
if(!containsSavedRes(b->resources))
{
logAi->debug("Player %d will build %s in town of %s at %s", playerID, b->Name(), t->name, t->pos.toString());
cb->buildBuilding(t, buildID);
return true;
}
continue;
buildStructure(t, buildID);
return true;
}
else if(canBuild == EBuildingState::NO_RESOURCES)
{
//We can't do anything about it - no requests from this function
continue;
}
else if(canBuild == EBuildingState::PREREQUIRES)
else if (canBuild == EBuildingState::PREREQUIRES)
{
// can happen when dependencies have their own missing dependencies
if(tryBuildStructure(t, buildID, maxDays - 1))
if (tryBuildThisStructure(t, buildID, maxDays - 1))
return true;
}
else if(canBuild == EBuildingState::MISSING_BASE)
else if (canBuild == EBuildingState::MISSING_BASE)
{
if(tryBuildStructure(t, b->upgrade, maxDays - 1))
return true;
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);
potentialBuildings.push_back(pb); //these are checked again in try
return false;
}
else
return false;
}
return false;
}
bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays) const
bool VCAI::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
for(const auto & building : buildList)
{
if(t->hasBuilt(building))
continue;
if(tryBuildStructure(t, building, maxDays))
return true;
return tryBuildThisStructure(t, building, maxDays);
}
return false; //Can't build anything
}
@ -1261,17 +1275,24 @@ BuildingID VCAI::canBuildAnyStructure(const CGTownInstance * t, std::vector<Buil
return BuildingID::NONE; //Can't build anything
}
bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays) const
bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
{
for(const auto & building : buildList)
{
if(t->hasBuilt(building))
continue;
return tryBuildStructure(t, building, maxDays);
return tryBuildThisStructure(t, building, maxDays);
}
return false; //Nothing to build
}
void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
{
auto name = t->town->buildings.at(building)->Name();
logAi->debug("Player %d will build %s in town of %s at %s", playerID, name, t->name, t->pos.toString());
cb->buildBuilding(t, building); //just do this;
}
//Set of buildings for different goals. Does not include any prerequisites.
static const BuildingID essential[] = {BuildingID::TAVERN, BuildingID::TOWN_HALL};
static const BuildingID goldSource[] = {BuildingID::TOWN_HALL, BuildingID::CITY_HALL, BuildingID::CAPITOL};
@ -1287,7 +1308,7 @@ static const BuildingID _spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGE
static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
void VCAI::buildStructure(const CGTownInstance * t) const
bool VCAI::tryBuildStructure(const CGTownInstance * t)
{
//TODO make *real* town development system
//TODO: faction-specific development: use special buildings, build dwellings in better order, etc
@ -1299,41 +1320,40 @@ void VCAI::buildStructure(const CGTownInstance * t) const
TResources currentIncome = t->dailyIncome();
if(tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
return;
return true;
//the more gold the better and less problems later
if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
return;
return true;
//workaround for mantis #2696 - build fort and citadel - building castle will be handled without bug
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL)
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) &&
cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL)
{
if(cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
{
if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements, capitolRequirements + ARRAY_COUNT(capitolRequirements))))
return;
if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements,
capitolRequirements + ARRAY_COUNT(capitolRequirements))))
return true;
}
}
//save money for capitol or city hall if capitol unavailable, do not build other things (unless gold source buildings are disabled in map editor)
if(!vstd::contains(t->builtBuildings, BuildingID::CAPITOL) && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
return;
else if(!vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && cb->canBuildStructure(t, BuildingID::CAPITOL) == EBuildingState::HAVE_CAPITAL && cb->canBuildStructure(t, BuildingID::CITY_HALL) != EBuildingState::FORBIDDEN)
return;
else if(!vstd::contains(t->builtBuildings, BuildingID::TOWN_HALL) && cb->canBuildStructure(t, BuildingID::TOWN_HALL) != EBuildingState::FORBIDDEN)
return;
//TODO: save money for capitol or city hall if capitol unavailable
//do not build other things (unless gold source buildings are disabled in map editor)
if(cb->getDate(Date::DAY_OF_WEEK) > 6) // last 2 days of week - try to focus on growth
{
if(tryBuildNextStructure(t, std::vector<BuildingID>(unitGrowth, unitGrowth + ARRAY_COUNT(unitGrowth)), 2))
return;
return true;
}
// first in-game week or second half of any week: try build dwellings
if(cb->getDate(Date::DAY) < 7 || cb->getDate(Date::DAY_OF_WEEK) > 3)
{
if(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
return;
if(tryBuildAnyStructure(t, std::vector<BuildingID>(unitsSource,
unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK)))
return true;
}
//try to upgrade dwelling
@ -1341,16 +1361,16 @@ void VCAI::buildStructure(const CGTownInstance * t) const
{
if(t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
{
if(tryBuildStructure(t, unitsUpgrade[i]))
return;
if(tryBuildThisStructure(t, unitsUpgrade[i]))
return true;
}
}
//remaining tasks
if(tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
return;
return true;
if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
return;
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;
@ -1360,7 +1380,9 @@ void VCAI::buildStructure(const CGTownInstance * t) const
extraBuildings.push_back(buildingInfo.first);
}
if(tryBuildAnyStructure(t, extraBuildings))
return;
return true;
return false;
}
bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm)
@ -1419,7 +1441,7 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
t = findTownWithTavern();
if(!t)
return false;
if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST)
if(cb->getResourceAmount(Res::GOLD) < GameConstants::HERO_GOLD_COST) //TODO: use ResourceManager
return false;
if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
return false;
@ -1602,12 +1624,13 @@ void VCAI::evaluateGoal(HeroPtr h)
void VCAI::completeGoal(Goals::TSubgoal goal)
{
logAi->trace("Completing goal: %s", goal->name());
ah->notifyGoalCompleted(goal);
if(const CGHeroInstance * h = goal->hero.get(true))
{
auto it = lockedHeroes.find(h);
if(it != lockedHeroes.end())
{
if(it->second == goal)
if(it->second == goal || it->second->fulfillsMe(goal)) //FIXME this is overspecified, fulfillsMe shoudl be complete
{
logAi->debug(goal->completeMessage());
lockedHeroes.erase(it); //goal fulfilled, free hero
@ -1618,7 +1641,7 @@ void VCAI::completeGoal(Goals::TSubgoal goal)
{
vstd::erase_if(lockedHeroes, [goal](std::pair<HeroPtr, Goals::TSubgoal> p)
{
if(*(p.second) == *goal || p.second->fulfillsMe(goal)) //we could have fulfilled goals of other heroes by chance
if(p.second == goal || p.second->fulfillsMe(goal)) //we could have fulfilled goals of other heroes by chance
{
logAi->debug(p.second->completeMessage());
return true;
@ -2090,30 +2113,19 @@ void VCAI::tryRealize(Goals::VisitHero & g)
void VCAI::tryRealize(Goals::BuildThis & g)
{
const CGTownInstance * t = g.town;
auto b = BuildingID(g.bid);
auto t = g.town;
if(!t && g.hero)
t = g.hero->visitedTown;
if(!t)
if (t)
{
for(const CGTownInstance * t : cb->getTownsInfo())
if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED)
{
switch(cb->canBuildStructure(t, BuildingID(g.bid)))
{
case EBuildingState::ALLOWED:
cb->buildBuilding(t, BuildingID(g.bid));
return;
default:
break;
}
logAi->debug("Player %d will build %s in town of %s at %s",
playerID, t->town->buildings.at(b)->Name(), t->name, t->pos.toString());
cb->buildBuilding(t, b);
throw goalFulfilledException(sptr(g));
}
}
else if(cb->canBuildStructure(t, BuildingID(g.bid)) == EBuildingState::ALLOWED)
{
cb->buildBuilding(t, BuildingID(g.bid));
return;
}
throw cannotFulfillGoalException("Cannot build a given structure!");
}
@ -2132,10 +2144,10 @@ void VCAI::tryRealize(Goals::DigAtTile & g)
}
}
void VCAI::tryRealize(Goals::CollectRes & g)
void VCAI::tryRealize(Goals::CollectRes & g) //trade
{
if(cb->getResourceAmount(static_cast<Res::ERes>(g.resID)) >= g.value)
throw cannotFulfillGoalException("Goal is already fulfilled!");
if(ah->freeResources()[g.resID] >= g.value) //goal is already fulfilled. Why we need this chek, anyway?
throw goalFulfilledException(sptr(g));
if(const CGObjectInstance * obj = cb->getObj(ObjectInstanceID(g.objid), false))
{
@ -2143,15 +2155,16 @@ void VCAI::tryRealize(Goals::CollectRes & g)
{
for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1))
{
if(i == g.resID)
if(i == g.resID) //sell any other resource
continue;
//TODO: check all our reserved resources and avoid them
int toGive, toGet;
m->getOffer(i, g.resID, toGive, toGet, EMarketMode::RESOURCE_RESOURCE);
toGive = toGive * (cb->getResourceAmount(i) / toGive);
//TODO trade only as much as needed
cb->trade(obj, EMarketMode::RESOURCE_RESOURCE, i, g.resID, toGive);
if(cb->getResourceAmount(static_cast<Res::ERes>(g.resID)) >= g.value)
return;
if (ah->freeResources()[g.resID] >= g.value)
throw goalFulfilledException(sptr(g));
}
throw cannotFulfillGoalException("I cannot get needed resources by trade!");
@ -2163,28 +2176,87 @@ void VCAI::tryRealize(Goals::CollectRes & g)
}
else
{
saving[g.resID] = 1;
throw cannotFulfillGoalException("No object that could be used to raise resources!");
}
}
void VCAI::tryRealize(Goals::Build & g)
{
bool didWeBuildSomething = false;
for(const CGTownInstance * t : cb->getTownsInfo())
{
logAi->debug("Looking into %s", t->name);
buildStructure(t);
if(!ai->primaryHero() ||
(t->getArmyStrength() > ai->primaryHero()->getArmyStrength() * 2 && !isAccessibleForHero(t->visitablePos(), ai->primaryHero())))
potentialBuildings.clear(); //start fresh with every town
if (tryBuildStructure(t))
didWeBuildSomething = true;
else if (potentialBuildings.size())
{
recruitHero(t);
buildArmyIn(t);
auto pb = potentialBuildings.front(); //gather resources for any we can't afford
auto goal = ah->whatToDo(pb.price, sptr(Goals::BuildThis(pb.bid, t)));
if (goal->goalType == Goals::BUILD_STRUCTURE)
{
logAi->error("We were supposed to NOT afford any building");
buildStructure(t, pb.bid); //do it right now
didWeBuildSomething = true;
}
else
{
//TODO: right now we do that for every town in order. Consider comparison of all potential goals.
striveToGoal(goal); //gather resources, or something else?
}
}
}
throw cannotFulfillGoalException("BUILD has been realized as much as possible.");
if (!didWeBuildSomething)
throw cannotFulfillGoalException("BUILD has been realized as much as possible."); //who catches it and what for?
}
void VCAI::tryRealize(Goals::BuyArmy & g)
{
auto t = g.town;
ui64 valueBought = 0;
//buy the stacks with largest AI value
while (valueBought < g.value)
{
auto res = ah->allResources();
std::vector<creInfo> creaturesInDwellings;
for (int i = 0; i < t->creatures.size(); i++)
{
auto ci = infoFromDC(t->creatures[i]);
ci.level = i; //this is important for Dungeon Summoning Portal
creaturesInDwellings.push_back(ci);
}
vstd::erase_if(creaturesInDwellings, [](const creInfo & ci) -> bool
{
return !ci.count || ci.creID == -1;
});
if (creaturesInDwellings.empty())
throw cannotFulfillGoalException("Can't buy any more creatures!");
creInfo ci =
*boost::max_element(creaturesInDwellings, [&res](const creInfo & lhs, const creInfo & rhs)
{
//max value of creatures we can buy with our res
int value1 = lhs.cre->AIValue * (std::min(lhs.count, res / lhs.cre->cost)),
value2 = rhs.cre->AIValue * (std::min(rhs.count, res / rhs.cre->cost));
return value1 < value2;
});
vstd::amin(ci.count, res / ci.cre->cost); //max count we can afford
if (ci.count > 0)
{
cb->recruitCreatures(t, t->getUpperArmy(), ci.creID, ci.count, ci.level);
valueBought += ci.count * ci.cre->AIValue;
}
else
throw cannotFulfillGoalException("Can't buy any more creatures!");
}
throw goalFulfilledException(sptr(g)); //we bought as many creatures as we wanted
}
void VCAI::tryRealize(Goals::Invalid & g)
{
throw cannotFulfillGoalException("I don't know how to fulfill this!");
@ -2303,7 +2375,7 @@ Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool on
boost::this_thread::interruption_point();
goal = goal->whatToDoToAchieve();
--maxGoals;
if(*goal == *ultimateGoal) //compare objects by value
if(goal == ultimateGoal) //compare objects by value
throw cannotFulfillGoalException("Goal dependency loop detected!");
}
catch(goalFulfilledException & e)
@ -2319,7 +2391,6 @@ Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool on
return sptr(Goals::Invalid());
}
}
try
{
boost::this_thread::interruption_point();
@ -2328,10 +2399,10 @@ Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool on
{
if(ultimateGoal->hero) // we seemingly don't know what to do with hero, free him
vstd::erase_if_present(lockedHeroes, ultimateGoal->hero);
std::runtime_error e("Too many subgoals, don't know what to do");
throw (e);
throw (std::runtime_error("Too many subgoals, don't know what to do"));
}
else //we can proceed
else //we found elementar goal and can proceed
{
if(goal->hero) //lock this hero to fulfill ultimate goal
{
@ -2345,7 +2416,7 @@ Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool on
logAi->debug("Choosing abstract goal %s", goal->name());
break;
}
else
else //try realize
{
logAi->debug("Trying to realize %s (value %2.3f)", goal->name(), goal->priority);
goal->accept(this);
@ -2360,7 +2431,9 @@ Goals::TSubgoal VCAI::striveToGoalInternal(Goals::TSubgoal ultimateGoal, bool on
}
catch(goalFulfilledException & e)
{
//the goal was completed successfully
//the sub-goal was completed successfully
completeGoal(e.goal);
//local goal was also completed... TODO: or not?
completeGoal(goal);
//completed goal was main goal //TODO: find better condition
if(ultimateGoal->fulfillsMe(goal) || maxGoals > searchDepth2)
@ -2515,11 +2588,6 @@ void VCAI::striveToQuest(const QuestInfo & q)
void VCAI::performTypicalActions()
{
//TODO: build army only on request
for(auto t : cb->getTownsInfo())
{
buildArmyIn(t);
}
for(auto h : getUnblockedHeroes())
{
if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
@ -2688,49 +2756,6 @@ int3 VCAI::explorationDesperate(HeroPtr h)
return bestTile;
}
TResources VCAI::estimateIncome() const
{
TResources ret;
for(const CGTownInstance * t : cb->getTownsInfo())
{
ret += t->dailyIncome();
}
for(const CGObjectInstance * obj : 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;
}
bool VCAI::containsSavedRes(const TResources & cost) const
{
for(int i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
{
if(saving[i] && cost[i])
return true;
}
return false;
}
void VCAI::checkHeroArmy(HeroPtr h)
{
auto it = lockedHeroes.find(h);
@ -2755,6 +2780,7 @@ void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
hero = heroes[1];
}
cb->recruitHero(t, hero);
throw goalFulfilledException(sptr(Goals::RecruitHero().settown(t)));
}
else if(throwing)
{
@ -2849,20 +2875,6 @@ void VCAI::validateObject(ObjectIdRef obj)
}
}
TResources VCAI::freeResources() const
{
TResources myRes = cb->getResourceAmount();
auto iterator = cb->getTownsInfo();
if(std::none_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x) -> bool
{
return x->builtBuildings.find(BuildingID::CAPITOL) != x->builtBuildings.end();
})
/*|| std::all_of(iterator.begin(), iterator.end(), [](const CGTownInstance * x) -> bool { return x->forbiddenBuildings.find(BuildingID::CAPITOL) != x->forbiddenBuildings.end(); })*/ )
myRes[Res::GOLD] -= GOLD_RESERVE; //what if capitol is blocked from building in all possessed towns (set in map editor)? What about reserve for city hall or something similar in that case?
vstd::amax(myRes[Res::GOLD], 0);
return myRes;
}
std::shared_ptr<SectorMap> VCAI::getCachedSectorMap(HeroPtr h)
{
auto it = cachedSectorMaps.find(h);
@ -3271,8 +3283,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
case Obj::SCHOOL_OF_MAGIC:
case Obj::SCHOOL_OF_WAR:
{
TResources myRes = ai->myCb->getResourceAmount();
if(myRes[Res::GOLD] - GOLD_RESERVE < 1000)
if (ah->freeGold() < 1000)
return false;
break;
}
@ -3282,8 +3293,8 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
break;
case Obj::TREE_OF_KNOWLEDGE:
{
TResources myRes = ai->myCb->getResourceAmount();
if(myRes[Res::GOLD] - GOLD_RESERVE < 2000 || myRes[Res::GEMS] < 10)
TResources myRes = ah->freeResources();
if(myRes[Res::GOLD] < 2000 || myRes[Res::GEMS] < 10)
return false;
break;
}
@ -3297,7 +3308,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
//TODO: only on request
if(ai->myCb->getHeroesInfo().size() >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
return false;
else if(ai->myCb->getResourceAmount()[Res::GOLD] - GOLD_RESERVE < GameConstants::HERO_GOLD_COST)
else if(ah->freeGold() < GameConstants::HERO_GOLD_COST)
return false;
break;
}

View File

@ -126,21 +126,33 @@ struct SectorMap
int3 findFirstVisitableTile(HeroPtr h, crint3 dst);
};
class VCAI : public CAdventureAI
class DLL_EXPORT VCAI : public CAdventureAI
{
public:
//internal methods for town development
//TODO: refactor to separate class BuildManager
//try build an unbuilt structure in maxDays at most (0 = indefinite)
/*bool canBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays=7);*/
bool tryBuildStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays = 7) const;
//try build anything in given town, and execute resulting Goal if any
bool tryBuildStructure(const CGTownInstance * t);
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
BuildingID canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7) const;
bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7) const;
//try build first unbuilt structure
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7) const;
bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
void buildStructure(const CGTownInstance * t, BuildingID building); //actually execute build operation
struct PotentialBuilding
{
BuildingID bid;
TResources price;
//days to build?
};
std::vector<PotentialBuilding> potentialBuildings; //what we can build in current town
friend class FuzzyHelper;
friend class ResourceManager;
std::map<TeleportChannelID, std::shared_ptr<TeleportChannel>> knownTeleportChannels;
std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
@ -161,8 +173,6 @@ public:
std::map<HeroPtr, std::shared_ptr<SectorMap>> cachedSectorMaps; //TODO: serialize? not necessary
TResources saving;
AIStatus status;
std::string battlename;
@ -182,6 +192,7 @@ public:
void tryRealize(Goals::DigAtTile & g);
void tryRealize(Goals::CollectRes & g);
void tryRealize(Goals::Build & g);
void tryRealize(Goals::BuyArmy & g);
void tryRealize(Goals::Invalid & g);
void tryRealize(Goals::AbstractGoal & g);
@ -189,71 +200,70 @@ public:
int3 explorationNewPoint(HeroPtr h);
int3 explorationDesperate(HeroPtr h);
bool isTileNotReserved(const CGHeroInstance * h, int3 t); //the tile is not occupied by allied hero and the object is not reserved
void recruitHero();
virtual std::string getBattleAIName() const override;
std::string getBattleAIName() const override;
virtual void init(std::shared_ptr<CCallback> CB) override;
virtual void yourTurn() override;
void init(std::shared_ptr<CCallback> CB) override;
void yourTurn() override;
virtual 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
virtual void commanderGotLevel(const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override; //TODO
virtual 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.
virtual 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
virtual void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) 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;
virtual void saveGame(BinarySerializer & h, const int version) override; //saving
virtual void loadGame(BinaryDeserializer & h, const int version) override; //loading
virtual void finish() override;
void saveGame(BinarySerializer & h, const int version) override; //saving
void loadGame(BinaryDeserializer & h, const int version) override; //loading
void finish() override;
virtual void availableCreaturesChanged(const CGDwelling * town) override;
virtual void heroMoved(const TryMoveHero & details) override;
virtual void heroInGarrisonChange(const CGTownInstance * town) override;
virtual void centerView(int3 pos, int focusTime) override;
virtual void tileHidden(const std::unordered_set<int3, ShashInt3> & pos) override;
virtual void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
virtual void artifactAssembled(const ArtifactLocation & al) override;
virtual void showTavernWindow(const CGObjectInstance * townOrTavern) override;
virtual void showThievesGuildWindow(const CGObjectInstance * obj) override;
virtual void playerBlocked(int reason, bool start) override;
virtual void showPuzzleMap() override;
virtual void showShipyardDialog(const IShipyard * obj) override;
virtual void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
virtual void artifactPut(const ArtifactLocation & al) override;
virtual void artifactRemoved(const ArtifactLocation & al) override;
virtual void artifactDisassembled(const ArtifactLocation & al) override;
virtual void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
virtual void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
virtual void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
virtual void tileRevealed(const std::unordered_set<int3, ShashInt3> & pos) override;
virtual void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
virtual void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
virtual void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
virtual void heroMovePointsChanged(const CGHeroInstance * hero) override;
virtual void garrisonsChanged(ObjectInstanceID id1, ObjectInstanceID id2) override;
virtual void newObject(const CGObjectInstance * obj) override;
virtual void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
virtual void playerBonusChanged(const Bonus & bonus, bool gain) override;
virtual void heroCreated(const CGHeroInstance *) override;
virtual void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
virtual void showInfoDialog(const std::string & text, const std::vector<Component> & components, int soundID) override;
virtual void requestRealized(PackageApplied * pa) override;
virtual void receivedResource() override;
virtual void objectRemoved(const CGObjectInstance * obj) override;
virtual void showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor) override;
virtual void heroManaPointsChanged(const CGHeroInstance * hero) override;
virtual void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
virtual void battleResultsApplied() override;
virtual void objectPropertyChanged(const SetObjectProperty * sop) override;
virtual void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
virtual void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
virtual void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) 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;
virtual void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side) override;
virtual void battleEnd(const BattleResult * br) override;
void makeTurn();
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 makeTurnInternal();
void performTypicalActions();
@ -269,7 +279,6 @@ public:
void recruitHero(const CGTownInstance * t, bool throwing = false);
bool isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm);
void buildStructure(const CGTownInstance * t) 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?
@ -299,7 +308,7 @@ public:
void validateVisitableObjs();
void retrieveVisitableObjs(std::vector<const CGObjectInstance *> & out, bool includeOwned = false) const;
void retrieveVisitableObjs();
std::vector<const CGObjectInstance *> getFlaggedObjects() const;
virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
const CGObjectInstance * lookForArt(int aid) const;
bool isAccessible(const int3 & pos);
@ -317,9 +326,6 @@ public:
bool canAct(HeroPtr h) const;
std::vector<HeroPtr> getUnblockedHeroes() const;
HeroPtr primaryHero() const;
TResources freeResources() const; //owned resources minus gold reserve
TResources estimateIncome() const;
bool containsSavedRes(const TResources & cost) const;
void checkHeroArmy(HeroPtr h);
void requestSent(const CPackForServer * pack, int requestID) override;
@ -406,7 +412,11 @@ public:
h & visitableObjs;
h & alreadyVisited;
h & reservedObjs;
h & saving;
if (version < 788 && !h.saving)
{
TResources saving;
h & saving; //mind the ambiguity
}
h & status;
h & battlename;
h & heroesUnableToExplore;

View File

@ -50,102 +50,102 @@ protected:
public:
//various
int getDate(Date::EDateType mode=Date::DAY)const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
const StartInfo * getStartInfo(bool beforeRandomization = false)const;
bool isAllowed(int type, int id); //type: 0 - spell; 1- artifact; 2 - secondary skill
virtual int getDate(Date::EDateType mode=Date::DAY)const; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
virtual const StartInfo * getStartInfo(bool beforeRandomization = false)const;
virtual bool isAllowed(int type, int id); //type: 0 - spell; 1- artifact; 2 - secondary skill
//player
const PlayerState * getPlayer(PlayerColor color, bool verbose = true) const;
int getResource(PlayerColor Player, Res::ERes which) const;
bool isVisible(int3 pos) const;
PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2) const;
void getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj); //get thieves' guild info obtainable while visiting given object
EPlayerStatus::EStatus getPlayerStatus(PlayerColor player, bool verbose = true) const; //-1 if no such player
PlayerColor getCurrentPlayer() const; //player that currently makes move // TODO synchronous turns
virtual const PlayerState * getPlayer(PlayerColor color, bool verbose = true) const;
virtual int getResource(PlayerColor Player, Res::ERes which) const;
virtual bool isVisible(int3 pos) const;
virtual PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2) const;
virtual void getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj); //get thieves' guild info obtainable while visiting given object
virtual EPlayerStatus::EStatus getPlayerStatus(PlayerColor player, bool verbose = true) const; //-1 if no such player
virtual PlayerColor getCurrentPlayer() const; //player that currently makes move // TODO synchronous turns
virtual PlayerColor getLocalPlayer() const; //player that is currently owning given client (if not a client, then returns current player)
const PlayerSettings * getPlayerSettings(PlayerColor color) const;
virtual const PlayerSettings * getPlayerSettings(PlayerColor color) const;
//armed object
void getUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out)const;
virtual void getUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out)const;
//hero
const CGHeroInstance* getHero(ObjectInstanceID objid) const;
const CGHeroInstance* getHeroWithSubid(int subid) const;
int getHeroCount(PlayerColor player, bool includeGarrisoned) const;
bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const;
int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction
int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
const CGObjectInstance * getArmyInstance(ObjectInstanceID oid) const;
virtual const CGHeroInstance* getHero(ObjectInstanceID objid) const;
virtual const CGHeroInstance* getHeroWithSubid(int subid) const;
virtual int getHeroCount(PlayerColor player, bool includeGarrisoned) const;
virtual bool getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject = nullptr) const;
virtual int getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //when called during battle, takes into account creatures' spell cost reduction
virtual int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
virtual const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
virtual const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
//virtual const CGObjectInstance * getArmyInstance(ObjectInstanceID oid) const;
//objects
const CGObjectInstance* getObj(ObjectInstanceID objid, bool verbose = true) const;
std::vector <const CGObjectInstance * > getBlockingObjs(int3 pos)const;
std::vector <const CGObjectInstance * > getVisitableObjs(int3 pos, bool verbose = true)const;
std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
const CGObjectInstance * getTopObj (int3 pos) const;
PlayerColor getOwner(ObjectInstanceID heroID) const;
const CGObjectInstance *getObjByQuestIdentifier(int identifier) const; //nullptr if object has been removed (eg. killed)
virtual const CGObjectInstance* getObj(ObjectInstanceID objid, bool verbose = true) const;
virtual std::vector <const CGObjectInstance * > getBlockingObjs(int3 pos)const;
virtual std::vector <const CGObjectInstance * > getVisitableObjs(int3 pos, bool verbose = true)const;
virtual std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
virtual const CGObjectInstance * getTopObj (int3 pos) const;
virtual PlayerColor getOwner(ObjectInstanceID heroID) const;
virtual const CGObjectInstance *getObjByQuestIdentifier(int identifier) const; //nullptr if object has been removed (eg. killed)
//map
int3 guardingCreaturePosition (int3 pos) const;
std::vector<const CGObjectInstance*> getGuardingCreatures (int3 pos) const;
const CMapHeader * getMapHeader()const;
int3 getMapSize() const; //returns size of map - z is 1 for one - level map and 2 for two level map
const TerrainTile * getTile(int3 tile, bool verbose = true) const;
std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
bool isInTheMap(const int3 &pos) const;
void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
virtual int3 guardingCreaturePosition (int3 pos) const;
virtual std::vector<const CGObjectInstance*> getGuardingCreatures (int3 pos) const;
virtual const CMapHeader * getMapHeader()const;
virtual int3 getMapSize() const; //returns size of map - z is 1 for one - level map and 2 for two level map
virtual const TerrainTile * getTile(int3 tile, bool verbose = true) const;
virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
virtual bool isInTheMap(const int3 &pos) const;
virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
//town
const CGTownInstance* getTown(ObjectInstanceID objid) const;
int howManyTowns(PlayerColor Player) const;
const CGTownInstance * getTownInfo(int val, bool mode)const; //mode = 0 -> val = player town serial; mode = 1 -> val = object id (serial)
std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
std::string getTavernRumor(const CGObjectInstance * townOrTavern) const;
EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;
virtual int howManyTowns(PlayerColor Player) const;
//virtual const CGTownInstance * getTownInfo(int val, bool mode)const; //mode = 0 -> val = player town serial; mode = 1 -> val = object id (serial)
virtual std::vector<const CGHeroInstance *> getAvailableHeroes(const CGObjectInstance * townOrTavern) const; //heroes that can be recruited
virtual std::string getTavernRumor(const CGObjectInstance * townOrTavern) const;
virtual EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID);//// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
virtual bool getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject = nullptr) const;
const CTown *getNativeTown(PlayerColor color) const;
virtual const CTown *getNativeTown(PlayerColor color) const;
//from gs
const TeamState *getTeam(TeamID teamID) const;
const TeamState *getPlayerTeam(PlayerColor color) const;
EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID) const;// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
virtual const TeamState *getTeam(TeamID teamID) const;
virtual const TeamState *getPlayerTeam(PlayerColor color) const;
//virtual EBuildingState::EBuildingState canBuildStructure(const CGTownInstance *t, BuildingID ID) const;// 0 - no more than one capitol, 1 - lack of water, 2 - forbidden, 3 - Add another level to Mage Guild, 4 - already built, 5 - cannot build, 6 - cannot afford, 7 - build, 8 - lack of requirements
//teleport
std::vector<ObjectInstanceID> getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const;
std::vector<ObjectInstanceID> getTeleportChannelEntraces(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const;
std::vector<ObjectInstanceID> getTeleportChannelExits(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const;
ETeleportChannelType getTeleportChannelType(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportChannelImpassable(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportChannelBidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportChannelUnidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
bool isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const;
virtual std::vector<ObjectInstanceID> getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const;
virtual std::vector<ObjectInstanceID> getTeleportChannelEntraces(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const;
virtual std::vector<ObjectInstanceID> getTeleportChannelExits(TeleportChannelID id, PlayerColor Player = PlayerColor::UNFLAGGABLE) const;
virtual ETeleportChannelType getTeleportChannelType(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
virtual bool isTeleportChannelImpassable(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
virtual bool isTeleportChannelBidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
virtual bool isTeleportChannelUnidirectional(TeleportChannelID id, PlayerColor player = PlayerColor::UNFLAGGABLE) const;
virtual bool isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const;
};
class DLL_LINKAGE CPlayerSpecificInfoCallback : public CGameInfoCallback
{
public:
int howManyTowns() const;
int howManyHeroes(bool includeGarrisoned = true) const;
int3 getGrailPos(double *outKnownRatio);
boost::optional<PlayerColor> getMyColor() const;
virtual int howManyTowns() const;
virtual int howManyHeroes(bool includeGarrisoned = true) const;
virtual int3 getGrailPos(double *outKnownRatio);
virtual boost::optional<PlayerColor> getMyColor() const;
std::vector <const CGTownInstance *> getTownsInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
int getHeroSerial(const CGHeroInstance * hero, bool includeGarrisoned=true) const;
const CGTownInstance* getTownBySerial(int serialId) const; // serial id is [0, number of towns)
const CGHeroInstance* getHeroBySerial(int serialId, bool includeGarrisoned=true) const; // serial id is [0, number of heroes)
std::vector <const CGHeroInstance *> getHeroesInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
std::vector <const CGDwelling *> getMyDwellings() const; //returns all dwellings that belong to player
std::vector <const CGObjectInstance * > getMyObjects() const; //returns all objects flagged by belonging player
std::vector <QuestInfo> getMyQuests() const;
virtual std::vector <const CGTownInstance *> getTownsInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
virtual int getHeroSerial(const CGHeroInstance * hero, bool includeGarrisoned=true) const;
virtual const CGTownInstance* getTownBySerial(int serialId) const; // serial id is [0, number of towns)
virtual const CGHeroInstance* getHeroBySerial(int serialId, bool includeGarrisoned=true) const; // serial id is [0, number of heroes)
virtual std::vector <const CGHeroInstance *> getHeroesInfo(bool onlyOur = true) const; //true -> only owned; false -> all visible
virtual std::vector <const CGDwelling *> getMyDwellings() const; //returns all dwellings that belong to player
virtual std::vector <const CGObjectInstance * > getMyObjects() const; //returns all objects flagged by belonging player
virtual std::vector <QuestInfo> getMyQuests() const;
int getResourceAmount(Res::ERes type) const;
TResources getResourceAmount() const;
const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map
const PlayerSettings * getPlayerSettings(PlayerColor color) const;
virtual int getResourceAmount(Res::ERes type) const;
virtual TResources getResourceAmount() const;
virtual const std::vector< std::vector< std::vector<ui8> > > & getVisibilityMap()const; //returns visibility map
//virtual const PlayerSettings * getPlayerSettings(PlayerColor color) const;
};
class DLL_LINKAGE IGameEventRealizer

View File

@ -28,6 +28,21 @@ Res::ResourceSet::ResourceSet(const JsonNode & node)
push_back(node[name].Float());
}
Res::ResourceSet::ResourceSet(TResource wood, TResource mercury, TResource ore, TResource sulfur, TResource crystal,
TResource gems, TResource gold, TResource mithril)
{
resize(GameConstants::RESOURCE_QUANTITY);
auto d = data();
d[Res::WOOD] = wood;
d[Res::MERCURY] = mercury;
d[Res::ORE] = ore;
d[Res::SULFUR] = sulfur;
d[Res::CRYSTAL] = crystal;
d[Res::GEMS] = gems;
d[Res::GOLD] = gold;
d[Res::MITHRIL] = mithril;
}
void Res::ResourceSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
{
if(!handler.saving)

View File

@ -25,7 +25,8 @@ namespace Res
{
WOOD = 0, MERCURY, ORE, SULFUR, CRYSTAL, GEMS, GOLD, MITHRIL,
WOOD_AND_ORE = 127 // special case for town bonus resource
WOOD_AND_ORE = 127, // special case for town bonus resource
INVALID = -1
};
//class to be representing a vector of resource
@ -35,6 +36,8 @@ namespace Res
DLL_LINKAGE ResourceSet();
// read resources set from json. Format example: { "gold": 500, "wood":5 }
DLL_LINKAGE ResourceSet(const JsonNode & node);
DLL_LINKAGE ResourceSet(TResource wood, TResource mercury, TResource ore, TResource sulfur, TResource crystal,
TResource gems, TResource gold, TResource mithril = 0);
#define scalarOperator(OPSIGN) \

View File

@ -1262,6 +1262,18 @@ bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const
return false;
}
TResources CGTownInstance::getBuildingCost(BuildingID buildingID) const
{
if (vstd::contains(town->buildings, buildingID))
return town->buildings.at(buildingID)->resources;
else
{
logGlobal->error("Town %s at %s has no possible building %d!", name, pos.toString(), buildingID.toEnum());
return TResources();
}
}
bool CGTownInstance::hasBuilt(BuildingID buildingID) const
{
return vstd::contains(builtBuildings, buildingID);

View File

@ -265,6 +265,7 @@ public:
//checks if building is constructed and town has same subID
bool hasBuilt(BuildingID buildingID) const;
bool hasBuilt(BuildingID buildingID, int townID) const;
TResources getBuildingCost(BuildingID buildingID) const;
TResources dailyIncome() const; //calculates daily income of this town
int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5)
bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero

View File

@ -137,7 +137,7 @@ EMonsterStrength::EMonsterStrength CMapGenOptions::getMonsterStrength() const
void CMapGenOptions::setMonsterStrength(EMonsterStrength::EMonsterStrength value)
{
monsterStrength = value;
monsterStrength = value;
}
void CMapGenOptions::resetPlayersMap()

View File

@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 787;
const ui32 SERIALIZATION_VERSION = 788;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -0,0 +1,13 @@
/*
* {file}.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 "mock_CPSICallback.h"

View File

@ -0,0 +1,28 @@
/*
* mock_CPLayerSpecificInfoCallback.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 "gtest/gtest.h"
#include "../lib/CGameInfoCallback.h"
#include "../lib/ResourceSet.h"
class CCallback;
class GameCallbackMock : public CPlayerSpecificInfoCallback
{
public:
using CPlayerSpecificInfoCallback::CPlayerSpecificInfoCallback;
MOCK_CONST_METHOD0(getResourceAmount, TResources());
//std::vector <const CGTownInstance *> getTownsInfo(bool onlyOur = true) const;
MOCK_CONST_METHOD0(getTownsInfo, std::vector <const CGTownInstance *>());
MOCK_CONST_METHOD1(getTownsInfo, std::vector <const CGTownInstance *>(bool));
};

View File

@ -0,0 +1,13 @@
/*
* ResourceManagerTest.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
extern boost::thread_specific_ptr<CCallback> cb;

View File

@ -0,0 +1,254 @@
/*
* ResourceManagerTest.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 "gtest/gtest.h"
#include "../AI/VCAI/VCAI.h"
#include "ResourceManagerTest.h"
#include "../AI/VCAI/Goals.h"
#include "mock_VCAI_CGoal.h"
#include "mock_VCAI.h"
#include "mock_ResourceManager.h"
#include "../mock/mock_CPSICallback.h"
#include "../lib/CGameInfoCallback.h"
using namespace Goals;
using namespace ::testing;
struct ResourceManagerTest : public Test//, public IResourceManager
{
std::unique_ptr<ResourceManagerMock> rm;
NiceMock<GameCallbackMock> gcm;
NiceMock<VCAIMock> aim;
TSubgoal invalidGoal, gatherArmy, buildThis, buildAny, recruitHero;
ResourceManagerTest()
{
rm = std::make_unique<NiceMock<ResourceManagerMock>>(&gcm, &aim);
//note: construct new goal for modfications
invalidGoal = sptr(StrictMock<InvalidGoalMock>());
gatherArmy = sptr(StrictMock<GatherArmyGoalMock>());
buildThis = sptr(StrictMock<BuildThis>());
buildAny = sptr(StrictMock<Build>());
recruitHero = sptr(StrictMock<RecruitHero>());
//auto AI = CDynLibHandler::getNewAI("VCAI.dll");
//SET_GLOBAL_STATE(AI);
//gtest couldn't deduce default return value;
ON_CALL(gcm, getTownsInfo(_))
.WillByDefault(Return(std::vector < const CGTownInstance *>()));
ON_CALL(gcm, getTownsInfo())
.WillByDefault(Return(std::vector < const CGTownInstance *>()));
ON_CALL(aim, getFlaggedObjects())
.WillByDefault(Return(std::vector < const CGObjectInstance *>()));
//enable if get unexpected exceptions
//ON_CALL(gcm, getResourceAmount())
// .WillByDefault(Return(TResources()));
}
};
TEST_F(ResourceManagerTest, canAffordMaths)
{
//mocking cb calls inside canAfford()
ON_CALL(gcm, getResourceAmount())
.WillByDefault(Return(TResources(10, 0, 11, 0, 0, 0, 12345)));
TResources buildingCost(10, 0, 10, 0, 0, 0, 5000);
//EXPECT_CALL(gcm, getResourceAmount()).RetiresOnSaturation();
//EXPECT_CALL(gcm, getTownsInfo(_)).RetiresOnSaturation();
EXPECT_NO_THROW(rm->canAfford(buildingCost));
EXPECT_TRUE(rm->canAfford(buildingCost));
TResources armyCost(0, 0, 0, 0, 0, 0, 54321);
EXPECT_FALSE(rm->canAfford(armyCost));
rm->reserveResoures(armyCost, gatherArmy);
EXPECT_FALSE(rm->canAfford(buildingCost)) << "Reserved value should be substracted from free resources";
}
TEST_F(ResourceManagerTest, notifyGoalImplemented)
{
ASSERT_FALSE(rm->hasTasksLeft());
EXPECT_FALSE(rm->notifyGoalCompleted(invalidGoal));
EXPECT_FALSE(rm->hasTasksLeft());
TResources res(0,0,0,0,0,0,12345);;
rm->reserveResoures(res, invalidGoal);
ASSERT_FALSE(rm->hasTasksLeft()) << "Can't push Invalid goal";
EXPECT_FALSE(rm->notifyGoalCompleted(invalidGoal));
EXPECT_FALSE(rm->notifyGoalCompleted(gatherArmy)) << "Queue should be empty";
rm->reserveResoures(res, gatherArmy);
EXPECT_TRUE(rm->notifyGoalCompleted(gatherArmy)) << "Not implemented"; //TODO: try it with not a copy
EXPECT_FALSE(rm->notifyGoalCompleted(gatherArmy)); //already completed
}
TEST_F(ResourceManagerTest, notifyFulfillsAll)
{
TResources res;
ASSERT_TRUE(buildAny->fulfillsMe(buildThis)) << "Goal dependency implemented incorrectly"; //TODO: goal mock?
rm->reserveResoures(res, buildAny);
rm->reserveResoures(res, buildAny);
rm->reserveResoures(res, buildAny);
ASSERT_TRUE(rm->hasTasksLeft()); //regardless if duplicates are allowed or not
rm->notifyGoalCompleted(buildThis);
ASSERT_FALSE(rm->hasTasksLeft()) << "BuildThis didn't remove Build Any!";
}
TEST_F(ResourceManagerTest, queueOrder)
{
ASSERT_FALSE(rm->hasTasksLeft());
TSubgoal buildLow = sptr(StrictMock<BuildThis>()),
buildBit = sptr(StrictMock<BuildThis>()),
buildMed = sptr(StrictMock<BuildThis>()),
buildHigh = sptr(StrictMock<BuildThis>()),
buildVeryHigh = sptr(StrictMock<BuildThis>()),
buildExtra = sptr(StrictMock<BuildThis>()),
buildNotSoExtra = sptr(StrictMock<BuildThis>());
buildLow->setpriority(0).setbid(1);
buildLow->setpriority(1).setbid(2);
buildMed->setpriority(2).setbid(3);
buildHigh->setpriority(5).setbid(4);
buildVeryHigh->setpriority(10).setbid(5);
TResources price(0, 0, 0, 0, 0, 0, 1000);
rm->reserveResoures(price, buildLow);
rm->reserveResoures(price, buildHigh);
rm->reserveResoures(price, buildMed);
ON_CALL(gcm, getResourceAmount())
.WillByDefault(Return(TResources(0,0,0,0,0,0,4000,0))); //we can afford 4 top goals
auto goal = rm->whatToDo();
EXPECT_EQ(goal->goalType, Goals::BUILD_STRUCTURE);
ASSERT_EQ(rm->whatToDo()->bid, 4);
rm->reserveResoures(price, buildBit);
rm->reserveResoures(price, buildVeryHigh);
goal = rm->whatToDo();
EXPECT_EQ(goal->goalType, Goals::BUILD_STRUCTURE);
ASSERT_EQ(goal->bid, 5);
buildExtra->setpriority(3).setbid(100);
EXPECT_EQ(rm->whatToDo(price, buildExtra)->bid, 100);
buildNotSoExtra->setpriority(0.7f).setbid(7);
goal = rm->whatToDo(price, buildNotSoExtra);
EXPECT_NE(goal->bid, 7);
EXPECT_EQ(goal->goalType, Goals::COLLECT_RES) << "We can't afford this goal, need to collect resources";
EXPECT_EQ(goal->resID, Res::GOLD) << "We need to collect gold";
goal = rm->whatToDo();
EXPECT_NE(goal->goalType, Goals::COLLECT_RES);
EXPECT_EQ(goal->bid, 5) << "Should return highest-priority goal";
}
TEST_F(ResourceManagerTest, updateGoalImplemented)
{
ASSERT_FALSE(rm->hasTasksLeft());
TResources res;
res[Res::GOLD] = 12345;
buildThis->setpriority(1);
buildThis->bid = 666;
EXPECT_FALSE(rm->updateGoal(buildThis)); //try update with no objectives -> fail
rm->reserveResoures(res, buildThis);
ASSERT_TRUE(rm->hasTasksLeft());
buildThis->setpriority(4.444f);
auto buildThis2 = sptr(StrictMock<BuildThis>());
buildThis2->bid = 777;
buildThis2->setpriority(3.33f);
EXPECT_FALSE(rm->updateGoal(buildThis2)) << "Shouldn't update with wrong goal";
EXPECT_TRUE(rm->updateGoal(buildThis)) << "Not implemented"; //try update with copy of reserved goal -> true
EXPECT_FALSE(rm->updateGoal(invalidGoal)) << "Can't update Invalid goal";
}
TEST_F(ResourceManagerTest, complexGoalUpdates)
{
//TODO
ASSERT_FALSE(rm->hasTasksLeft());
}
TEST_F(ResourceManagerTest, tasksLeft)
{
ASSERT_FALSE(rm->hasTasksLeft());
}
TEST_F(ResourceManagerTest, reservedResources)
{
//TOOO, not worth it now
}
TEST_F(ResourceManagerTest, freeResources)
{
ON_CALL(gcm, getResourceAmount()) //in case callback or gs gets crazy
.WillByDefault(Return(TResources(-1, 0, -13.0f, -38763, -93764, -464, -12, -98765)));
auto res = rm->freeResources();
ASSERT_GE(res[Res::WOOD], 0);
ASSERT_GE(res[Res::MERCURY], 0);
ASSERT_GE(res[Res::ORE], 0);
ASSERT_GE(res[Res::SULFUR], 0);
ASSERT_GE(res[Res::CRYSTAL], 0);
ASSERT_GE(res[Res::GEMS], 0);
ASSERT_GE(res[Res::GOLD], 0);
ASSERT_GE(res[Res::MITHRIL], 0);
}
TEST_F(ResourceManagerTest, freeResourcesWithManyGoals)
{
ON_CALL(gcm, getResourceAmount())
.WillByDefault(Return(TResources(20, 10, 20, 10, 10, 10, 20000, 0)));
ASSERT_EQ(rm->freeResources(), TResources(20, 10, 20, 10, 10, 10, 20000, 0));
rm->reserveResoures(TResources(0, 4, 0, 0, 0, 0, 13000), gatherArmy);
ASSERT_EQ(rm->freeResources(), TResources(20, 6, 20, 10, 10, 10, 7000, 0));
rm->reserveResoures(TResources(5, 4, 5, 4, 4, 4, 5000), buildThis);
ASSERT_EQ(rm->freeResources(), TResources(15, 2, 15, 6, 6, 6, 2000, 0));
rm->reserveResoures(TResources(0, 0, 0, 0, 0, 0, 2500), recruitHero);
auto res = rm->freeResources();
EXPECT_EQ(res[Res::GOLD], 0) << "We should have 0 gold left";
ON_CALL(gcm, getResourceAmount())
.WillByDefault(Return(TResources(20, 10, 20, 10, 10, 10, 30000, 0))); //+10000 gold
res = rm->freeResources();
EXPECT_EQ(res[Res::GOLD], 9500) << "We should have extra savings now";
}
TEST_F(ResourceManagerTest, freeGold)
{
ON_CALL(gcm, getResourceAmount())
.WillByDefault(Return(TResources(0, 0, 0, 0, 0, 0, 666)));
ASSERT_EQ(rm->freeGold(), 666);
ON_CALL(gcm, getResourceAmount())
.WillByDefault(Return(TResources(0, 0, 0, 0, 0, 0, -3762363)));
ASSERT_GE(rm->freeGold(), 0) << "We should never see negative savings";
}

View File

@ -0,0 +1,18 @@
#include "StdInc.h"
#include "mock_ResourceManager.h"
void ResourceManagerMock::reserveResoures(TResources &res, Goals::TSubgoal goal)
{
ResourceManager::reserveResoures(res, goal);
}
bool ResourceManagerMock::updateGoal(Goals::TSubgoal goal)
{
return ResourceManager::updateGoal(goal);
}
bool ResourceManagerMock::notifyGoalCompleted(Goals::TSubgoal goal)
{
return ResourceManager::notifyGoalCompleted(goal);
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "gtest/gtest.h"
#include "../AI/VCAI/ResourceManager.h"
class ResourceManager;
class CPlayerSpecificInfoCallback;
class VCAI;
class ResourceManagerMock : public ResourceManager
{
friend class ResourceManagerTest; //everything is public
public:
using ResourceManager::ResourceManager;
//access protected members, TODO: consider other architecture?
void reserveResoures(TResources &res, Goals::TSubgoal goal = Goals::TSubgoal()) override;
bool updateGoal(Goals::TSubgoal goal) override;
bool notifyGoalCompleted(Goals::TSubgoal goal) override;
};

23
test/vcai/mock_VCAI.cpp Normal file
View File

@ -0,0 +1,23 @@
/*
* ResourceManagerTest.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 "mock_VCAI.h"
VCAIMock::VCAIMock()
: VCAI()
{
makingTurn = nullptr;
}
VCAIMock::~VCAIMock()
{
}

96
test/vcai/mock_VCAI.h Normal file
View File

@ -0,0 +1,96 @@
/*
* mock_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 "gtest/gtest.h"
#include "../AI/VCAI/VCAI.h"
//dependency hell
#include "../../lib/NetPacks.h" //for Component, TryMoveHero
#include "../../lib/serializer/BinarySerializer.h"
#include "../../lib/serializer/BinaryDeserializer.h"
class VCAIMock : public VCAI
{
public:
VCAIMock();
~VCAIMock() override;
//overloading all "override" methods from VCAI. AI should never call them, anyway
MOCK_CONST_METHOD0(getBattleAIName, std::string());
MOCK_METHOD1(init, void(std::shared_ptr<CCallback> CB));
MOCK_METHOD0(yourTurn, void());
MOCK_METHOD4(heroGotLevel, void(const CGHeroInstance * hero, PrimarySkill::PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID));
MOCK_METHOD3(commanderGotLevel, void(const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID));
MOCK_METHOD6(showBlockingDialog, void(const std::string & text, const std::vector<Component> & components, QueryID askID, const int soundID, bool selection, bool cancel));
MOCK_METHOD4(showGarrisonDialog, void(const CArmedInstance * up, const CGHeroInstance * down, bool removableUnits, QueryID queryID));
MOCK_METHOD4(showTeleportDialog, void(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID));
MOCK_METHOD5(showMapObjectSelectDialog, void(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects));
MOCK_METHOD2(saveGame, void(BinarySerializer & h, const int version));
MOCK_METHOD2(loadGame, void(BinaryDeserializer & h, const int version));
MOCK_METHOD0(finish, void());
MOCK_METHOD1(availableCreaturesChanged, void(const CGDwelling * town));
MOCK_METHOD1(heroMoved, void(const TryMoveHero & details));
MOCK_METHOD1(heroInGarrisonChange, void(const CGTownInstance * town));
MOCK_METHOD2(centerView, void(int3 pos, int focusTime));
MOCK_METHOD1(tileHidden, void(const std::unordered_set<int3, ShashInt3> & pos));
MOCK_METHOD2(artifactMoved, void(const ArtifactLocation & src, const ArtifactLocation & dst));
MOCK_METHOD1(artifactAssembled, void(const ArtifactLocation & al));
MOCK_METHOD1(showTavernWindow, void(const CGObjectInstance * townOrTavern));
MOCK_METHOD1(showThievesGuildWindow, void(const CGObjectInstance * obj));
MOCK_METHOD2(playerBlocked, void(int reason, bool start));
MOCK_METHOD0(showPuzzleMap, void());
MOCK_METHOD1(showShipyardDialog, void(const IShipyard * obj));
MOCK_METHOD2(gameOver, void(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult));
MOCK_METHOD1(artifactPut, void(const ArtifactLocation & al));
MOCK_METHOD1(artifactRemoved, void(const ArtifactLocation & al));
MOCK_METHOD1(artifactDisassembled, void(const ArtifactLocation & al));
MOCK_METHOD3(heroVisit, void(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start));
MOCK_METHOD1(availableArtifactsChanged, void(const CGBlackMarket * bm));
MOCK_METHOD2(heroVisitsTown, void(const CGHeroInstance * hero, const CGTownInstance * town));
MOCK_METHOD1(tileRevealed, void(const std::unordered_set<int3, ShashInt3> & pos));
MOCK_METHOD3(heroExchangeStarted, void(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query));
MOCK_METHOD3(heroPrimarySkillChanged, void(const CGHeroInstance * hero, int which, si64 val));
MOCK_METHOD3(showRecruitmentDialog, void(const CGDwelling * dwelling, const CArmedInstance * dst, int level));
MOCK_METHOD1(heroMovePointsChanged, void(const CGHeroInstance * hero));
MOCK_METHOD2(garrisonsChanged, void(ObjectInstanceID id1, ObjectInstanceID id2));
MOCK_METHOD1(newObject, void(const CGObjectInstance * obj));
MOCK_METHOD2(showHillFortWindow, void(const CGObjectInstance * object, const CGHeroInstance * visitor));
MOCK_METHOD2(playerBonusChanged, void(const Bonus & bonus, bool gain));
MOCK_METHOD1(heroCreated, void(const CGHeroInstance *));
MOCK_METHOD2(advmapSpellCast, void(const CGHeroInstance * caster, int spellID));
MOCK_METHOD3(showInfoDialog, void(const std::string & text, const std::vector<Component> & components, int soundID));
MOCK_METHOD1(requestRealized, void(PackageApplied * pa));
MOCK_METHOD0(receivedResource, void());
MOCK_METHOD1(objectRemoved, void(const CGObjectInstance * obj));
MOCK_METHOD2(showUniversityWindow, void(const IMarket * market, const CGHeroInstance * visitor));
MOCK_METHOD1(heroManaPointsChanged, void(const CGHeroInstance * hero));
MOCK_METHOD3(heroSecondarySkillChanged, void(const CGHeroInstance * hero, int which, int val));
MOCK_METHOD0(battleResultsApplied, void());
MOCK_METHOD1(objectPropertyChanged, void(const SetObjectProperty * sop));
MOCK_METHOD3(buildChanged, void(const CGTownInstance * town, BuildingID buildingID, int what));
MOCK_METHOD3(heroBonusChanged, void(const CGHeroInstance * hero, const Bonus & bonus, bool gain));
MOCK_METHOD2(showMarketWindow, void(const IMarket * market, const CGHeroInstance * visitor));
MOCK_METHOD1(showWorldViewEx, void(const std::vector<ObjectPosInfo> & objectPositions));
MOCK_METHOD6(battleStart, void(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side));
MOCK_METHOD1(battleEnd, void(const BattleResult * br));
MOCK_METHOD2(requestSent, void(const CPackForServer * pack, int requestID));
//interetsing stuff
MOCK_CONST_METHOD0(getFlaggedObjects, std::vector<const CGObjectInstance *>());
};

View File

@ -0,0 +1,35 @@
/*
* mock_VCAI_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 "../AI/VCAI/Goals.h"
namespace Goals
{
class InvalidGoalMock : public Invalid
{
public:
//MOCK_METHOD1(accept, void(VCAI *));
//MOCK_METHOD1(accept, float(FuzzyHelper *));
//MOCK_METHOD1(fulfillsMe, bool(TSubgoal));
//bool operator==(AbstractGoal & g) override
//{
// return false;
//};
//MOCK_METHOD0(getAllPossibleSubgoals, TGoalVec());
//MOCK_METHOD0(whatToDoToAchieve, TSubgoal());
};
class GatherArmyGoalMock : public GatherArmy
{
};
}