Forráskód Böngészése

Distribute limited objects evenly in zones with matching terrain

Tomasz Zieliński 2 éve
szülő
commit
18a87d1ec0

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -101,6 +101,7 @@ 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
@@ -338,6 +339,7 @@ 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

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

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

+ 116 - 0
lib/rmg/ObjectDistributor.cpp

@@ -0,0 +1,116 @@
+/*
+* 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;
+
+					//TODO: Are all terrains initialized at this point? Including water?
+					for (const auto& it : zones)
+					{
+						if (!handler->getTemplates(it.second->getTerrainType()).empty())
+						{
+							matchingZones.push_back(it.second);
+						}
+					}
+
+					//TODO: Also check if the object value is within zone max value
+
+					size_t numZones = matchingZones.size();
+					if (!numZones)
+						continue;
+
+					auto rmgInfo = handler->getRMGInfo();
+
+					for (auto& zone : matchingZones)
+					{
+						auto temp = handler->getTemplates(zone->getTerrainType()).front();
+						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

+ 2 - 0
lib/rmg/RmgMap.cpp

@@ -21,6 +21,7 @@
 #include "TreasurePlacer.h"
 #include "ConnectionsPlacer.h"
 #include "TownPlacer.h"
+#include "ObjectDistributor.h"
 #include "WaterAdopter.h"
 #include "WaterProxy.h"
 #include "WaterRoutes.h"
@@ -118,6 +119,7 @@ void RmgMap::addModificators()
 		auto zone = z.second;
 		
 		zone->addModificator<ObjectManager>();
+		zone->addModificator<ObjectDistributor>();
 		zone->addModificator<TreasurePlacer>();
 		zone->addModificator<ObstaclePlacer>();
 		zone->addModificator<TerrainPainter>();

+ 34 - 19
lib/rmg/TreasurePlacer.cpp

@@ -46,6 +46,11 @@ void TreasurePlacer::setQuestArtZone(Zone * otherZone)
 	questArtZone = otherZone;
 }
 
+void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
+{
+	possibleObjects.push_back(oi);
+}
+
 void TreasurePlacer::addAllPossibleObjects()
 {
 	ObjectInfo oi;
@@ -59,6 +64,14 @@ void TreasurePlacer::addAllPossibleObjects()
 			auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
 			if(!handler->isStaticObject() && handler->getRMGInfo().value)
 			{
+				auto rmgInfo = handler->getRMGInfo();
+				if (rmgInfo.mapLimit)
+				{
+					//Skip objects with per-map limit here
+					continue;
+				}
+
+				//TODO: Also check if the object value is within zone max value
 				for(const auto & temp : handler->getTemplates())
 				{
 					if(temp->canBePlacedAt(zone.getTerrainType()))
@@ -67,15 +80,12 @@ void TreasurePlacer::addAllPossibleObjects()
 						{
 							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);
+						addObjectToRandomPool(oi);
 
-						break; //Place only one template per zone
 					}
 				}
 			}
@@ -116,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
@@ -172,7 +182,7 @@ void TreasurePlacer::addAllPossibleObjects()
 						};
 
 						oi.templ = tmplate;
-						possibleObjects.push_back(oi);
+						addObjectToRandomPool(oi);
 					}
 				}
 			}
@@ -201,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
@@ -217,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
@@ -233,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
@@ -286,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
@@ -315,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
@@ -344,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
@@ -372,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
 	
@@ -438,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());
@@ -474,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 *
 			{
@@ -497,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);