Browse Source

Merge pull request #1781 from vcmi/object_distribution

Improved object distribution
DjWarmonger 2 years ago
parent
commit
53df84459f

+ 4 - 0
cmake_modules/VCMI_lib.cmake

@@ -101,11 +101,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/rmg/Zone.cpp
 		${MAIN_LIB_DIR}/rmg/Functions.cpp
 		${MAIN_LIB_DIR}/rmg/ObjectManager.cpp
+		${MAIN_LIB_DIR}/rmg/ObjectDistributor.cpp
 		${MAIN_LIB_DIR}/rmg/RoadPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/TreasurePlacer.cpp
 		${MAIN_LIB_DIR}/rmg/RmgMap.cpp
 		${MAIN_LIB_DIR}/rmg/ConnectionsPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/WaterAdopter.cpp
+		${MAIN_LIB_DIR}/rmg/MinePlacer.cpp
 		${MAIN_LIB_DIR}/rmg/TownPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/WaterProxy.cpp
 		${MAIN_LIB_DIR}/rmg/WaterRoutes.cpp
@@ -338,11 +340,13 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/rmg/Zone.h
 		${MAIN_LIB_DIR}/rmg/Functions.h
 		${MAIN_LIB_DIR}/rmg/ObjectManager.h
+		${MAIN_LIB_DIR}/rmg/ObjectDistributor.h
 		${MAIN_LIB_DIR}/rmg/RoadPlacer.h
 		${MAIN_LIB_DIR}/rmg/TreasurePlacer.h
 		${MAIN_LIB_DIR}/rmg/RmgMap.h
 		${MAIN_LIB_DIR}/rmg/ConnectionsPlacer.h
 		${MAIN_LIB_DIR}/rmg/WaterAdopter.h
+		${MAIN_LIB_DIR}/rmg/MinePlacer.h
 		${MAIN_LIB_DIR}/rmg/TownPlacer.h
 		${MAIN_LIB_DIR}/rmg/WaterProxy.h
 		${MAIN_LIB_DIR}/rmg/WaterRoutes.h

+ 5 - 1
lib/mapObjects/CObjectClassesHandler.cpp

@@ -508,7 +508,11 @@ void AObjectTypeHandler::init(const JsonNode & input)
 	if (!input["rmg"].isNull())
 	{
 		rmgInfo.value =     static_cast<ui32>(input["rmg"]["value"].Float());
-		rmgInfo.mapLimit =  loadJsonOrMax(input["rmg"]["mapLimit"]);
+
+		const JsonNode & mapLimit = input["rmg"]["mapLimit"];
+		if (!mapLimit.isNull())
+			rmgInfo.mapLimit.reset(static_cast<ui32>(mapLimit.Float()));
+
 		rmgInfo.zoneLimit = loadJsonOrMax(input["rmg"]["zoneLimit"]);
 		rmgInfo.rarity =    static_cast<ui32>(input["rmg"]["rarity"].Float());
 	} // else block is not needed - set in constructor

+ 3 - 2
lib/mapObjects/CObjectClassesHandler.h

@@ -43,7 +43,7 @@ struct DLL_LINKAGE RandomMapInfo
 	ui32 value;
 
 	/// How many of such objects can be placed on map, 0 = object can not be placed by RMG
-	ui32 mapLimit;
+	boost::optional<ui32> mapLimit;
 
 	/// How many of such objects can be placed in one zone, 0 = unplaceable
 	ui32 zoneLimit;
@@ -53,11 +53,12 @@ struct DLL_LINKAGE RandomMapInfo
 
 	RandomMapInfo():
 		value(0),
-		mapLimit(0),
 		zoneLimit(0),
 		rarity(0)
 	{}
 
+	void setMapLimit(ui32 val) { mapLimit.reset(val); }
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & value;

+ 6 - 1
lib/mapObjects/ObjectTemplate.h

@@ -105,7 +105,12 @@ public:
 	inline bool canBePlacedAtAnyTerrain() const
 	{
 		return anyTerrain;
-	}; 
+	};
+
+	const std::set<TerrainId>& getAllowedTerrains() const
+	{
+		return allowedTerrains;
+	}
 
 	// Checks if object can be placed on specific terrain
 	bool canBePlacedAt(TerrainId terrain) const;

