Przeglądaj źródła

I have no idea what I'm doing

DJWarmonger 7 lat temu
rodzic
commit
273802c92c

+ 49 - 0
AI/VCAI/AIUtility.cpp

@@ -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;

+ 15 - 2
AI/VCAI/AIUtility.h

@@ -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 - 0
AI/VCAI/AIhelper.cpp

@@ -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 - 0
AI/VCAI/AIhelper.h

@@ -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;
+};
+

+ 8 - 4
AI/VCAI/Fuzzy.cpp

@@ -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
 }

+ 1 - 0
AI/VCAI/Fuzzy.h

@@ -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);

+ 322 - 74
AI/VCAI/Goals.cpp

@@ -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)
-	{
-		return false;
-	}
-	auto obj = cb->getObj(ObjectInstanceID(objid));
-	if(!obj)
+	//TODO: VisitObj shoudl not be used for heroes, but...
+	if(goal->goalType == Goals::VISIT_TILE)
 	{
-		logAi->error("Hero %s: VisitHero::fulfillsMe at %s: object %d not found", hero.name, goal->tile.toString(), objid);
-		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;
 	}
-	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())
 		{

+ 72 - 39
AI/VCAI/Goals.h

@@ -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 - 0
AI/VCAI/ResourceManager.cpp

@@ -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 - 0
AI/VCAI/ResourceManager.h

@@ -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;
+	}
+};

+ 177 - 166
AI/VCAI/VCAI.cpp

@@ -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)
+		else if (canBuild == EBuildingState::PREREQUIRES)
 		{
-			//We can't do anything about it - no requests from this function
-			continue;
+			// can happen when dependencies have their own missing dependencies
+			if (tryBuildThisStructure(t, buildID, maxDays - 1))
+				return true;
 		}
-		else if(canBuild == EBuildingState::PREREQUIRES)
+		else if (canBuild == EBuildingState::MISSING_BASE)
 		{
-			// can happen when dependencies have their own missing dependencies
-			if(tryBuildStructure(t, buildID, maxDays - 1))
+			if (tryBuildThisStructure(t, b->upgrade, maxDays - 1))
 				return true;
 		}
-		else if(canBuild == EBuildingState::MISSING_BASE)
+		else if (canBuild == EBuildingState::NO_RESOURCES)
 		{
-			if(tryBuildStructure(t, b->upgrade, maxDays - 1))
-				 return true;
+			//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;
 	}

+ 83 - 73
AI/VCAI/VCAI.h

@@ -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;
-
-	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 saveGame(BinarySerializer & h, const int version) override; //saving
+	void loadGame(BinaryDeserializer & h, const int version) override; //loading
+	void finish() override;
+
+	void availableCreaturesChanged(const CGDwelling * town) override;
+	void heroMoved(const TryMoveHero & details) override;
+	void heroInGarrisonChange(const CGTownInstance * town) override;
+	void centerView(int3 pos, int focusTime) override;
+	void tileHidden(const std::unordered_set<int3, ShashInt3> & pos) override;
+	void artifactMoved(const ArtifactLocation & src, const ArtifactLocation & dst) override;
+	void artifactAssembled(const ArtifactLocation & al) override;
+	void showTavernWindow(const CGObjectInstance * townOrTavern) override;
+	void showThievesGuildWindow(const CGObjectInstance * obj) override;
+	void playerBlocked(int reason, bool start) override;
+	void showPuzzleMap() override;
+	void showShipyardDialog(const IShipyard * obj) override;
+	void gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult) override;
+	void artifactPut(const ArtifactLocation & al) override;
+	void artifactRemoved(const ArtifactLocation & al) override;
+	void artifactDisassembled(const ArtifactLocation & al) override;
+	void heroVisit(const CGHeroInstance * visitor, const CGObjectInstance * visitedObj, bool start) override;
+	void availableArtifactsChanged(const CGBlackMarket * bm = nullptr) override;
+	void heroVisitsTown(const CGHeroInstance * hero, const CGTownInstance * town) override;
+	void tileRevealed(const std::unordered_set<int3, ShashInt3> & pos) override;
+	void heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, QueryID query) override;
+	void heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) override;
+	void showRecruitmentDialog(const CGDwelling * dwelling, const CArmedInstance * dst, int level) override;
+	void heroMovePointsChanged(const CGHeroInstance * hero) override;
+	void garrisonsChanged(ObjectInstanceID id1, ObjectInstanceID id2) override;
+	void newObject(const CGObjectInstance * obj) override;
+	void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
+	void playerBonusChanged(const Bonus & bonus, bool gain) override;
+	void heroCreated(const CGHeroInstance *) override;
+	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
+	void showInfoDialog(const std::string & text, const std::vector<Component> & components, int soundID) override;
+	void requestRealized(PackageApplied * pa) override;
+	void receivedResource() override;
+	void objectRemoved(const CGObjectInstance * obj) override;
+	void showUniversityWindow(const IMarket * market, const CGHeroInstance * visitor) override;
+	void heroManaPointsChanged(const CGHeroInstance * hero) override;
+	void heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val) override;
+	void battleResultsApplied() override;
+	void objectPropertyChanged(const SetObjectProperty * sop) override;
+	void buildChanged(const CGTownInstance * town, BuildingID buildingID, int what) override;
+	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
+	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
 
-	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;

+ 72 - 72
lib/CGameInfoCallback.h

@@ -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;
-
-	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;
-
-	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 howManyTowns() const;
+	virtual int howManyHeroes(bool includeGarrisoned = true) const;
+	virtual int3 getGrailPos(double *outKnownRatio);
+	virtual boost::optional<PlayerColor> getMyColor() 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;
+
+	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

+ 15 - 0
lib/ResourceSet.cpp

@@ -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)

+ 4 - 1
lib/ResourceSet.h

@@ -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)									\

+ 12 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -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);

+ 1 - 0
lib/mapObjects/CGTownInstance.h

@@ -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

+ 1 - 1
lib/rmg/CMapGenOptions.cpp

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

+ 1 - 1
lib/serializer/CSerializer.h

@@ -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";
 

+ 13 - 0
test/mock/mock_CPSICallback.cpp

@@ -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"

+ 28 - 0
test/mock/mock_CPSICallback.h

@@ -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));
+};

+ 13 - 0
test/vcai/ResourceManagerTest.h

@@ -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;

+ 254 - 0
test/vcai/ResurceManagerTest.cpp

@@ -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";
+}

+ 18 - 0
test/vcai/mock_ResourceManager.cpp

@@ -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);
+}

+ 19 - 0
test/vcai/mock_ResourceManager.h

@@ -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 - 0
test/vcai/mock_VCAI.cpp

@@ -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 - 0
test/vcai/mock_VCAI.h

@@ -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 *>());
+
+};

+ 35 - 0
test/vcai/mock_VCAI_CGoal.h

@@ -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
+	{
+	};
+}