DJWarmonger 7 éve
szülő
commit
6615870045

+ 27 - 0
AI/VCAI/AIhelper.cpp

@@ -11,12 +11,15 @@
 
 
 #include "AIhelper.h"
 #include "AIhelper.h"
 #include "ResourceManager.h"
 #include "ResourceManager.h"
+#include "BuildingManager.h"
 
 
 boost::thread_specific_ptr<AIhelper> ah;
 boost::thread_specific_ptr<AIhelper> ah;
 
 
 AIhelper::AIhelper()
 AIhelper::AIhelper()
 {
 {
 	resourceManager.reset(new ResourceManager());
 	resourceManager.reset(new ResourceManager());
+	buildingManager.reset(new BuildingManager());
+	//TODO: push to vector
 }
 }
 
 
 AIhelper::~AIhelper()
 AIhelper::~AIhelper()
@@ -30,12 +33,36 @@ bool AIhelper::notifyGoalCompleted(Goals::TSubgoal goal)
 
 
 void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
 void AIhelper::setCB(CPlayerSpecificInfoCallback * CB)
 {
 {
+	//TODO: for
 	resourceManager->setCB(CB);
 	resourceManager->setCB(CB);
+	buildingManager->setCB(CB);
 }
 }
 
 
 void AIhelper::setAI(VCAI * AI)
 void AIhelper::setAI(VCAI * AI)
 {
 {
+	//TODO: for loop
 	resourceManager->setAI(AI);
 	resourceManager->setAI(AI);
+	buildingManager->setAI(AI);
+}
+
+bool AIhelper::getBuildingOptions(const CGTownInstance * t)
+{
+	return buildingManager->getBuildingOptions(t);
+}
+
+boost::optional<PotentialBuilding> AIhelper::immediateBuilding() const
+{
+	return buildingManager->immediateBuilding();
+}
+
+boost::optional<PotentialBuilding> AIhelper::expensiveBuilding() const
+{
+	return buildingManager->expensiveBuilding();
+}
+
+boost::optional<BuildingID> AIhelper::canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const
+{
+	return buildingManager->canBuildAnyStructure(t, buildList, maxDays);
 }
 }
 
 
 Goals::TSubgoal AIhelper::whatToDo(TResources & res, Goals::TSubgoal goal)
 Goals::TSubgoal AIhelper::whatToDo(TResources & res, Goals::TSubgoal goal)

+ 14 - 2
AI/VCAI/AIhelper.h

@@ -15,23 +15,28 @@
 */
 */
 
 
 #include "ResourceManager.h"
 #include "ResourceManager.h"
+#include "BuildingManager.h"
 
 
 class ResourceManager;
 class ResourceManager;
+class BuildingManager;
+
 
 
 //indirection interface for various modules
 //indirection interface for various modules
-class DLL_EXPORT AIhelper : public IResourceManager
+class DLL_EXPORT AIhelper : public IResourceManager, public IBuildingManager
 {
 {
 	friend class VCAI;
 	friend class VCAI;
 	friend struct SetGlobalState; //mess?
 	friend struct SetGlobalState; //mess?
 
 
 	//members are thread_specific. AIhelper is global
 	//members are thread_specific. AIhelper is global
 	std::shared_ptr<ResourceManager> resourceManager;
 	std::shared_ptr<ResourceManager> resourceManager;
+	std::shared_ptr<BuildingManager> buildingManager;
+	//TODO: vector<IAbstractManager>
 	std::shared_ptr<VCAI> ai;
 	std::shared_ptr<VCAI> ai;
 public:
 public:
 	AIhelper();
 	AIhelper();
 	~AIhelper();
 	~AIhelper();
 
 
-	//TODO: consider common interface with Resource Manager?
+	//from ResourceManager
 	bool canAfford(const TResources & cost) const;
 	bool canAfford(const TResources & cost) const;
 	TResources reservedResources() const override;
 	TResources reservedResources() const override;
 	TResources freeResources() const override;
 	TResources freeResources() const override;
@@ -49,5 +54,12 @@ private:
 
 
 	void setCB(CPlayerSpecificInfoCallback * CB) override;
 	void setCB(CPlayerSpecificInfoCallback * CB) override;
 	void setAI(VCAI * AI) override;
 	void setAI(VCAI * AI) override;
+
+	//from BuildingManager
+public:
+	bool getBuildingOptions(const CGTownInstance * t) override;
+	boost::optional<PotentialBuilding> immediateBuilding() const override;
+	boost::optional<PotentialBuilding> expensiveBuilding() const override;
+	boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
 };
 };
 
 

+ 248 - 0
AI/VCAI/BuildingManager.cpp