+ 9 - 9
lib/rmg/CMapGenerator.cpp

@@ -276,11 +276,11 @@ void CMapGenerator::genZones()
 
 void CMapGenerator::createWaterTreasures()
 {
-	if(!getZoneWater())
+	if (!getZoneWater())
 		return;
-	
+
 	//add treasures on water
-	for(const auto & treasureInfo : getConfig().waterTreasure)
+	for (const auto& treasureInfo : getConfig().waterTreasure)
 	{
 		getZoneWater()->addTreasureInfo(treasureInfo);
 	}
@@ -296,7 +296,7 @@ void CMapGenerator::fillZones()
 	//we need info about all town types to evaluate dwellings and pandoras with creatures properly
 	//place main town in the middle
 	Load::Progress::setupStepsTill(map->getZones().size(), 50);
-	for(const auto & it : map->getZones())
+	for (const auto& it : map->getZones())
 	{
 		it.second->initFreeTiles();
 		it.second->initModificators();
@@ -305,10 +305,10 @@ void CMapGenerator::fillZones()
 
 	Load::Progress::setupStepsTill(map->getZones().size(), 240);
 	std::vector<std::shared_ptr<Zone>> treasureZones;
-	for(const auto & it : map->getZones())
+	for (const auto& it : map->getZones())
 	{
 		it.second->processModificators();
-		
+
 		if (it.second->getType() == ETemplateZoneType::TREASURE)
 			treasureZones.push_back(it.second);
 
@@ -316,10 +316,10 @@ void CMapGenerator::fillZones()
 	}
 
 	//find place for Grail
-	if(treasureZones.empty())
+	if (treasureZones.empty())
 	{
-		for(const auto & it : map->getZones())
-			if(it.second->getType() != ETemplateZoneType::WATER)
+		for (const auto& it : map->getZones())
+			if (it.second->getType() != ETemplateZoneType::WATER)
 				treasureZones.push_back(it.second);
 	}
 	auto grailZone = *RandomGeneratorUtil::nextItem(treasureZones, rand);

+ 21 - 0
lib/rmg/CRmgTemplate.cpp

@@ -136,6 +136,7 @@ ZoneOptions::ZoneOptions():
 	id(0),
 	type(ETemplateZoneType::PLAYER_START),
 	size(1),
+	maxTreasureValue(0),
 	owner(boost::none),
 	matchTerrainToTown(true),
 	townsAreSameType(false),
@@ -242,11 +243,22 @@ std::map<TResource, ui16> ZoneOptions::getMinesInfo() const
 void ZoneOptions::setTreasureInfo(const std::vector<CTreasureInfo> & value)
 {
 	treasureInfo = value;
+	recalculateMaxTreasureValue();
+}
+
+void ZoneOptions::recalculateMaxTreasureValue()
+{
+	maxTreasureValue = 0;
+	for (const auto& ti : treasureInfo)
+	{
+		vstd::amax(maxTreasureValue, ti.max);
+	}
 }
 	
 void ZoneOptions::addTreasureInfo(const CTreasureInfo & value)
 {
 	treasureInfo.push_back(value);
+	vstd::amax(maxTreasureValue, value.max);
 }
 
 const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const
@@ -254,6 +266,11 @@ const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const
 	return treasureInfo;
 }
 
+ui32 ZoneOptions::getMaxTreasureValue() const
+{
+	return maxTreasureValue;
+}
+
 TRmgTemplateZoneId ZoneOptions::getMinesLikeZone() const
 {
 	return minesLikeZone;
@@ -386,6 +403,10 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 	{
 		auto treasureData = handler.enterArray("treasure");
 		treasureData.serializeStruct(treasureInfo);
+		if (!handler.saving)
+		{
+			recalculateMaxTreasureValue();
+		}
 	}
 
 	if((minesLikeZone == NO_ZONE) && (!handler.saving || !mines.empty()))

+ 3 - 0
lib/rmg/CRmgTemplate.h

@@ -144,6 +144,8 @@ public:
 	void setTreasureInfo(const std::vector<CTreasureInfo> & value);
 	void addTreasureInfo(const CTreasureInfo & value);
 	const std::vector<CTreasureInfo> & getTreasureInfo() const;
+	ui32 getMaxTreasureValue() const;
+	void recalculateMaxTreasureValue();
 
 	TRmgTemplateZoneId getMinesLikeZone() const;
 	TRmgTemplateZoneId getTerrainTypeLikeZone() const;
@@ -163,6 +165,7 @@ protected:
 	TRmgTemplateZoneId id;
 	ETemplateZoneType::ETemplateZoneType type;
 	int size;
+	ui32 maxTreasureValue;
 	boost::optional<int> owner;
 	CTownInfo playerTowns;
 	CTownInfo neutralTowns;

+ 97 - 0
lib/rmg/MinePlacer.cpp

@@ -0,0 +1,97 @@
+/*
+* 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 "MinePlacer.h"
+#include "TownPlacer.h"
+#include "CMapGenerator.h"
+#include "RmgMap.h"
+#include "../mapping/CMap.h"
+#include "../mapping/CMapEditManager.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "../spells/CSpellHandler.h" //for choosing random spells
+#include "RmgPath.h"
+#include "RmgObject.h"
+#include "ObjectManager.h"
+#include "Functions.h"
+#include "RoadPlacer.h"
+#include "WaterAdopter.h"
+#include "TileInfo.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void MinePlacer::process()
+{
+	auto * manager = zone.getModificator<ObjectManager>();
+	if(!manager)
+	{
+		logGlobal->error("ObjectManager doesn't exist for zone %d, skip modificator %s", zone.getId(), getName());
+		return;
+	}
+
+	placeMines(*manager);
+}
+
+void MinePlacer::init()
+{
+	DEPENDENCY(TownPlacer);
+	POSTFUNCTION(ObjectManager);
+	POSTFUNCTION(RoadPlacer);
+}
+
+bool MinePlacer::placeMines(ObjectManager & manager)
+{
+	using namespace Res;
+	std::vector<CGMine*> createdMines;
+
+	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
+
+	for(const auto & mineInfo : zone.getMinesInfo())
+	{
+		ERes res = static_cast<ERes>(mineInfo.first);
+		for(int i = 0; i < mineInfo.second; ++i)
+		{
+			auto mineHandler = VLC->objtypeh->getHandlerFor(Obj::MINE, res);
+			const auto & rmginfo = mineHandler->getRMGInfo();
+			auto * mine = dynamic_cast<CGMine *>(mineHandler->create());
+			mine->producedResource = res;
+			mine->tempOwner = PlayerColor::NEUTRAL;
+			mine->producedQuantity = mine->defaultResProduction();
+			createdMines.push_back(mine);
+
+
+			if(!i && (res == ERes::WOOD || res == ERes::ORE))
+				manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
+			else
+				requiredObjects.push_back(std::pair<CGObjectInstance*, ui32>(mine, rmginfo.value));
+		}
+	}
+
+	//Shuffle mines to avoid patterns, but don't shuffle key objects like towns
+	RandomGeneratorUtil::randomShuffle(requiredObjects, generator.rand);
+	for (const auto& obj : requiredObjects)
+	{
+		manager.addRequiredObject(obj.first, obj.second);
+	}
+
+	//create extra resources
+	if(int extraRes = generator.getConfig().mineExtraResources)
+	{
+		for(auto * mine : createdMines)
+		{
+			for(int rc = generator.rand.nextInt(1, extraRes); rc > 0; --rc)
+			{
+				auto * resourse = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
+				resourse->amount = CGResource::RANDOM_AMOUNT;
+				manager.addNearbyObject(resourse, mine);
+			}
+		}
+	}
+
+	return true;
+}
+
+VCMI_LIB_NAMESPACE_END

+ 30 - 0
lib/rmg/MinePlacer.h

@@ -0,0 +1,30 @@
+/*
+* MinePlacer.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 "Zone.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class ObjectManager;
+
+class MinePlacer: public Modificator
+{
+public:
+	MODIFICATOR(MinePlacer);
+
+	void process() override;
+	void init() override;
+
+protected:
+	bool placeMines(ObjectManager & manager);
+
+};
+
+VCMI_LIB_NAMESPACE_END

+ 121 - 0
lib/rmg/ObjectDistributor.cpp

@@ -0,0 +1,121 @@
+/*
+* ObjectDistributor.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 "ObjectDistributor.h"
+
+#include "../VCMI_Lib.h"
+#include "RmgMap.h"
+#include "CMapGenerator.h"
+#include "TreasurePlacer.h"
+#include "TownPlacer.h"
+#include "TerrainPainter.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "../mapObjects/MapObjects.h"
+#include "Functions.h"
+#include "RmgObject.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void ObjectDistributor::process()
+{
+	//Firts call will add objects to ALL zones, once they were added skip it
+	if (zone.getModificator<TreasurePlacer>()->getPossibleObjectsSize() == 0)
+	{
+		ObjectDistributor::distributeLimitedObjects();
+	}
+}
+
+void ObjectDistributor::init()
+{
+	DEPENDENCY(TownPlacer);
+	DEPENDENCY(TerrainPainter);
+	POSTFUNCTION(TreasurePlacer);
+}
+
+void ObjectDistributor::distributeLimitedObjects()
+{
+	//FIXME: Must be called after TerrainPainter::process()
+
+	ObjectInfo oi;
+	auto zones = map.getZones();
+
+	for (auto primaryID : VLC->objtypeh->knownObjects())
+	{
+		for (auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
+		{
+			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
+			if (!handler->isStaticObject() && handler->getRMGInfo().value)
+			{
+				auto rmgInfo = handler->getRMGInfo();
+
+				//Skip objects which don't have global per-map limit here 
+				if (rmgInfo.mapLimit)
+				{
+					//Count all zones where this object can be placed
+					std::vector<std::shared_ptr<Zone>> matchingZones;
+
+					for (const auto& it : zones)
+					{
+						if (!handler->getTemplates(it.second->getTerrainType()).empty() &&
+							rmgInfo.value <= it.second->getMaxTreasureValue())
+						{
+							matchingZones.push_back(it.second);
+						}
+					}
+
+					size_t numZones = matchingZones.size();
+					if (!numZones)
+						continue;
+
+					auto rmgInfo = handler->getRMGInfo();
+
+					for (auto& zone : matchingZones)
+					{
+						//We already know there are some templates
+						auto templates = handler->getTemplates(zone->getTerrainType());
+
+						//Assume the template with fewest terrains is the most suitable
+						auto temp = *boost::min_element(templates, [](std::shared_ptr<const ObjectTemplate> lhs, std::shared_ptr<const ObjectTemplate> rhs) -> bool
+						{
+							return lhs->getAllowedTerrains().size() < rhs->getAllowedTerrains().size();
+						});
+
+						oi.generateObject = [temp]() -> CGObjectInstance *
+						{
+							return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
+						};
+						
+						oi.value = rmgInfo.value;
+						oi.probability = rmgInfo.rarity;
+						oi.templ = temp;
+
+						//Rounding up will make sure all possible objects are exhausted
+						uint32_t mapLimit = rmgInfo.mapLimit.get();
+						uint32_t maxPerZone = std::ceil(float(mapLimit) / numZones);
+
+						//But not more than zone limit
+						oi.maxPerZone = std::min(maxPerZone, rmgInfo.zoneLimit);
+						numZones--;
+
+						rmgInfo.setMapLimit(mapLimit - oi.maxPerZone);
+						//Don't add objects with 0 count remaining
+						if (oi.maxPerZone)
+						{
+							zone->getModificator<TreasurePlacer>()->addObjectToRandomPool(oi);
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+VCMI_LIB_NAMESPACE_END

+ 32 - 0
lib/rmg/ObjectDistributor.h

@@ -0,0 +1,32 @@
+/*
+* ObjectDistributor.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 "Zone.h"
+#include "RmgObject.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CGObjectInstance;
+class ObjectTemplate;
+
+class ObjectDistributor : public Modificator
+{
+	void distributeLimitedObjects();
+
+public:
+	MODIFICATOR(ObjectDistributor);
+
+	void process() override;
+	void init() override;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 2
lib/rmg/ObjectManager.cpp

@@ -236,8 +236,7 @@ rmg::Path ObjectManager::placeAndConnectObject(const rmg::Area & searchArea, rmg
 bool ObjectManager::createRequiredObjects()
 {
 	logGlobal->trace("Creating required objects");
-	
-	RandomGeneratorUtil::randomShuffle(requiredObjects, generator.rand);	
+		
 	for(const auto & object : requiredObjects)
 	{
 		auto * obj = object.first;

+ 4 - 0
lib/rmg/RmgMap.cpp

@@ -21,6 +21,8 @@
 #include "TreasurePlacer.h"
 #include "ConnectionsPlacer.h"
 #include "TownPlacer.h"
+#include "MinePlacer.h"
+#include "ObjectDistributor.h"
 #include "WaterAdopter.h"
 #include "WaterProxy.h"
 #include "WaterRoutes.h"
@@ -118,6 +120,7 @@ void RmgMap::addModificators()
 		auto zone = z.second;
 		
 		zone->addModificator<ObjectManager>();
+		zone->addModificator<ObjectDistributor>();
 		zone->addModificator<TreasurePlacer>();
 		zone->addModificator<ObstaclePlacer>();
 		zone->addModificator<TerrainPainter>();
@@ -135,6 +138,7 @@ void RmgMap::addModificators()
 		else
 		{
 			zone->addModificator<TownPlacer>();
+			zone->addModificator<MinePlacer>();
 			zone->addModificator<ConnectionsPlacer>();
 			zone->addModificator<RoadPlacer>();
 			zone->addModificator<RiverPlacer>();

+ 3 - 47
lib/rmg/TownPlacer.cpp

@@ -21,6 +21,7 @@
 #include "ObjectManager.h"
 #include "Functions.h"
 #include "RoadPlacer.h"
+#include "MinePlacer.h"
 #include "WaterAdopter.h"
 #include "TileInfo.h"
 
@@ -35,14 +36,12 @@ void TownPlacer::process()
 		return;
 	}
 	
-	
 	placeTowns(*manager);
-	placeMines(*manager);
 }
 
 void TownPlacer::init()
 {
-	POSTFUNCTION(ObjectManager);
+	POSTFUNCTION(MinePlacer);
 	POSTFUNCTION(RoadPlacer);
 } 
 
@@ -146,56 +145,13 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town)
 		float distance = zone.getPos().dist2dSQ(t);
 		return 100000.f - distance; //some big number
 	}, ObjectManager::OptimizeType::WEIGHT);
-	rmgObject.setPosition(position);
+	rmgObject.setPosition(position + int3(2, 2, 0)); //place visitable tile in the exact center of a zone
 	manager.placeObject(rmgObject, false, true);
 	cleanupBoundaries(rmgObject);
 	zone.setPos(rmgObject.getVisitablePosition()); //roads lead to main town
 	return position;
 }
 
-bool TownPlacer::placeMines(ObjectManager & manager)
-{
-	using namespace Res;
-	std::vector<CGMine*> createdMines;
-	
-	for(const auto & mineInfo : zone.getMinesInfo())
-	{
-		ERes res = static_cast<ERes>(mineInfo.first);
-		for(int i = 0; i < mineInfo.second; ++i)
-		{
-			auto mineHandler = VLC->objtypeh->getHandlerFor(Obj::MINE, res);
-			const auto & rmginfo = mineHandler->getRMGInfo();
-			auto * mine = dynamic_cast<CGMine *>(mineHandler->create());
-			mine->producedResource = res;
-			mine->tempOwner = PlayerColor::NEUTRAL;
-			mine->producedQuantity = mine->defaultResProduction();
-			createdMines.push_back(mine);
-			
-			
-			if(!i && (res == ERes::WOOD || res == ERes::ORE))
-				manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
-			else
-				manager.addRequiredObject(mine, rmginfo.value);
-		}
-	}
-	
-	//create extra resources
-	if(int extraRes = generator.getConfig().mineExtraResources)
-	{
-		for(auto * mine : createdMines)
-		{
-			for(int rc = generator.rand.nextInt(1, extraRes); rc > 0; --rc)
-			{
-				auto * resourse = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
-				resourse->amount = CGResource::RANDOM_AMOUNT;
-				manager.addNearbyObject(resourse, mine);
-			}
-		}
-	}
-	
-	return true;
-}
-
 void TownPlacer::cleanupBoundaries(const rmg::Object & rmgObject)
 {
 	for(const auto & t : rmgObject.getArea().getBorderOutside())

+ 49 - 32
lib/rmg/TreasurePlacer.cpp

@@ -46,12 +46,15 @@ void TreasurePlacer::setQuestArtZone(Zone * otherZone)
 	questArtZone = otherZone;
 }
 
+void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
+{
+	possibleObjects.push_back(oi);
+}
+
 void TreasurePlacer::addAllPossibleObjects()
 {
 	ObjectInfo oi;
 	
-	int numZones = static_cast<int>(map.getZones().size());
-	
 	for(auto primaryID : VLC->objtypeh->knownObjects())
 	{
 		for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
@@ -59,23 +62,32 @@ void TreasurePlacer::addAllPossibleObjects()
 			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
 			if(!handler->isStaticObject() && handler->getRMGInfo().value)
 			{
-				for(const auto & temp : handler->getTemplates())
+				auto rmgInfo = handler->getRMGInfo();
+				if (rmgInfo.mapLimit || rmgInfo.value > zone.getMaxTreasureValue())
 				{
-					if(temp->canBePlacedAt(zone.getTerrainType()))
-					{
-						oi.generateObject = [temp]() -> CGObjectInstance *
-						{
-							return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
-						};
-						auto rmgInfo = handler->getRMGInfo();
-						oi.value = rmgInfo.value;
-						oi.probability = rmgInfo.rarity;
-						oi.templ = temp;
-						oi.maxPerZone = rmgInfo.zoneLimit;
-						vstd::amin(oi.maxPerZone, rmgInfo.mapLimit / numZones); //simple, but should distribute objects evenly on large maps
-						possibleObjects.push_back(oi);
-					}
+					//Skip objects with per-map limit here
+					continue;
 				}
+
+				auto templates = handler->getTemplates(zone.getTerrainType());
+				if (templates.empty())
+					continue;
+
+				//Assume the template with fewest terrains is the most suitable
+				auto temp = *boost::min_element(templates, [](std::shared_ptr<const ObjectTemplate> lhs, std::shared_ptr<const ObjectTemplate> rhs) -> bool
+				{
+					return lhs->getAllowedTerrains().size() < rhs->getAllowedTerrains().size();
+				});
+
+				oi.generateObject = [temp]() -> CGObjectInstance *
+				{
+					return VLC->objtypeh->getHandlerFor(temp->id, temp->subid)->create(temp);
+				};
+				oi.value = rmgInfo.value;
+				oi.probability = rmgInfo.rarity;
+				oi.templ = temp;
+				oi.maxPerZone = rmgInfo.zoneLimit;
+				addObjectToRandomPool(oi);
 			}
 		}
 	}
@@ -114,7 +126,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.value = generator.getConfig().prisonValues[i];
 		oi.probability = 30;
 		oi.maxPerZone = generator.getPrisonsRemaning() / 5; //probably not perfect, but we can't generate more prisons than hereos.
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	//all following objects are unlimited
@@ -170,7 +182,7 @@ void TreasurePlacer::addAllPossibleObjects()
 						};
 
 						oi.templ = tmplate;
-						possibleObjects.push_back(oi);
+						addObjectToRandomPool(oi);
 					}
 				}
 			}
@@ -199,7 +211,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.setTemplate(Obj::SPELL_SCROLL, 0, zone.getTerrainType());
 		oi.value = generator.getConfig().scrollValues[i];
 		oi.probability = 30;
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	//pandora box with gold
@@ -215,7 +227,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
 		oi.value = i * generator.getConfig().pandoraMultiplierGold;
 		oi.probability = 5;
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	//pandora box with experience
@@ -231,7 +243,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
 		oi.value = i * generator.getConfig().pandoraMultiplierExperience;
 		oi.probability = 20;
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	//pandora box with creatures
@@ -284,7 +296,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
 		oi.value = static_cast<ui32>((2 * (creature->AIValue) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) / 3);
 		oi.probability = 3;
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	//Pandora with 12 spells of certain level
@@ -313,7 +325,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
 		oi.value = (i + 1) * generator.getConfig().pandoraMultiplierSpells; //5000 - 15000
 		oi.probability = 2;
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	//Pandora with 15 spells of certain school
@@ -342,7 +354,7 @@ void TreasurePlacer::addAllPossibleObjects()
 		oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
 		oi.value = generator.getConfig().pandoraSpellSchool;
 		oi.probability = 2;
-		possibleObjects.push_back(oi);
+		addObjectToRandomPool(oi);
 	}
 	
 	// Pandora box with 60 random spells
@@ -370,7 +382,7 @@ void TreasurePlacer::addAllPossibleObjects()
 	oi.setTemplate(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
 	oi.value = generator.getConfig().pandoraSpell60;
 	oi.probability = 2;
-	possibleObjects.push_back(oi);
+	addObjectToRandomPool(oi);
 	
 	//seer huts with creatures or generic rewards
 	
@@ -436,14 +448,14 @@ void TreasurePlacer::addAllPossibleObjects()
 				generator.banQuestArt(artid);
 				
 				
-				this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
+				this->questArtZone->getModificator<TreasurePlacer>()->addObjectToRandomPool(generateArtInfo(artid));
 				
 				return obj;
 			};
 			oi.setTemplate(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
 			oi.value = static_cast<ui32>(((2 * (creature->AIValue) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->faction)) / map.getTotalZoneCount())) - 4000) / 3);
 			oi.probability = 3;
-			possibleObjects.push_back(oi);
+			addObjectToRandomPool(oi);
 		}
 		
 		static int seerLevels = std::min(generator.getConfig().questValues.size(), generator.getConfig().questRewardValues.size());
@@ -472,12 +484,12 @@ void TreasurePlacer::addAllPossibleObjects()
 				
 				generator.banQuestArt(artid);
 				
-				this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
+				this->questArtZone->getModificator<TreasurePlacer>()->addObjectToRandomPool(generateArtInfo(artid));
 				
 				return obj;
 			};
 			
-			possibleObjects.push_back(oi);
+			addObjectToRandomPool(oi);
 			
 			oi.generateObject = [i, randomAppearance, this, generateArtInfo]() -> CGObjectInstance *
 			{
@@ -495,16 +507,21 @@ void TreasurePlacer::addAllPossibleObjects()
 				
 				generator.banQuestArt(artid);
 				
-				this->questArtZone->getModificator<TreasurePlacer>()->possibleObjects.push_back(generateArtInfo(artid));
+				this->questArtZone->getModificator<TreasurePlacer>()->addObjectToRandomPool(generateArtInfo(artid));
 				
 				return obj;
 			};
 			
-			possibleObjects.push_back(oi);
+			addObjectToRandomPool(oi);
 		}
 	}
 }
 
+size_t TreasurePlacer::getPossibleObjectsSize() const
+{
+	return possibleObjects.size();
+}
+
 bool TreasurePlacer::isGuardNeededForTreasure(int value)
 {
 	return zone.getType() != ETemplateZoneType::WATER && value > minGuardedValue;

+ 3 - 0
lib/rmg/TreasurePlacer.h

@@ -45,7 +45,10 @@ public:
 	void createTreasures(ObjectManager & manager);
 	
 	void setQuestArtZone(Zone * otherZone);
+	void addObjectToRandomPool(const ObjectInfo& oi);
 	void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
+
+	size_t getPossibleObjectsSize() const;
 	
 protected:
 	bool isGuardNeededForTreasure(int value);