@@ -0,0 +1,248 @@
+/*
+* BuildingManager.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#include "StdInc.h"
+#include "BuildingManager.h"
+
+#include "../../CCallback.h"
+#include "../../lib/mapObjects/MapObjects.h"
+
+bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays)
+{
+	if (maxDays == 0)
+	{
+		logAi->warn("Request to build building %d in 0 days!", building.toEnum());
+		return false;
+	}
+
+	if (!vstd::contains(t->town->buildings, building))
+		return false; // no such building in town
+
+	if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
+		return true;
+
+	const CBuilding * buildPtr = t->town->buildings.at(building);
+
+	auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
+	{
+		return t->hasBuilt(buildID);
+	});
+	toBuild.push_back(building);
+
+	for (BuildingID buildID : toBuild)
+	{
+		EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
+		if (canBuild == EBuildingState::HAVE_CAPITAL || canBuild == EBuildingState::FORBIDDEN || canBuild == EBuildingState::NO_WATER)
+			return false; //we won't be able to build this
+	}
+
+	if (maxDays && toBuild.size() > maxDays)
+		return false;
+
+	//TODO: calculate if we have enough resources to build it in maxDays?
+
+	for (const auto & buildID : toBuild)
+	{
+		const CBuilding * b = t->town->buildings.at(buildID);
+
+		EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
+		if (canBuild == EBuildingState::ALLOWED)
+		{
+
+			PotentialBuilding pb;
+			pb.bid = buildID;
+			pb.price = t->getBuildingCost(buildID);
+			immediateBuildings.push_back(pb); //these are checked again in try
+			return true;
+		}
+		else if (canBuild == EBuildingState::PREREQUIRES)
+		{
+			// can happen when dependencies have their own missing dependencies
+			if (tryBuildThisStructure(t, buildID, maxDays - 1))
+				return true;
+		}
+		else if (canBuild == EBuildingState::MISSING_BASE)
+		{
+			if (tryBuildThisStructure(t, b->upgrade, maxDays - 1))
+				return true;
+		}
+		else if (canBuild == EBuildingState::NO_RESOURCES)
+		{
+			//we may need to gather resources for those
+			PotentialBuilding pb;
+			pb.bid = buildID;
+			pb.price = t->getBuildingCost(buildID);
+			expensiveBuildings.push_back(pb); //these are checked again in try
+			return false;
+		}
+		else
+			return false;
+	}
+	return false;
+}
+
+bool BuildingManager::tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
+{
+	for (const auto & building : buildList)
+	{
+		if (t->hasBuilt(building))
+			continue;
+		return tryBuildThisStructure(t, building, maxDays);
+
+	}
+	return false; //Can't build anything
+}
+
+
+boost::optional<BuildingID> BuildingManager::canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const
+{
+	for (const auto & building : buildList)
+	{
+		if (t->hasBuilt(building))
+			continue;
+		if (cb->canBuildStructure(t, building))
+			return boost::optional<BuildingID>(building);
+	}
+	return boost::optional<BuildingID>(); //Can't build anything
+}
+
+bool BuildingManager::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
+{
+	for (const auto & building : buildList)
+	{
+		if (t->hasBuilt(building))
+			continue;
+		return tryBuildThisStructure(t, building, maxDays);
+	}
+	return false; //Nothing to build
+}
+
+void BuildingManager::setCB(CPlayerSpecificInfoCallback * CB)
+{
+	cb = CB;
+}
+
+void BuildingManager::setAI(VCAI * AI)
+{
+	ai = AI;
+}
+//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 };
+static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
+static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
+BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
+static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
+BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
+static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
+BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
+static const BuildingID _spells[] = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
+BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
+static const BuildingID extra[] = { BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
+BuildingID::SPECIAL_4, BuildingID::SHIPYARD }; // all remaining buildings
+
+bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
+{
+	//TODO make *real* town development system
+	//TODO: faction-specific development: use special buildings, build dwellings in better order, etc
+	//TODO: build resource silo, defences when needed
+	//Possible - allow "locking" on specific building (build prerequisites and then building itself)
+
+	immediateBuildings.clear();
+	expensiveBuildings.clear();
+
+	//below algorithm focuses on economy growth at start of the game.
+
+	TResources currentRes = cb->getResourceAmount();
+	TResources currentIncome = t->dailyIncome();
+
+	if (tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
+		return true;
+
+	//the more gold the better and less problems later
+	if (tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
+		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 (cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
+		{
+			if (tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements,
+				capitolRequirements + ARRAY_COUNT(capitolRequirements))))
+				return true;
+		}
+	}
+
+	//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 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 true;
+	}
+
+	//try to upgrade dwelling
+	for (int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
+	{
+		if (t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
+		{
+			if (tryBuildThisStructure(t, unitsUpgrade[i]))
+				return true;
+		}
+	}
+
+	//remaining tasks
+	if (tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
+		return true;
+	if (tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
+		return true;
+
+	//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
+	std::vector<BuildingID> extraBuildings;
+	for (auto buildingInfo : t->town->buildings)
+	{
+		if (buildingInfo.first > 43)
+			extraBuildings.push_back(buildingInfo.first);
+	}
+	if (tryBuildAnyStructure(t, extraBuildings))
+		return true;
+
+	return false;
+
+}
+
+boost::optional<PotentialBuilding> BuildingManager::immediateBuilding() const
+{
+	if (immediateBuildings.size())
+		return boost::optional<PotentialBuilding>(immediateBuildings.front()); //back? whatever
+	else
+		return boost::optional<PotentialBuilding>();
+}
+
+boost::optional<PotentialBuilding> BuildingManager::expensiveBuilding() const
+{
+	if (expensiveBuildings.size())
+		return boost::optional<PotentialBuilding>(expensiveBuildings.front());
+	else
+		return boost::optional<PotentialBuilding>();
+}
+

+ 74 - 0
AI/VCAI/BuildingManager.h

@@ -0,0 +1,74 @@
+/*
+* BuildingManager.h, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#pragma once
+
+#include "AIUtility.h"
+
+#include "../../lib/GameConstants.h"
+#include "../../lib/VCMI_Lib.h"
+#include "../../lib/CTownHandler.h"
+#include "../../lib/CBuildingHandler.h"
+#include "VCAI.h"
+
+struct DLL_EXPORT PotentialBuilding
+{
+	BuildingID bid;
+	TResources price;
+	//days to build?
+};
+
+class DLL_EXPORT IBuildingManager //: public: IAbstractManager
+{ //info about town development
+public:
+	virtual ~IBuildingManager() = default;
+	virtual void setCB(CPlayerSpecificInfoCallback * CB) = 0;
+	virtual void setAI(VCAI * AI) = 0;
+
+	virtual bool getBuildingOptions(const CGTownInstance * t) = 0;
+	virtual boost::optional<PotentialBuilding> immediateBuilding() const = 0;
+	virtual boost::optional<PotentialBuilding> expensiveBuilding() const = 0;
+	virtual boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays) const = 0;
+};
+
+class DLL_EXPORT BuildingManager : public IBuildingManager
+{
+	friend class VCAI;
+	friend class AIhelper;
+	friend struct SetGlobalState;
+
+	CPlayerSpecificInfoCallback * cb; //this is enough, but we downcast from CCallback
+	VCAI * ai;
+
+public:
+
+	//try build anything in given town, and execute resulting Goal if any
+	bool getBuildingOptions(const CGTownInstance * t) override;
+	boost::optional<PotentialBuilding> immediateBuilding() const override;
+	boost::optional<PotentialBuilding> expensiveBuilding() const override;
+	boost::optional<BuildingID> canBuildAnyStructure(const CGTownInstance * t, const std::vector<BuildingID> & buildList, unsigned int maxDays = 7) const override;
+
+protected:
+
+	bool tryBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
+	//try build first unbuilt structure
+	bool tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays = 7);
+	//try build ANY unbuilt structure
+	
+	bool tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays = 7);
+
+private:
+	//TODO: remember current town?
+	std::vector<PotentialBuilding> immediateBuildings; //what we can build right now in current town
+	std::vector<PotentialBuilding> expensiveBuildings; //what we coudl build but can't afford
+
+	void setCB(CPlayerSpecificInfoCallback * CB) override;
+	void setAI(VCAI * AI) override;
+};

+ 4 - 0
AI/VCAI/CMakeLists.txt

@@ -11,6 +11,8 @@ set(VCAI_SRCS
 		AIUtility.cpp
 		AIUtility.cpp
 		AIhelper.cpp
 		AIhelper.cpp
 		ResourceManager.cpp
 		ResourceManager.cpp
+		BuildingManager.cpp
+		MapObjectsEvaluator.cpp
 		Fuzzy.cpp
 		Fuzzy.cpp
 		Goals.cpp
 		Goals.cpp
 		main.cpp
 		main.cpp
@@ -23,6 +25,8 @@ set(VCAI_HEADERS
 		AIUtility.h
 		AIUtility.h
 		AIhelper.h
 		AIhelper.h
 		ResourceManager.h
 		ResourceManager.h
+		BuildingManager.h
+		MapObjectsEvaluator.h
 		Fuzzy.h
 		Fuzzy.h
 		Goals.h
 		Goals.h
 		VCAI.h
 		VCAI.h

+ 106 - 14
AI/VCAI/Fuzzy.cpp

@@ -19,6 +19,7 @@
 #include "../../lib/VCMI_Lib.h"
 #include "../../lib/VCMI_Lib.h"
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "VCAI.h"
 #include "VCAI.h"
+#include "MapObjectsEvaluator.h"
 
 
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define MIN_AI_STRENGHT (0.5f) //lower when combat AI gets smarter
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
 #define UNGUARDED_OBJECT (100.0f) //we consider unguarded objects 100 times weaker than us
@@ -97,6 +98,8 @@ FuzzyHelper::FuzzyHelper()
 	ta.configure();
 	ta.configure();
 	initVisitTile();
 	initVisitTile();
 	vt.configure();
 	vt.configure();
+	initWanderTarget();
+	wanderTarget.configure();
 }
 }
 
 
 
 
@@ -199,6 +202,20 @@ void FuzzyHelper::initTacticalAdvantage()
 	}
 	}
 }
 }
 
 
+float FuzzyHelper::calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const
+{
+	float turns = 0.0f;
+	float distance = CPathfinderHelper::getMovementCost(h, tile);
+	if(distance)
+	{
+		if(distance < h->movement) //we can move there within one turn
+			turns = (fl::scalar)distance / h->movement;
+		else
+			turns = 1 + (fl::scalar)(distance - h->movement) / h->maxMovePoints(true); //bool on land?
+	}
+	return turns;
+}
+
 ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
 ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
 {
 {
 	//this one is not fuzzy anymore, just calculate weighted average
 	//this one is not fuzzy anymore, just calculate weighted average
@@ -272,6 +289,38 @@ float FuzzyHelper::getTacticalAdvantage(const CArmedInstance * we, const CArmedI
 	return output;
 	return output;
 }
 }
 
 
+float FuzzyHelper::getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj)
+{
+	float distFromObject = calculateTurnDistanceInputValue(&h, obj->pos);
+	boost::optional<int> objValueKnownByAI = MapObjectsEvaluator::getInstance().getObjectValue(obj->ID, obj->subID);
+	int objValue = 0;
+
+	if(objValueKnownByAI != boost::none) //consider adding value manipulation based on object instances on map
+	{
+		objValue = std::min(std::max(objValueKnownByAI.get(), 0), 20000);
+	}
+	else
+	{
+		MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
+		logGlobal->warn("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
+	}
+	
+	float output = -1.0f;
+	try
+	{
+		wanderTarget.distance->setValue(distFromObject);
+		wanderTarget.objectValue->setValue(objValue);
+		wanderTarget.engine.process();
+		output = wanderTarget.visitGain->getValue();
+	}
+	catch (fl::Exception & fe)
+	{
+		logAi->error("evaluate getWanderTargetObjectValue: %s", fe.getWhat());
+	}
+	assert(output >= 0.0f);
+	return output;
+}
+
 FuzzyHelper::TacticalAdvantage::~TacticalAdvantage()
 FuzzyHelper::TacticalAdvantage::~TacticalAdvantage()
 {
 {
 	//TODO: smart pointers?
 	//TODO: smart pointers?
@@ -413,6 +462,55 @@ void FuzzyHelper::initVisitTile()
 	}
 	}
 }
 }
 
 
+void FuzzyHelper::initWanderTarget()
+{
+	try
+	{
+		wanderTarget.distance = new fl::InputVariable("distance"); //distance on map from object
+		wanderTarget.objectValue = new fl::InputVariable("objectValue"); //value of that object type known by AI
+		wanderTarget.visitGain = new fl::OutputVariable("visitGain");
+		wanderTarget.visitGain->setMinimum(0);
+		wanderTarget.visitGain->setMaximum(10);
+
+		wanderTarget.engine.addInputVariable(wanderTarget.distance);
+		wanderTarget.engine.addInputVariable(wanderTarget.objectValue);
+		wanderTarget.engine.addOutputVariable(wanderTarget.visitGain);
+
+		//for now distance variable same as in as VisitTile
+		wanderTarget.distance->addTerm(new fl::Ramp("SHORT", 0.5, 0)); 
+		wanderTarget.distance->addTerm(new fl::Triangle("MEDIUM", 0.1, 0.8));
+		wanderTarget.distance->addTerm(new fl::Ramp("LONG", 0.5, 3));
+		wanderTarget.distance->setRange(0, 3.0);
+
+		//objectValue ranges are based on checking RMG priorities of some objects and trying to guess sane value ranges
+		wanderTarget.objectValue->addTerm(new fl::Ramp("LOW", 3000, 0)); //I have feeling that concave shape might work well instead of ramp for objectValue FL terms
+		wanderTarget.objectValue->addTerm(new fl::Triangle("MEDIUM", 2500, 6000));
+		wanderTarget.objectValue->addTerm(new fl::Ramp("HIGH", 5000, 20000));
+		wanderTarget.objectValue->setRange(0, 20000); //relic artifact value is border value by design, even better things are scaled down.
+
+		wanderTarget.visitGain->addTerm(new fl::Ramp("LOW", 5, 0)); 
+		wanderTarget.visitGain->addTerm(new fl::Triangle("MEDIUM", 4, 6));
+		wanderTarget.visitGain->addTerm(new fl::Ramp("HIGH", 5, 10));
+		wanderTarget.visitGain->setRange(0, 10);
+
+		wanderTarget.addRule("if distance is LONG and objectValue is HIGH then visitGain is MEDIUM");
+		wanderTarget.addRule("if distance is MEDIUM and objectValue is HIGH then visitGain is somewhat HIGH");
+		wanderTarget.addRule("if distance is SHORT and objectValue is HIGH then visitGain is HIGH");
+
+		wanderTarget.addRule("if distance is LONG and objectValue is MEDIUM then visitGain is somewhat LOW");
+		wanderTarget.addRule("if distance is MEDIUM and objectValue is MEDIUM then visitGain is MEDIUM");
+		wanderTarget.addRule("if distance is SHORT and objectValue is MEDIUM then visitGain is somewhat HIGH");
+		
+		wanderTarget.addRule("if distance is LONG and objectValue is LOW then visitGain is very LOW");
+		wanderTarget.addRule("if distance is MEDIUM and objectValue is LOW then visitGain is LOW");
+		wanderTarget.addRule("if distance is SHORT and objectValue is LOW then visitGain is MEDIUM");
+	}
+	catch(fl::Exception & fe)
+	{
+		logAi->error("FindWanderTarget: %s", fe.getWhat());
+	}
+}
+
 float FuzzyHelper::evaluate(Goals::VisitTile & g)
 float FuzzyHelper::evaluate(Goals::VisitTile & g)
 {
 {
 	//we assume that hero is already set and we want to choose most suitable one for the mission
 	//we assume that hero is already set and we want to choose most suitable one for the mission
@@ -420,20 +518,7 @@ float FuzzyHelper::evaluate(Goals::VisitTile & g)
 		return 0;
 		return 0;
 
 
 	//assert(cb->isInTheMap(g.tile));
 	//assert(cb->isInTheMap(g.tile));
-	float turns = 0;
-	float distance = CPathfinderHelper::getMovementCost(g.hero.h, g.tile);
-	if(!distance) //we stand on that tile
-	{
-		turns = 0;
-	}
-	else
-	{
-		if(distance < g.hero->movement) //we can move there within one turn
-			turns = (fl::scalar)distance / g.hero->movement;
-		else
-			turns = 1 + (fl::scalar)(distance - g.hero->movement) / g.hero->maxMovePoints(true); //bool on land?
-	}
-
+	float turns = calculateTurnDistanceInputValue(g.hero.h, g.tile);
 	float missionImportance = 0;
 	float missionImportance = 0;
 	if(vstd::contains(ai->lockedHeroes, g.hero))
 	if(vstd::contains(ai->lockedHeroes, g.hero))
 		missionImportance = ai->lockedHeroes[g.hero]->priority;
 		missionImportance = ai->lockedHeroes[g.hero]->priority;
@@ -549,3 +634,10 @@ void FuzzyHelper::setPriority(Goals::TSubgoal & g) //calls evaluate - Visitor pa
 {
 {
 	g->setpriority(g->accept(this)); //this enforces returned value is set
 	g->setpriority(g->accept(this)); //this enforces returned value is set
 }
 }
+
+FuzzyHelper::EvalWanderTargetObject::~EvalWanderTargetObject()
+{ 
+	delete distance;
+	delete objectValue;
+	delete visitGain;
+}

+ 13 - 1
AI/VCAI/Fuzzy.h

@@ -55,7 +55,17 @@ class FuzzyHelper
 		~EvalVisitTile();
 		~EvalVisitTile();
 	} vt;
 	} vt;
 
 
-	
+	class EvalWanderTargetObject : public engineBase //designed for use with VCAI::wander()
+	{
+	public:
+		fl::InputVariable * distance;
+		fl::InputVariable * objectValue;
+		fl::OutputVariable * visitGain;
+		~EvalWanderTargetObject();
+	} wanderTarget;
+
+private:
+	float calculateTurnDistanceInputValue(const CGHeroInstance * h, int3 tile) const;
 
 
 public:
 public:
 	enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE};
 	enum RuleBlocks {BANK_DANGER, TACTICAL_ADVANTAGE, VISIT_TILE};
@@ -64,6 +74,7 @@ public:
 	FuzzyHelper();
 	FuzzyHelper();
 	void initTacticalAdvantage();
 	void initTacticalAdvantage();
 	void initVisitTile();
 	void initVisitTile();
+	void initWanderTarget();
 
 
 	float evaluate(Goals::Explore & g);
 	float evaluate(Goals::Explore & g);
 	float evaluate(Goals::RecruitHero & g);
 	float evaluate(Goals::RecruitHero & g);
@@ -82,6 +93,7 @@ public:
 
 
 	ui64 estimateBankDanger(const CBank * bank);
 	ui64 estimateBankDanger(const CBank * bank);
 	float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
 	float getTacticalAdvantage(const CArmedInstance * we, const CArmedInstance * enemy); //returns factor how many times enemy is stronger than us
+	float getWanderTargetObjectValue(const CGHeroInstance & h, const ObjectIdRef & obj);
 
 
 	Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
 	Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
 	//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
 	//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);

+ 4 - 3
AI/VCAI/Goals.cpp

@@ -12,6 +12,7 @@
 #include "VCAI.h"
 #include "VCAI.h"
 #include "Fuzzy.h"
 #include "Fuzzy.h"
 #include "ResourceManager.h"
 #include "ResourceManager.h"
+#include "BuildingManager.h"
 #include "../../lib/mapping/CMap.h" //for victory conditions
 #include "../../lib/mapping/CMap.h" //for victory conditions
 #include "../../lib/CPathfinder.h"
 #include "../../lib/CPathfinder.h"
 #include "../../lib/StringConstants.h"
 #include "../../lib/StringConstants.h"
@@ -1367,10 +1368,10 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 				}
 				}
 			}
 			}
 			//build dwelling
 			//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)
+			auto bid = ah->canBuildAnyStructure(t, std::vector<BuildingID>(unitsSource, unitsSource + ARRAY_COUNT(unitsSource)), 8 - cb->getDate(Date::DAY_OF_WEEK));
+			if (bid.is_initialized())
 			{
 			{
-				auto goal = sptr(BuildThis(bid, t).setpriority(priority));
+				auto goal = sptr(BuildThis(bid.get(), t).setpriority(priority));
 				if (!ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
 				if (!ah->containsObjective(goal)) //avoid loops caused by reserving same objective twice
 					ret.push_back(goal);
 					ret.push_back(goal);
 			}
 			}

+ 63 - 0
AI/VCAI/MapObjectsEvaluator.cpp

@@ -0,0 +1,63 @@
+#include "StdInc.h"
+#include "MapObjectsEvaluator.h"
+#include "../../lib/GameConstants.h"
+#include "../../lib/VCMI_Lib.h"
+
+MapObjectsEvaluator & MapObjectsEvaluator::getInstance()
+{
+	static std::unique_ptr<MapObjectsEvaluator> singletonInstance;
+	if(singletonInstance == nullptr)
+		singletonInstance.reset(new MapObjectsEvaluator());
+
+	return *(singletonInstance.get());
+}
+
+MapObjectsEvaluator::MapObjectsEvaluator()
+{
+	for(auto primaryID : VLC->objtypeh->knownObjects())
+	{
+		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
+		{
+			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
+			if(!handler->isStaticObject())
+			{
+				if(handler->getAiValue() != boost::none)
+				{
+					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get();
+				}
+				else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists
+				{
+					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get();
+				}
+				else
+				{
+					objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; //some default handling when aiValue not found
+				}
+			}
+		}	
+	}
+}
+
+boost::optional<int> MapObjectsEvaluator::getObjectValue(int primaryID, int secondaryID) const
+{
+	CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
+	auto object = objectDatabase.find(internalIdentifier);
+	if(object != objectDatabase.end())
+		return object->second;
+
+	logGlobal->trace("Unknown object for AI, ID: " + std::to_string(primaryID) + ", SubID: " + std::to_string(secondaryID));
+	return boost::optional<int>();
+}
+
+void MapObjectsEvaluator::addObjectData(int primaryID, int secondaryID, int value) //by current design it updates value if already in AI database
+{
+	CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
+	objectDatabase[internalIdentifier] = value;
+}
+
+void MapObjectsEvaluator::removeObjectData(int primaryID, int secondaryID)
+{
+	CompoundMapObjectID internalIdentifier = CompoundMapObjectID(primaryID, secondaryID);
+	vstd::erase_if_present(objectDatabase, internalIdentifier);
+}
+

+ 25 - 0
AI/VCAI/MapObjectsEvaluator.h

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

+ 50 - 233
AI/VCAI/VCAI.cpp

@@ -11,6 +11,7 @@
 #include "VCAI.h"
 #include "VCAI.h"
 #include "Fuzzy.h"
 #include "Fuzzy.h"
 #include "ResourceManager.h"
 #include "ResourceManager.h"
+#include "BuildingManager.h"
 
 
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/MapObjects.h"
@@ -1181,241 +1182,35 @@ void VCAI::recruitCreatures(const CGDwelling * d, const CArmedInstance * recruit
 	}
 	}
 }
 }
 
 
-bool VCAI::tryBuildThisStructure(const CGTownInstance * t, BuildingID building, unsigned int maxDays)
-{
-	if(maxDays == 0)
-	{
-		logAi->warn("Request to build building %d in 0 days!", building.toEnum());
-		return false;
-	}
-
-	if(!vstd::contains(t->town->buildings, building))
-		return false; // no such building in town
-
-	if(t->hasBuilt(building)) //Already built? Shouldn't happen in general
-		return true;
-
-	const CBuilding * buildPtr = t->town->buildings.at(building);
-
-	auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
-	{
-		return t->hasBuilt(buildID);
-	});
-	toBuild.push_back(building);
-
-	for(BuildingID buildID : toBuild)
-	{
-		EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
-		if(canBuild == EBuildingState::HAVE_CAPITAL || canBuild == EBuildingState::FORBIDDEN || canBuild == EBuildingState::NO_WATER)
-			return false; //we won't be able to build this
-	}
-
-	if(maxDays && toBuild.size() > maxDays)
-		return false;
-
-	//TODO: calculate if we have enough resources to build it in maxDays?
-
-	for(const auto & buildID : toBuild)
-	{
-		const CBuilding * b = t->town->buildings.at(buildID);
-
-		EBuildingState::EBuildingState canBuild = cb->canBuildStructure(t, buildID);
-		if (canBuild == EBuildingState::ALLOWED)
-		{
-			buildStructure(t, buildID);
-			return true;
-		}
-		else if (canBuild == EBuildingState::PREREQUIRES)
-		{
-			// can happen when dependencies have their own missing dependencies
-			if (tryBuildThisStructure(t, buildID, maxDays - 1))
-				return true;
-		}
-		else if (canBuild == EBuildingState::MISSING_BASE)
-		{
-			if (tryBuildThisStructure(t, b->upgrade, maxDays - 1))
-				return true;
-		}
-		else if (canBuild == EBuildingState::NO_RESOURCES)
-		{
-			//we may need to gather resources for those
-			PotentialBuilding pb;
-			pb.bid = buildID;
-			pb.price = t->getBuildingCost(buildID);
-			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)
-{
-	for(const auto & building : buildList)
-	{
-		if(t->hasBuilt(building))
-			continue;
-		return tryBuildThisStructure(t, building, maxDays);
-		
-	}
-	return false; //Can't build anything
-}
-
-BuildingID VCAI::canBuildAnyStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays) const
-{
-	for(const auto & building : buildList)
-	{
-		if(t->hasBuilt(building))
-			continue;
-		if(cb->canBuildStructure(t, building))
-			return building;
-	}
-	return BuildingID::NONE; //Can't build anything
-}
-
-bool VCAI::tryBuildNextStructure(const CGTownInstance * t, std::vector<BuildingID> buildList, unsigned int maxDays)
-{
-	for(const auto & building : buildList)
-	{
-		if(t->hasBuilt(building))
-			continue;
-		return tryBuildThisStructure(t, building, maxDays);
-	}
-	return false; //Nothing to build
-}
-
-void 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};
-static const BuildingID capitolRequirements[] = { BuildingID::FORT, BuildingID::CITADEL };
-static const BuildingID unitsSource[] = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
-	BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7};
-static const BuildingID unitsUpgrade[] = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
-	BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP};
-static const BuildingID unitGrowth[] = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::HORDE_1,
-	BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR};
-static const BuildingID _spells[] = {BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
-	BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5};
-static const BuildingID extra[] = {BuildingID::RESOURCE_SILO, BuildingID::SPECIAL_1, BuildingID::SPECIAL_2, BuildingID::SPECIAL_3,
-	BuildingID::SPECIAL_4, BuildingID::SHIPYARD}; // all remaining buildings
-
-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
-	//TODO: build resource silo, defences when needed
-	//Possible - allow "locking" on specific building (build prerequisites and then building itself)
-
-	//below algorithm focuses on economy growth at start of the game.
-	TResources currentRes = cb->getResourceAmount();
-	TResources currentIncome = t->dailyIncome();
-
-	if(tryBuildAnyStructure(t, std::vector<BuildingID>(essential, essential + ARRAY_COUNT(essential))))
-		return true;
-
-	//the more gold the better and less problems later
-	if(tryBuildNextStructure(t, std::vector<BuildingID>(goldSource, goldSource + ARRAY_COUNT(goldSource))))
-		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(cb->canBuildStructure(t, BuildingID::CAPITOL) != EBuildingState::FORBIDDEN)
-		{
-			if(tryBuildNextStructure(t, std::vector<BuildingID>(capitolRequirements,
-									capitolRequirements + ARRAY_COUNT(capitolRequirements))))
-				return true;
-		}
-	}
-
-	//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 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 true;
-	}
-
-	//try to upgrade dwelling
-	for(int i = 0; i < ARRAY_COUNT(unitsUpgrade); i++)
-	{
-		if(t->hasBuilt(unitsSource[i]) && !t->hasBuilt(unitsUpgrade[i]))
-		{
-			if(tryBuildThisStructure(t, unitsUpgrade[i]))
-				return true;
-		}
-	}
-
-	//remaining tasks
-	if(tryBuildNextStructure(t, std::vector<BuildingID>(_spells, _spells + ARRAY_COUNT(_spells))))
-		return true;
-	if(tryBuildAnyStructure(t, std::vector<BuildingID>(extra, extra + ARRAY_COUNT(extra))))
-		return true;
-
-	//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
-	std::vector<BuildingID> extraBuildings;
-	for(auto buildingInfo : t->town->buildings)
-	{
-		if(buildingInfo.first > 43)
-			extraBuildings.push_back(buildingInfo.first);
-	}
-	if(tryBuildAnyStructure(t, extraBuildings))
-		return true;
-
-	return false;
-}
-
 bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm)
 bool VCAI::isGoodForVisit(const CGObjectInstance * obj, HeroPtr h, SectorMap & sm)
 {
 {
 	const int3 pos = obj->visitablePos();
 	const int3 pos = obj->visitablePos();
 	const int3 targetPos = sm.firstTileToGet(h, pos);
 	const int3 targetPos = sm.firstTileToGet(h, pos);
-	if(!targetPos.valid())
+	if (!targetPos.valid())
 		return false;
 		return false;
-	if(!isTileNotReserved(h.get(), targetPos))
+	if (!isTileNotReserved(h.get(), targetPos))
 		return false;
 		return false;
-	if(obj->wasVisited(playerID))
+	if (obj->wasVisited(playerID))
 		return false;
 		return false;
-	if(cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
+	if (cb->getPlayerRelations(ai->playerID, obj->tempOwner) != PlayerRelations::ENEMIES && !isWeeklyRevisitable(obj))
 		return false; // Otherwise we flag or get weekly resources / creatures
 		return false; // Otherwise we flag or get weekly resources / creatures
-	if(!isSafeToVisit(h, pos))
+	if (!isSafeToVisit(h, pos))
 		return false;
 		return false;
-	if(!shouldVisit(h, obj))
+	if (!shouldVisit(h, obj))
 		return false;
 		return false;
-	if(vstd::contains(alreadyVisited, obj))
+	if (vstd::contains(alreadyVisited, obj))
 		return false;
 		return false;
-	if(vstd::contains(reservedObjs, obj))
+	if (vstd::contains(reservedObjs, obj))
 		return false;
 		return false;
-	if(!isAccessibleForHero(targetPos, h))
+	if (!isAccessibleForHero(targetPos, h))
 		return false;
 		return false;
-
 	const CGObjectInstance * topObj = cb->getVisitableObjs(obj->visitablePos()).back(); //it may be hero visiting this obj
 	const CGObjectInstance * topObj = cb->getVisitableObjs(obj->visitablePos()).back(); //it may be hero visiting this obj
-	//we don't try visiting object on which allied or owned hero stands
-	// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
-	if(topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
+																						//we don't try visiting object on which allied or owned hero stands
+																						// -> it will just trigger exchange windows and AI will be confused that obj behind doesn't get visited
+	if (topObj->ID == Obj::HERO && cb->getPlayerRelations(h->tempOwner, topObj->tempOwner) != PlayerRelations::ENEMIES)
 		return false;
 		return false;
 	else
 	else
 		return true; //all of the following is met
 		return true; //all of the following is met
-
 }
 }
 
 
 bool VCAI::isTileNotReserved(const CGHeroInstance * h, int3 t)
 bool VCAI::isTileNotReserved(const CGHeroInstance * h, int3 t)
@@ -1592,7 +1387,12 @@ void VCAI::wander(HeroPtr h)
 
 
 		if(dests.size()) //performance improvement
 		if(dests.size()) //performance improvement
 		{
 		{
-			const ObjectIdRef & dest = *boost::min_element(dests, CDistanceSorter(h.get())); //find next closest one
+			auto fuzzyLogicSorter = [h](const ObjectIdRef & l, const ObjectIdRef & r) -> bool //TODO: create elementar GetObj goal usable for goal decomposition and Wander based on VisitTile logic and object value on top of it
+			{
+				return fh->getWanderTargetObjectValue( *h.get(), l) < fh->getWanderTargetObjectValue(*h.get(), r);
+			};
+
+			const ObjectIdRef & dest = *boost::max_element(dests, fuzzyLogicSorter); //find best object to visit based on fuzzy logic evaluation, TODO: use elementar version of GetObj here in future
 
 
 			//wander should not cause heroes to be reserved - they are always considered free
 			//wander should not cause heroes to be reserved - they are always considered free
 			logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), dest.id.getNum());
 			logAi->debug("Of all %d destinations, object oid=%d seems nice", dests.size(), dest.id.getNum());
@@ -2079,6 +1879,14 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 	}
 	}
 	return ret;
 	return ret;
 }
 }
+
+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", ai->playerID, name, t->name, t->pos.toString());
+	cb->buildBuilding(t, building); //just do this;
+}
+
 void VCAI::tryRealize(Goals::Explore & g)
 void VCAI::tryRealize(Goals::Explore & g)
 {
 {
 	throw cannotFulfillGoalException("EXPLORE is not an elementar goal!");
 	throw cannotFulfillGoalException("EXPLORE is not an elementar goal!");
@@ -2211,23 +2019,32 @@ void VCAI::tryRealize(Goals::Build & g)
 	for(const CGTownInstance * t : cb->getTownsInfo())
 	for(const CGTownInstance * t : cb->getTownsInfo())
 	{
 	{
 		logAi->debug("Looking into %s", t->name);
 		logAi->debug("Looking into %s", t->name);
-		potentialBuildings.clear(); //start fresh with every town
-		if (tryBuildStructure(t))
+		//start fresh with every town
+		ah->getBuildingOptions(t);
+		auto ib = ah->immediateBuilding();
+		if (ib.is_initialized())
+		{
+			buildStructure(t, ib.get().bid); //do it right now
 			didWeBuildSomething = true;
 			didWeBuildSomething = true;
-		else if (potentialBuildings.size())
+		}
+		else
 		{
 		{
-			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)
+			auto eb = ah->expensiveBuilding();
+			if (eb.is_initialized())
 			{
 			{
-				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?
+				auto pb = eb.get(); //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?
+				}
 			}
 			}
 		}
 		}
 	}
 	}

+ 2 - 21
AI/VCAI/VCAI.h

@@ -129,30 +129,10 @@ struct SectorMap
 class DLL_EXPORT VCAI : public CAdventureAI
 class DLL_EXPORT VCAI : public CAdventureAI
 {
 {
 public:
 public:
-	//internal methods for town development
-	//TODO: refactor to separate class BuildManager
-
-	//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 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 FuzzyHelper;
 	friend class ResourceManager;
 	friend class ResourceManager;
+	friend class BuildingManager;
 
 
 	std::map<TeleportChannelID, std::shared_ptr<TeleportChannel>> knownTeleportChannels;
 	std::map<TeleportChannelID, std::shared_ptr<TeleportChannel>> knownTeleportChannels;
 	std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
 	std::map<const CGObjectInstance *, const CGObjectInstance *> knownSubterraneanGates;
@@ -289,6 +269,7 @@ public:
 	void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
 	void performObjectInteraction(const CGObjectInstance * obj, HeroPtr h);
 
 
 	bool moveHeroToTile(int3 dst, HeroPtr h);
 	bool moveHeroToTile(int3 dst, HeroPtr h);
+	void buildStructure(const CGTownInstance * t, BuildingID building); //TODO: move to BuildingManager
 
 
 	void lostHero(HeroPtr h); //should remove all references to hero (assigned tasks and so on)
 	void lostHero(HeroPtr h); //should remove all references to hero (assigned tasks and so on)
 	void waitTillFree();
 	void waitTillFree();

+ 2 - 0
AI/VCAI/VCAI.vcxproj

@@ -141,6 +141,7 @@
     <ClCompile Include="Fuzzy.cpp" />
     <ClCompile Include="Fuzzy.cpp" />
     <ClCompile Include="Goals.cpp" />
     <ClCompile Include="Goals.cpp" />
     <ClCompile Include="main.cpp" />
     <ClCompile Include="main.cpp" />
+    <ClCompile Include="MapObjectsEvaluator.cpp" />
     <ClCompile Include="StdInc.cpp">
     <ClCompile Include="StdInc.cpp">
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
@@ -153,6 +154,7 @@
     <ClInclude Include="AIUtility.h" />
     <ClInclude Include="AIUtility.h" />
     <ClInclude Include="Fuzzy.h" />
     <ClInclude Include="Fuzzy.h" />
     <ClInclude Include="Goals.h" />
     <ClInclude Include="Goals.h" />
+    <ClInclude Include="MapObjectsEvaluator.h" />
     <ClInclude Include="StdInc.h" />
     <ClInclude Include="StdInc.h" />
     <ClInclude Include="VCAI.h" />
     <ClInclude Include="VCAI.h" />
   </ItemGroup>
   </ItemGroup>

+ 12 - 0
config/objects/creatureBanks.json

@@ -12,6 +12,7 @@
 			{
 			{
 				"index" : 0,
 				"index" : 0,
 				"name" : "Cyclops Stockpile",
 				"name" : "Cyclops Stockpile",
+				"aiValue" : 3000,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPCAVE"]
 					"ambient" : ["LOOPCAVE"]
 				},
 				},
@@ -120,6 +121,7 @@
 				"index" : 1,
 				"index" : 1,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Dwarven Treasury",
 				"name" : "Dwarven Treasury",
+				"aiValue" : 2000,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPDWAR"]
 					"ambient" : ["LOOPDWAR"]
 				},
 				},
@@ -210,6 +212,7 @@
 				"index" : 2,
 				"index" : 2,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Griffin Conservatory",
 				"name" : "Griffin Conservatory",
+				"aiValue" : 9000,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPGRIF"]
 					"ambient" : ["LOOPGRIF"]
 				},
 				},
@@ -284,6 +287,7 @@
 				"index" : 3,
 				"index" : 3,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Imp Cache",
 				"name" : "Imp Cache",
+				"aiValue" : 1500,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPFIRE"]
 					"ambient" : ["LOOPFIRE"]
 				},
 				},
@@ -373,6 +377,7 @@
 				"index" : 4,
 				"index" : 4,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Medusa Stores",
 				"name" : "Medusa Stores",
+				"aiValue" : 1500,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPMEDU"]
 					"ambient" : ["LOOPMEDU"]
 				},
 				},
@@ -463,6 +468,7 @@
 				"index" : 5,
 				"index" : 5,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Naga Bank",
 				"name" : "Naga Bank",
+				"aiValue" : 3000,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPNAGA"]
 					"ambient" : ["LOOPNAGA"]
 				},
 				},
@@ -553,6 +559,7 @@
 				"index" : 6,
 				"index" : 6,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Dragon Fly Hive",
 				"name" : "Dragon Fly Hive",
+				"aiValue" : 9000,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPLEAR"]
 					"ambient" : ["LOOPLEAR"]
 				},
 				},
@@ -633,6 +640,7 @@
 				"index" : 0,
 				"index" : 0,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Shipwreck",
 				"name" : "Shipwreck",
+				"aiValue" : 2000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 2000,
 					"value"		: 2000,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -724,6 +732,7 @@
 				"index" : 0,
 				"index" : 0,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Derelict Ship",
 				"name" : "Derelict Ship",
+				"aiValue" : 4000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 4000,
 					"value"		: 4000,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -822,6 +831,7 @@
 				"index" : 0,
 				"index" : 0,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Crypt",
 				"name" : "Crypt",
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1000,
 					"value"		: 1000,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -917,6 +927,7 @@
 				"index" : 0,
 				"index" : 0,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Dragon Utopia",
 				"name" : "Dragon Utopia",
+				"aiValue" : 11000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 10000,
 					"value"		: 10000,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -1031,6 +1042,7 @@
 				"index" : 0,
 				"index" : 0,
 				"resetDuration" : 0,
 				"resetDuration" : 0,
 				"name" : "Pyramid",
 				"name" : "Pyramid",
+				"aiValue" : 8000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 5000,
 					"value"		: 5000,
 					"rarity"	: 20
 					"rarity"	: 20

+ 33 - 1
config/objects/generic.json

@@ -10,7 +10,7 @@
 			}
 			}
 		},
 		},
 		"types" : {
 		"types" : {
-			"prison" : { "index" : 0 }
+			"prison" : { "index" : 0, "aiValue" : 5000 }
 		}
 		}
 	},
 	},
 
 
@@ -25,6 +25,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -45,6 +46,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -65,6 +67,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -79,6 +82,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -100,6 +104,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 8000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 8000,
 					"value"		: 8000,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -120,6 +125,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 10000,
 				"templates" : {
 				"templates" : {
 					"normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 					"normal" : { "animation" : "ava0128.def", "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VV", "VA"] }
 				},
 				},
@@ -151,6 +157,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 750,
 				"templates" :
 				"templates" :
 				{
 				{
 					"base" : { "animation" : "avxredw.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["grass", "swamp", "dirt", "sand", "lava", "rough"] },
 					"base" : { "animation" : "avxredw.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "VV", "VA"], "allowedTerrains":["grass", "swamp", "dirt", "sand", "lava", "rough"] },
@@ -176,6 +183,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 750,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 750,
 					"value"		: 750,
@@ -195,6 +203,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -246,6 +255,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 2000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 5000,
 					"value"		: 5000,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -265,6 +275,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 1500,
 					"value"		: 1500,
@@ -285,6 +296,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 500,
 					"value"		: 500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -304,6 +316,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 2000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 2000,
 					"value"		: 2000,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -323,6 +336,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 3000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 3000,
 					"value"		: 3000,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -344,6 +358,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 0,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -360,6 +375,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 750,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -376,6 +392,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -392,6 +409,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 350,
 				"rmg" : {
 				"rmg" : {
 					"mapLimit"	: 48,
 					"mapLimit"	: 48,
 					"value"		: 3500,
 					"value"		: 3500,
@@ -411,6 +429,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 0,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -428,6 +447,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -447,6 +467,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1000,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -463,6 +484,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 0,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -480,6 +502,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"mapLimit"	: 32,
 					"mapLimit"	: 32,
 					"value"		: 100,
 					"value"		: 100,
@@ -500,6 +523,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"templates" :
 				"templates" :
 				{
 				{
 					"green" : { "animation" : "avxdent.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "BA"], "allowedTerrains":["grass", "swamp", "dirt"] },
 					"green" : { "animation" : "avxdent.def", "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VV", "BA"], "allowedTerrains":["grass", "swamp", "dirt"] },
@@ -524,6 +548,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 2500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 2500,
 					"value"		: 2500,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -542,6 +567,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 3,
 					"zoneLimit"	: 3,
 					"value"		: 1500,
 					"value"		: 1500,
@@ -565,6 +591,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 10000,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -581,6 +608,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 250,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit" : 1,
 					"zoneLimit" : 1,
 					"value"		: 250,
 					"value"		: 250,
@@ -819,6 +847,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 7000,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 7000,
 					"value"		: 7000,
@@ -833,6 +862,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 0,
 				"rmg" : {
 				"rmg" : {
 				}
 				}
 			}
 			}
@@ -850,6 +880,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -870,6 +901,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,

+ 30 - 11
config/objects/moddables.json

@@ -23,6 +23,7 @@
 	"hero" : {
 	"hero" : {
 		"index" :34,
 		"index" :34,
 		"handler": "hero",
 		"handler": "hero",
+		"defaultAiValue" : 5000,
 		"base" : {
 		"base" : {
 			"base" : {
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
@@ -63,14 +64,14 @@
 			}
 			}
 		},
 		},
 		"types" : {
 		"types" : {
-			"wood" :    { "index" : 0, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } },
-			"mercury" : { "index" : 1, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } },
-			"ore" :     { "index" : 2, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def"  } } },
-			"sulfur" :  { "index" : 3, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } },
-			"crystal" : { "index" : 4, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } },
-			"gems" :    { "index" : 5, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } },
-			"gold" :    { "index" : 6, "rmg" : { "value" : 750,  "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } },
-			"mithril" : { "index" : 7 } // TODO: move to WoG?
+			"wood" :    { "index" : 0, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTwood0.def" } } },
+			"mercury" : { "index" : 1, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } },
+			"ore" :     { "index" : 2, "aiValue" : 1400, "rmg" : { "value" : 1400, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTore0.def"  } } },
+			"sulfur" :  { "index" : 3, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } },
+			"crystal" : { "index" : 4, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } },
+			"gems" :    { "index" : 5, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } },
+			"gold" :    { "index" : 6, "aiValue" : 750, "rmg" : { "value" : 750,  "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } },
+			"mithril" : { "index" : 7, "aiValue" : 3500 } // TODO: move to WoG?
 		}
 		}
 	},
 	},
 	
 	
@@ -78,6 +79,7 @@
 	"town" : {
 	"town" : {
 		"index" :98,
 		"index" :98,
 		"handler": "town",
 		"handler": "town",
+		"defaultAiValue" : 20000,
 		"base" : {
 		"base" : {
 			"filters" : {
 			"filters" : {
 				// village image - fort not present
 				// village image - fort not present
@@ -107,6 +109,7 @@
 	"boat" : {
 	"boat" : {
 		"index" :8,
 		"index" :8,
 		"handler": "boat",
 		"handler": "boat",
+		"defaultAiValue" : 0,
 		"base" : {
 		"base" : {
 			"base" : {
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
@@ -124,6 +127,7 @@
 	"borderGuard" : {
 	"borderGuard" : {
 		"index" :9,
 		"index" :9,
 		"handler": "borderGuard",
 		"handler": "borderGuard",
+		"defaultAiValue" : 0,
 		"base" : {
 		"base" : {
 			"sounds" : {
 			"sounds" : {
 				"visit" : ["CAVEHEAD"],
 				"visit" : ["CAVEHEAD"],
@@ -144,6 +148,7 @@
 	"borderGate" : {
 	"borderGate" : {
 		"index" :212,
 		"index" :212,
 		"handler": "borderGate",
 		"handler": "borderGate",
+		"defaultAiValue" : 0,
 		"base" : {
 		"base" : {
 			"sounds" : {
 			"sounds" : {
 				"visit" : ["CAVEHEAD"]
 				"visit" : ["CAVEHEAD"]
@@ -163,6 +168,7 @@
 	"keymasterTent" : {
 	"keymasterTent" : {
 		"index" :10,
 		"index" :10,
 		"handler": "keymaster",
 		"handler": "keymaster",
+		"defaultAiValue" : 10000,
 		"base" : {
 		"base" : {
 			"sounds" : {
 			"sounds" : {
 				"visit" : ["CAVEHEAD"]
 				"visit" : ["CAVEHEAD"]
@@ -183,6 +189,7 @@
 	"seerHut" : {
 	"seerHut" : {
 		"index" :83,
 		"index" :83,
 		"handler": "seerHut",
 		"handler": "seerHut",
+		"defaultAiValue" : 10000,
 		"base" : {
 		"base" : {
 			"base" : {
 			"base" : {
 				"visitableFrom" : [ "---", "+++", "+++" ],
 				"visitableFrom" : [ "---", "+++", "+++" ],
@@ -209,9 +216,9 @@
 			}
 			}
 		},
 		},
 		"types" : {
 		"types" : {
-			"water" : { "index" : 0, "rmg" : { "zoneLimit" : 1,  "value" : 5000, "rarity" : 20 } },
-			"land" : { "index" : 1, "rmg" : { "zoneLimit" : 1,  "value" : 10000, "rarity" : 20 } },
-			"subterra" : { "index" : 2, "rmg" : { "zoneLimit" : 1,  "value" : 7500, "rarity" : 20 } }
+			"water" : { "index" : 0, "aiValue" : 5000, "rmg" : { "zoneLimit" : 1,  "value" : 5000, "rarity" : 20 } },
+			"land" : { "index" : 1, "aiValue": 10000, "rmg" : { "zoneLimit" : 1,  "value" : 10000, "rarity" : 20 } },
+			"subterra" : { "index" : 2, "aiValue" : 7500, "rmg" : { "zoneLimit" : 1,  "value" : 7500, "rarity" : 20 } }
 		}
 		}
 	},
 	},
 
 
@@ -227,6 +234,7 @@
 		"types" : {
 		"types" : {
 			"sawmill" : {
 			"sawmill" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value" : 1500
 					"value" : 1500
 				},
 				},
@@ -236,6 +244,7 @@
 			},
 			},
 			"alchemistLab" : {
 			"alchemistLab" : {
 				"index" : 1,
 				"index" : 1,
+				"aiValue" : 3500,
 				"rmg" : {
 				"rmg" : {
 					"value" : 3500
 					"value" : 3500
 				},
 				},
@@ -245,6 +254,7 @@
 			},
 			},
 			"orePit" : {
 			"orePit" : {
 				"index" : 2,
 				"index" : 2,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value" : 1500
 					"value" : 1500
 				},
 				},
@@ -254,6 +264,7 @@
 			},
 			},
 			"sulfurDune" : {
 			"sulfurDune" : {
 				"index" : 3,
 				"index" : 3,
+				"aiValue" : 3500,
 				"rmg" : {
 				"rmg" : {
 					"value" : 3500
 					"value" : 3500
 				},
 				},
@@ -263,6 +274,7 @@
 			},
 			},
 			"crystalCavern" : {
 			"crystalCavern" : {
 				"index" : 4,
 				"index" : 4,
+				"aiValue" : 3500,
 				"rmg" : {
 				"rmg" : {
 					"value" : 3500
 					"value" : 3500
 				},
 				},
@@ -272,6 +284,7 @@
 			},
 			},
 			"gemPond" : {
 			"gemPond" : {
 				"index" : 5,
 				"index" : 5,
+				"aiValue" : 3500,
 				"rmg" : {
 				"rmg" : {
 					"value" : 3500
 					"value" : 3500
 				},
 				},
@@ -281,6 +294,7 @@
 			},
 			},
 			"goldMine" : {
 			"goldMine" : {
 				"index" : 6,
 				"index" : 6,
+				"aiValue" : 7000,
 				"rmg" : {
 				"rmg" : {
 					"value" : 7000
 					"value" : 7000
 				},
 				},
@@ -290,6 +304,7 @@
 			},
 			},
 			"abandoned" :	{
 			"abandoned" :	{
 				"index" : 7,
 				"index" : 7,
+				"aiValue" : 3500,
 				"sounds" : {
 				"sounds" : {
 					"ambient" : ["LOOPCAVE"],
 					"ambient" : ["LOOPCAVE"],
 					"visit" : ["MYSTERY"]
 					"visit" : ["MYSTERY"]
@@ -300,6 +315,7 @@
 	"abandonedMine" : {
 	"abandonedMine" : {
 		"index" :220,
 		"index" :220,
 		"handler": "mine",
 		"handler": "mine",
+		"defaultAiValue" : 3500,
 		"base" : {
 		"base" : {
 			"sounds" : {
 			"sounds" : {
 				"ambient" : ["LOOPCAVE"]
 				"ambient" : ["LOOPCAVE"]
@@ -313,6 +329,7 @@
 	"garrisonHorizontal": {
 	"garrisonHorizontal": {
 		"index" :33,
 		"index" :33,
 		"handler": "garrison",
 		"handler": "garrison",
+		"defaultAiValue" : 0,
 		"base" : {
 		"base" : {
 			"sounds" : {
 			"sounds" : {
 				"visit" : ["MILITARY"]
 				"visit" : ["MILITARY"]
@@ -336,6 +353,7 @@
 	"garrisonVertical" : {
 	"garrisonVertical" : {
 		"index" :219,
 		"index" :219,
 		"handler": "garrison",
 		"handler": "garrison",
+		"defaultAiValue" : 0,
 		"base" : {
 		"base" : {
 			"sounds" : {
 			"sounds" : {
 				"visit" : ["MILITARY"]
 				"visit" : ["MILITARY"]
@@ -454,6 +472,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"templates" : {
 				"templates" : {
 					"normal" : {
 					"normal" : {
 						"visitableFrom" : [ "+++", "+-+", "+++" ],
 						"visitableFrom" : [ "+++", "+-+", "+++" ],

+ 36 - 1
config/objects/rewardable.json

@@ -12,7 +12,8 @@
 		},
 		},
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
-				"index" : 0//,
+				"index" : 0,
+				"aiValue" : 500//,
 				//"rmg" : {
 				//"rmg" : {
 				//	"zoneLimit"	: 1,
 				//	"zoneLimit"	: 1,
 				//	"value"		: 500,
 				//	"value"		: 500,
@@ -35,6 +36,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 500,
 					"value"		: 500,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -54,6 +56,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 80
 					"rarity"	: 80
@@ -73,6 +76,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 750,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 750,
 					"value"		: 750,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -92,6 +96,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 500,
 					"value"		: 500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -110,6 +115,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 500,
 					"value"		: 500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -128,6 +134,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 500,
 					"value"		: 500,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -146,6 +153,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 6000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 6000,
 					"value"		: 6000,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -167,6 +175,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 2000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 2000,
 					"value"		: 2000,
 					"rarity"	: 500
 					"rarity"	: 500
@@ -186,6 +195,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 2000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 2000,
 					"value"		: 2000,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -205,6 +215,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 500
 					"rarity"	: 500
@@ -224,6 +235,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -243,6 +255,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 1000
 					"rarity"	: 1000
@@ -263,6 +276,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 3000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 3000,
 					"value"		: 3000,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -282,6 +296,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -301,6 +316,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -319,6 +335,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 12000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 12000,
 					"value"		: 12000,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -338,6 +355,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -357,6 +375,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -375,6 +394,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 2500,
 				"rmg" : {
 				"rmg" : {
 					"mapLimit"	: 100,
 					"mapLimit"	: 100,
 					"value"		: 2500,
 					"value"		: 2500,
@@ -395,6 +415,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1000,
 					"value"		: 1000,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -414,6 +435,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1000,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1000,
 					"value"		: 1000,
 					"rarity"	: 50
 					"rarity"	: 50
@@ -433,6 +455,7 @@
 		"types" : {
 		"types" : {
 			"object" : { 
 			"object" : { 
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 1500,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 1500,
 					"value"		: 1500,
 					"rarity"	: 200
 					"rarity"	: 200
@@ -454,6 +477,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 100,
 					"value"		: 100,
 					"rarity"	: 100
 					"rarity"	: 100
@@ -472,6 +496,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -492,6 +517,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -512,6 +538,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -532,6 +559,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -551,6 +579,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -573,6 +602,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"value"		: 100,
 					"value"		: 100,
 					"rarity"	: 20
 					"rarity"	: 20
@@ -591,6 +621,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -611,6 +642,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 200,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 200,
 					"value"		: 200,
@@ -631,6 +663,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -651,6 +684,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 100,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 100,
 					"value"		: 100,
@@ -670,6 +704,7 @@
 		"types" : {
 		"types" : {
 			"object" : {
 			"object" : {
 				"index" : 0,
 				"index" : 0,
+				"aiValue" : 500,
 				"rmg" : {
 				"rmg" : {
 					"zoneLimit"	: 1,
 					"zoneLimit"	: 1,
 					"value"		: 500,
 					"value"		: 500,

+ 3 - 0
config/schemas/object.json

@@ -13,6 +13,9 @@
 		"name": {
 		"name": {
 			"type":"string",
 			"type":"string",
 		},
 		},
+		"defaultAiValue": {
+			"type":"number",
+		},
 
 
 		"handler": {
 		"handler": {
 			"type":"string",
 			"type":"string",

+ 3 - 0
config/schemas/objectType.json

@@ -13,6 +13,9 @@
 		"name": {
 		"name": {
 			"type":"string",
 			"type":"string",
 		},
 		},
+		"aiValue": {
+			"type":"number",
+		},
 
 
 		"sounds": {
 		"sounds": {
 			"type":"object",
 			"type":"object",

+ 25 - 0
lib/mapObjects/CObjectClassesHandler.cpp

@@ -206,6 +206,11 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co
 	obj->handlerName = json["handler"].String();
 	obj->handlerName = json["handler"].String();
 	obj->base = json["base"];
 	obj->base = json["base"];
 	obj->id = selectNextID(json["index"], objects, 256);
 	obj->id = selectNextID(json["index"], objects, 256);
+	if(json["defaultAiValue"].isNull())
+		obj->groupDefaultAiValue = boost::none;
+	else
+		obj->groupDefaultAiValue = json["defaultAiValue"].Integer();
+
 	for (auto entry : json["types"].Struct())
 	for (auto entry : json["types"].Struct())
 	{
 	{
 		loadObjectEntry(entry.first, entry.second, obj);
 		loadObjectEntry(entry.first, entry.second, obj);
@@ -285,6 +290,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string type, std::s
 	throw std::runtime_error("Object type handler not found");
 	throw std::runtime_error("Object type handler not found");
 }
 }
 
 
+TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID compoundIdentifier) const
+{
+	return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
+}
+
 std::set<si32> CObjectClassesHandler::knownObjects() const
 std::set<si32> CObjectClassesHandler::knownObjects() const
 {
 {
 	std::set<si32> ret;
 	std::set<si32> ret;
@@ -382,6 +392,11 @@ std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const
 	return objects.at(type)->handlerName;
 	return objects.at(type)->handlerName;
 }
 }
 
 
+boost::optional<si32> CObjectClassesHandler::getObjGroupAiValue(si32 primaryID) const
+{
+	return objects.at(primaryID)->groupDefaultAiValue;
+}
+
 AObjectTypeHandler::AObjectTypeHandler():
 AObjectTypeHandler::AObjectTypeHandler():
 	type(-1), subtype(-1)
 	type(-1), subtype(-1)
 {
 {
@@ -452,6 +467,11 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional<std::strin
 	for(const JsonNode & node : input["sounds"]["removal"].Vector())
 	for(const JsonNode & node : input["sounds"]["removal"].Vector())
 		sounds.removal.push_back(node.String());
 		sounds.removal.push_back(node.String());
 
 
+	if(input["aiValue"].isNull())
+		aiValue = boost::none;
+	else
+		aiValue = input["aiValue"].Integer();
+
 	initTypeData(input);
 	initTypeData(input);
 }
 }
 
 
@@ -540,6 +560,11 @@ const RandomMapInfo & AObjectTypeHandler::getRMGInfo()
 	return rmgInfo;
 	return rmgInfo;
 }
 }
 
 
+boost::optional<si32> AObjectTypeHandler::getAiValue() const
+{
+	return aiValue;
+}
+
 bool AObjectTypeHandler::isStaticObject()
 bool AObjectTypeHandler::isStaticObject()
 {
 {
 	return false; // most of classes are not static
 	return false; // most of classes are not static

+ 37 - 1
lib/mapObjects/CObjectClassesHandler.h

@@ -65,6 +65,27 @@ struct DLL_LINKAGE RandomMapInfo
 	}
 	}
 };
 };
 
 
+struct DLL_LINKAGE CompoundMapObjectID
+{
+	si32 primaryID;
+	si32 secondaryID;
+
+	CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
+
+	bool operator<(const CompoundMapObjectID& other) const
+	{
+		if(this->primaryID != other.primaryID)
+			return this->primaryID < other.primaryID;
+		else
+			return this->secondaryID < other.secondaryID;
+	}
+
+	bool operator==(const CompoundMapObjectID& other) const
+	{
+		return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
+	}
+};
+
 class DLL_LINKAGE IObjectInfo
 class DLL_LINKAGE IObjectInfo
 {
 {
 public:
 public:
@@ -125,6 +146,8 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
 	std::vector<ObjectTemplate> templates;
 	std::vector<ObjectTemplate> templates;
 
 
 	SObjectSounds sounds;
 	SObjectSounds sounds;
+
+	boost::optional<si32> aiValue;
 protected:
 protected:
 	void preInitObject(CGObjectInstance * obj) const;
 	void preInitObject(CGObjectInstance * obj) const;
 	virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
 	virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
@@ -163,6 +186,8 @@ public:
 
 
 	const RandomMapInfo & getRMGInfo();
 	const RandomMapInfo & getRMGInfo();
 
 
+	boost::optional<si32> getAiValue() const;
+
 	virtual bool isStaticObject();
 	virtual bool isStaticObject();
 
 
 	virtual void afterLoadFinalization();
 	virtual void afterLoadFinalization();
@@ -194,6 +219,10 @@ public:
 		{
 		{
 			h & sounds;
 			h & sounds;
 		}
 		}
+		if(version >= 789)
+		{
+			h & aiValue;
+		}
 	}
 	}
 };
 };
 
 
@@ -216,6 +245,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 
 
 		SObjectSounds sounds;
 		SObjectSounds sounds;
 
 
+		boost::optional<si32> groupDefaultAiValue;
+
 		template <typename Handler> void serialize(Handler &h, const int version)
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
 		{
 			h & name;
 			h & name;
@@ -231,6 +262,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 			{
 			{
 				h & sounds;
 				h & sounds;
 			}
 			}
+			if(version >= 789)
+			{
+				h & groupDefaultAiValue;
+			}
 		}
 		}
 	};
 	};
 
 
@@ -274,6 +309,7 @@ public:
 	/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
 	/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
 	TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
 	TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
 	TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const;
 	TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const;
+	TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
 
 
 	std::string getObjectName(si32 type) const;
 	std::string getObjectName(si32 type) const;
 	std::string getObjectName(si32 type, si32 subtype) const;
 	std::string getObjectName(si32 type, si32 subtype) const;
@@ -284,7 +320,7 @@ public:
 	/// Returns handler string describing the handler (for use in client)
 	/// Returns handler string describing the handler (for use in client)
 	std::string getObjectHandlerName(si32 type) const;
 	std::string getObjectHandlerName(si32 type) const;
 
 
-
+	boost::optional<si32> getObjGroupAiValue(si32 primaryID) const; //default AI value of objects belonging to particular primaryID
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{

+ 1 - 1
lib/serializer/CSerializer.h

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