Browse Source

Place proper towns in underground (#743)

Implement feature of proper town selection in underground and surface
* Some minor refactoring of rmg
Nordsoft91 3 years ago
parent
commit
9d06e51631

+ 1 - 0
config/factions/dungeon.json

@@ -4,6 +4,7 @@
 		"index" : 5,
 		"nativeTerrain": "subterra",
 		"alignment" : "evil",
+		"preferUndergroundPlacement": true,
 		"creatureBackground" :
 		{
 			"120px" : "TPCASDUN",

+ 1 - 0
config/factions/inferno.json

@@ -4,6 +4,7 @@
 		"index" : 3,
 		"nativeTerrain": "lava",
 		"alignment" : "evil",
+		"preferUndergroundPlacement": true,
 		"creatureBackground" :
 		{
 			"120px" : "TPCASINF",

+ 1 - 0
config/factions/necropolis.json

@@ -4,6 +4,7 @@
 		"index" : 4,
 		"nativeTerrain": "dirt",
 		"alignment" : "evil",
+		"preferUndergroundPlacement": true,
 		"creatureBackground" :
 		{
 			"120px" : "TPCASNEC",

+ 4 - 0
config/schemas/faction.json

@@ -73,6 +73,10 @@
 			"type":"string",
 			"description": "Native terrain for creatures. Creatures fighting on native terrain receive several bonuses"
 		},
+		"preferUndergroundPlacement": {
+			"type":"bool",
+			"description": "Random map generator places player/cpu-owned towns underground if true is specified and on the ground otherwise. Parameter is unused for maps without underground. False by default."
+		},
 		"puzzleMap": {
 			"type":"object",
 			"additionalProperties" : false,

+ 2 - 2
lib/CGameState.cpp

@@ -853,9 +853,9 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
 		CStopWatch sw;
 
 		// Gen map
-		CMapGenerator mapGenerator;
+		CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed);
 
-		std::unique_ptr<CMap> randomMap = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed);
+		std::unique_ptr<CMap> randomMap = mapGenerator.generate();
 
 		if(allowSavingRandomMap)
 		{

+ 4 - 0
lib/CTownHandler.cpp

@@ -211,6 +211,7 @@ CFaction::CFaction()
 	town = nullptr;
 	index = 0;
 	alignment = EAlignment::NEUTRAL;
+	preferUndergroundPlacement = false;
 }
 
 CFaction::~CFaction()
@@ -1099,6 +1100,9 @@ CFaction * CTownHandler::loadFromJson(const std::string & scope, const JsonNode
 	int terrainNum = nativeTerrain.isNull()
 		? -1
 		: vstd::find_pos(GameConstants::TERRAIN_NAMES, nativeTerrain.String());
+	
+	auto preferUndergound = source["preferUndergroundPlacement"];
+	faction->preferUndergroundPlacement = preferUndergound.isNull() ? false : preferUndergound.Bool();
 
 	//Contructor is not called here, but operator=
 	faction->nativeTerrain = terrainNum < 0

+ 3 - 2
lib/CTownHandler.h

@@ -207,6 +207,7 @@ public:
 
 	ETerrainType nativeTerrain;
 	EAlignment::EAlignment alignment;
+	bool preferUndergroundPlacement;
 
 	CTown * town; //NOTE: can be null
 
@@ -399,8 +400,8 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 	void loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source);
 	void loadBuildings(CTown * town, const JsonNode & source);
 
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1);
-	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1);
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, int subtype = -1);
+	std::shared_ptr<Bonus> createBonus(CBuilding * build, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype = -1);
 	std::shared_ptr<Bonus> createBonusImpl(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, const std::string & description, int subtype = -1);
 
 	/// loads CStructure's into town

+ 6 - 55
lib/rmg/CMapGenOptions.cpp

@@ -205,11 +205,6 @@ void CMapGenOptions::setMapTemplate(const CRmgTemplate * value)
 	assert(0);
 }
 
-const std::map<std::string, CRmgTemplate *> & CMapGenOptions::getAvailableTemplates() const
-{
-	return VLC->tplh->getTemplates();
-}
-
 void CMapGenOptions::finalize(CRandomGenerator & rand)
 {
 	logGlobal->info("RMG settings: players %d, teams %d, computer players %d, computer teams %d, water %d, monsters %d",
@@ -394,59 +389,15 @@ bool CMapGenOptions::checkOptions() const
 
 const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand) const
 {
-	// Find potential templates
-	const auto & tpls = getAvailableTemplates();
-	std::list<const CRmgTemplate *> potentialTpls;
-	for(const auto & tplPair : tpls)
-	{
-		const auto & tpl = tplPair.second;
-		int3 tplSize(width, height, (hasTwoLevels ? 2 : 1));
-		if(tpl->matchesSize(tplSize))
-		{
-			bool isPlayerCountValid = false;
-			if (getPlayerCount() != RANDOM_SIZE)
-			{
-				if (tpl->getPlayers().isInRange(getPlayerCount()))
-					isPlayerCountValid = true;
-			}
-			else
-			{
-				// Human players shouldn't be banned when playing with random player count
-				auto playerNumbers = tpl->getPlayers().getNumbers();
-				if(countHumanPlayers() <= *boost::min_element(playerNumbers))
-				{
-					isPlayerCountValid = true;
-				}
-			}
-
-			if (isPlayerCountValid)
-			{
-				bool isCpuPlayerCountValid = false;
-				if(compOnlyPlayerCount != RANDOM_SIZE)
-				{
-					if (tpl->getCpuPlayers().isInRange(compOnlyPlayerCount))
-						isCpuPlayerCountValid = true;
-				}
-				else
-				{
-					isCpuPlayerCountValid = true;
-				}
-
-				if(isCpuPlayerCountValid)
-					potentialTpls.push_back(tpl);
-			}
-		}
-	}
+	int3 tplSize(width, height, (hasTwoLevels ? 2 : 1));
+	
+	auto templates = VLC->tplh->getTemplates(tplSize, getPlayerCount(), countHumanPlayers(), compOnlyPlayerCount);
 
 	// Select tpl
-	if(potentialTpls.empty())
-	{
+	if(templates.empty())
 		return nullptr;
-	}
-	else
-	{
-		return *RandomGeneratorUtil::nextItem(potentialTpls, rand);
-	}
+	
+	return *RandomGeneratorUtil::nextItem(templates, rand);
 }
 
 CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI)

+ 1 - 2
lib/rmg/CMapGenOptions.h

@@ -94,6 +94,7 @@ public:
 	};
 
 	CMapGenOptions();
+	CMapGenOptions(const CMapGenOptions&) = delete;
 
 	si32 getWidth() const;
 	void setWidth(si32 value);
@@ -141,8 +142,6 @@ public:
 	const CRmgTemplate * getMapTemplate() const;
 	void setMapTemplate(const CRmgTemplate * value);
 
-	const std::map<std::string, CRmgTemplate *> & getAvailableTemplates() const;
-
 	/// Finalizes the options. All random sizes for various properties will be overwritten by numbers from
 	/// a random number generator by keeping the options in a valid state. Check options should return true, otherwise
 	/// this function fails.

+ 55 - 53
lib/rmg/CMapGenerator.cpp

@@ -46,7 +46,7 @@ void CMapGenerator::foreachDirectNeighbour(const int3& pos, std::function<void(i
 	}
 }
 
-void CMapGenerator::foreachDiagonaltNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
+void CMapGenerator::foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
 {
 	for (const int3 &dir : dirsDiagonal)
 	{
@@ -57,11 +57,13 @@ void CMapGenerator::foreachDiagonaltNeighbour(const int3& pos, std::function<voi
 }
 
 
-CMapGenerator::CMapGenerator() :
-	mapGenOptions(nullptr), randomSeed(0), editManager(nullptr),
+CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) :
+	mapGenOptions(mapGenOptions), randomSeed(RandomSeed),
 	zonesTotal(0), tiles(nullptr), prisonsRemaining(0),
     monolithIndex(0)
 {
+	rand.setSeed(this->randomSeed);
+	mapGenOptions.finalize(rand);
 }
 
 void CMapGenerator::initTiles()
@@ -89,8 +91,8 @@ CMapGenerator::~CMapGenerator()
 {
 	if (tiles)
 	{
-		int width = mapGenOptions->getWidth();
-		int height = mapGenOptions->getHeight();
+		int width = mapGenOptions.getWidth();
+		int height = mapGenOptions.getHeight();
 		for (int i=0; i < width; i++)
 		{
 			for(int j=0; j < height; j++)
@@ -111,7 +113,7 @@ void CMapGenerator::initPrisonsRemaining()
 		if (isAllowed)
 			prisonsRemaining++;
 	}
-	prisonsRemaining = std::max<int> (0, prisonsRemaining - 16 * mapGenOptions->getPlayerCount()); //so at least 16 heroes will be available for every player
+	prisonsRemaining = std::max<int> (0, prisonsRemaining - 16 * mapGenOptions.getPlayerCount()); //so at least 16 heroes will be available for every player
 }
 
 void CMapGenerator::initQuestArtsRemaining()
@@ -123,26 +125,27 @@ void CMapGenerator::initQuestArtsRemaining()
 	}
 }
 
-std::unique_ptr<CMap> CMapGenerator::generate(CMapGenOptions * mapGenOptions, int randomSeed)
+const CMapGenOptions& CMapGenerator::getMapGenOptions() const
 {
-	this->mapGenOptions = mapGenOptions;
-	this->randomSeed = randomSeed;
-
-	assert(mapGenOptions);
+	return mapGenOptions;
+}
 
-	rand.setSeed(this->randomSeed);
-	mapGenOptions->finalize(rand);
+CMapEditManager* CMapGenerator::getEditManager() const
+{
+	if(!map)
+		return nullptr;
+	return map->getEditManager();
+}
 
+std::unique_ptr<CMap> CMapGenerator::generate()
+{
 	map = make_unique<CMap>();
-	editManager = map->getEditManager();
-
 	try
 	{
-		editManager->getUndoManager().setUndoRedoLimit(0);
+		map->getEditManager()->getUndoManager().setUndoRedoLimit(0);
 		//FIXME:  somehow mapGenOption is nullptr at this point :?
 		addHeaderInfo();
 		initTiles();
-
 		initPrisonsRemaining();
 		initQuestArtsRemaining();
 		genZones();
@@ -160,14 +163,13 @@ std::unique_ptr<CMap> CMapGenerator::generate(CMapGenOptions * mapGenOptions, in
 
 std::string CMapGenerator::getMapDescription() const
 {
-	assert(mapGenOptions);
 	assert(map);
 
 	const std::string waterContentStr[3] = { "none", "normal", "islands" };
 	const std::string monsterStrengthStr[3] = { "weak", "normal", "strong" };
 
-	int monsterStrengthIndex = mapGenOptions->getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0
-	const auto * mapTemplate = mapGenOptions->getMapTemplate();
+	int monsterStrengthIndex = mapGenOptions.getMonsterStrength() - EMonsterStrength::GLOBAL_WEAK; //does not start from 0
+	const auto * mapTemplate = mapGenOptions.getMapTemplate();
 
 	if(!mapTemplate)
 		throw rmgException("Map template for Random Map Generator is not found. Could not start the game.");
@@ -175,11 +177,11 @@ std::string CMapGenerator::getMapDescription() const
     std::stringstream ss;
     ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") +
         ", levels %s, players %d, computers %d, water %s, monster %s, VCMI map") % mapTemplate->getName() %
-		randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions->getPlayerCount()) %
-		static_cast<int>(mapGenOptions->getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions->getWaterContent()] %
+		randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions.getPlayerCount()) %
+		static_cast<int>(mapGenOptions.getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions.getWaterContent()] %
 		monsterStrengthStr[monsterStrengthIndex]);
 
-	for(const auto & pair : mapGenOptions->getPlayersSettings())
+	for(const auto & pair : mapGenOptions.getPlayersSettings())
 	{
 		const auto & pSettings = pair.second;
 		if(pSettings.getPlayerType() == EPlayerType::HUMAN)
@@ -211,13 +213,13 @@ void CMapGenerator::addPlayerInfo()
 	{
 		if (i == CPHUMAN)
 		{
-			playerCount = mapGenOptions->getPlayerCount();
-			teamCount = mapGenOptions->getTeamCount();
+			playerCount = mapGenOptions.getPlayerCount();
+			teamCount = mapGenOptions.getTeamCount();
 		}
 		else
 		{
-			playerCount = mapGenOptions->getCompOnlyPlayerCount();
-			teamCount = mapGenOptions->getCompOnlyTeamCount();
+			playerCount = mapGenOptions.getCompOnlyPlayerCount();
+			teamCount = mapGenOptions.getCompOnlyTeamCount();
 		}
 
 		if(playerCount == 0)
@@ -246,7 +248,7 @@ void CMapGenerator::addPlayerInfo()
 
 	// Team numbers are assigned randomly to every player
 	//TODO: allow customize teams in rmg template
-	for(const auto & pair : mapGenOptions->getPlayersSettings())
+	for(const auto & pair : mapGenOptions.getPlayersSettings())
 	{
 		const auto & pSettings = pair.second;
 		PlayerInfo player;
@@ -262,36 +264,34 @@ void CMapGenerator::addPlayerInfo()
 			logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human"));
 			assert (teamNumbers[j].size());
 		}
-        auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
+		auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
 		player.team = TeamID(*itTeam);
 		teamNumbers[j].erase(itTeam);
 		map->players[pSettings.getColor().getNum()] = player;
 	}
 
-	map->howManyTeams = (mapGenOptions->getTeamCount() == 0 ? mapGenOptions->getPlayerCount() : mapGenOptions->getTeamCount())
-			+ (mapGenOptions->getCompOnlyTeamCount() == 0 ? mapGenOptions->getCompOnlyPlayerCount() : mapGenOptions->getCompOnlyTeamCount());
+	map->howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount())
+			+ (mapGenOptions.getCompOnlyTeamCount() == 0 ? mapGenOptions.getCompOnlyPlayerCount() : mapGenOptions.getCompOnlyTeamCount());
 }
 
 void CMapGenerator::genZones()
 {
-	editManager->clearTerrain(&rand);
-	editManager->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions->getWidth(), mapGenOptions->getHeight()));
-	editManager->drawTerrain(ETerrainType::GRASS, &rand);
+	getEditManager()->clearTerrain(&rand);
+	getEditManager()->getTerrainSelection().selectRange(MapRect(int3(0, 0, 0), mapGenOptions.getWidth(), mapGenOptions.getHeight()));
+	getEditManager()->drawTerrain(ETerrainType::GRASS, &rand);
 
-	auto tmpl = mapGenOptions->getMapTemplate();
+	auto tmpl = mapGenOptions.getMapTemplate();
 	zones.clear();
 	for(const auto & option : tmpl->getZones())
 	{
-		auto zone = std::make_shared<CRmgTemplateZone>();
+		auto zone = std::make_shared<CRmgTemplateZone>(this);
 		zone->setOptions(option.second.get());
 		zones[zone->getId()] = zone;
-		//todo: move to CRmgTemplateZone constructor
-		zone->setGenPtr(this);//immediately set gen pointer before taking any actions on zones
 	}
 
 	CZonePlacer placer(this);
-	placer.placeZones(mapGenOptions, &rand);
-	placer.assignZones(mapGenOptions);
+	placer.placeZones(&rand);
+	placer.assignZones();
 
 	logGlobal->info("Zones generated successfully");
 }
@@ -310,18 +310,19 @@ void CMapGenerator::fillZones()
 	//place main town in the middle
 	for (auto it : zones)
 		it.second->initTownType();
-
+	
 	//make sure there are some free tiles in the zone
 	for (auto it : zones)
 		it.second->initFreeTiles();
-
+	
 	createDirectConnections(); //direct
+	
 	//make sure all connections are passable before creating borders
 	for (auto it : zones)
 		it.second->createBorder(); //once direct connections are done
-
+	
 	createConnections2(); //subterranean gates and monoliths
-
+	
 	std::vector<std::shared_ptr<CRmgTemplateZone>> treasureZones;
 	for (auto it : zones)
 	{
@@ -329,12 +330,13 @@ void CMapGenerator::fillZones()
 		if (it.second->getType() == ETemplateZoneType::TREASURE)
 			treasureZones.push_back(it.second);
 	}
-
+	
 	//set apriopriate free/occupied tiles, including blocked underground rock
 	createObstaclesCommon1();
 	//set back original terrain for underground zones
 	for (auto it : zones)
 		it.second->createObstacles1();
+	
 	createObstaclesCommon2();
 	//place actual obstacles matching zone terrain
 	for (auto it : zones)
@@ -345,7 +347,7 @@ void CMapGenerator::fillZones()
 	#define PRINT_MAP_BEFORE_ROADS false
 	if (PRINT_MAP_BEFORE_ROADS) //enable to debug
 	{
-		std::ofstream out("road debug");
+		std::ofstream out("road_debug.txt");
 		int levels = map->twoLevel ? 2 : 1;
 		int width = map->width;
 		int height = map->height;
@@ -413,8 +415,8 @@ void CMapGenerator::createObstaclesCommon1()
 				}
 			}
 		}
-		editManager->getTerrainSelection().setSelection(rockTiles);
-		editManager->drawTerrain(ETerrainType::ROCK, &rand);
+		getEditManager()->getTerrainSelection().setSelection(rockTiles);
+		getEditManager()->drawTerrain(ETerrainType::ROCK, &rand);
 	}
 }
 
@@ -483,7 +485,7 @@ void CMapGenerator::findZonesForQuestArts()
 {
 	//we want to place arties in zones that were not yet filled (higher index)
 
-	for (auto connection : mapGenOptions->getMapTemplate()->getConnections())
+	for (auto connection : mapGenOptions.getMapTemplate()->getConnections())
 	{
 		auto zoneA = zones[connection.getZoneA()];
 		auto zoneB = zones[connection.getZoneB()];
@@ -501,7 +503,7 @@ void CMapGenerator::findZonesForQuestArts()
 
 void CMapGenerator::createDirectConnections()
 {
-	for (auto connection : mapGenOptions->getMapTemplate()->getConnections())
+	for (auto connection : mapGenOptions.getMapTemplate()->getConnections())
 	{
 		auto zoneA = zones[connection.getZoneA()];
 		auto zoneB = zones[connection.getZoneB()];
@@ -703,9 +705,9 @@ void CMapGenerator::createConnections2()
 void CMapGenerator::addHeaderInfo()
 {
 	map->version = EMapFormat::VCMI;
-	map->width = mapGenOptions->getWidth();
-	map->height = mapGenOptions->getHeight();
-	map->twoLevel = mapGenOptions->getHasTwoLevels();
+	map->width = mapGenOptions.getWidth();
+	map->height = mapGenOptions.getHeight();
+	map->twoLevel = mapGenOptions.getHasTwoLevels();
 	map->name = VLC->generaltexth->allTexts[740];
 	map->description = getMapDescription();
 	map->difficulty = 1;

+ 12 - 9
lib/rmg/CMapGenerator.h

@@ -52,16 +52,16 @@ class DLL_LINKAGE CMapGenerator
 public:
 	using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>;
 
-	explicit CMapGenerator();
+	explicit CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed = std::time(nullptr));
 	~CMapGenerator(); // required due to std::unique_ptr
-
-	std::unique_ptr<CMap> generate(CMapGenOptions * mapGenOptions, int RandomSeed = std::time(nullptr));
-
-	CMapGenOptions * mapGenOptions;
-	std::unique_ptr<CMap> map;
+	
+	mutable std::unique_ptr<CMap> map;
 	CRandomGenerator rand;
-	int randomSeed;
-	CMapEditManager * editManager;
+	
+	CMapEditManager* getEditManager() const;
+	const CMapGenOptions& getMapGenOptions() const;
+	
+	std::unique_ptr<CMap> generate();
 
 	Zones & getZones();
 	void createDirectConnections();
@@ -69,7 +69,7 @@ public:
 	void findZonesForQuestArts();
 	void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
 	void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
-	void foreachDiagonaltNeighbour(const int3& pos, std::function<void(int3& pos)> foo);
+	void foreachDiagonalNeighbour(const int3& pos, std::function<void(int3& pos)> foo);
 
 	bool isBlocked(const int3 &tile) const;
 	bool shouldBeBlocked(const int3 &tile) const;
@@ -101,6 +101,9 @@ public:
 	void setZoneID(const int3& tile, TRmgTemplateZoneId zid);
 
 private:
+	int randomSeed;
+	CMapGenOptions& mapGenOptions;
+	
 	std::list<rmg::ZoneConnection> connectionsLeft;
 	Zones zones;
 	std::map<TFaction, ui32> zonesPerFaction;

+ 56 - 21
lib/rmg/CRmgTemplateStorage.cpp

@@ -17,11 +17,6 @@
 
 using namespace rmg;
 
-const std::map<std::string, CRmgTemplate *> & CRmgTemplateStorage::getTemplates() const
-{
-	return templates;
-}
-
 void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
 	//unused
@@ -30,31 +25,20 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 
 void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto tpl = new CRmgTemplate();
 	try
 	{
 		JsonDeserializer handler(nullptr, data);
-		auto fullKey = normalizeIdentifier(scope, "core", name);
-		tpl->setId(name);
-		tpl->serializeJson(handler);
-		tpl->validate();
-		templates[fullKey] = tpl;
+		auto fullKey = normalizeIdentifier(scope, "core", name); //actually it's not used
+		templates[fullKey].setId(name);
+		templates[fullKey].serializeJson(handler);
+		templates[fullKey].validate();
 	}
 	catch(const std::exception & e)
 	{
-		logGlobal->error("Template %s has errors. Message: %s.", tpl->getName(), std::string(e.what()));
+		logGlobal->error("Template %s has errors. Message: %s.", name, std::string(e.what()));
 	}
 }
 
-CRmgTemplateStorage::CRmgTemplateStorage()
-{
-}
-
-CRmgTemplateStorage::~CRmgTemplateStorage()
-{
-	for (auto & pair : templates) delete pair.second;
-}
-
 std::vector<bool> CRmgTemplateStorage::getDefaultAllowed() const
 {
 	//all templates are allowed
@@ -66,3 +50,54 @@ std::vector<JsonNode> CRmgTemplateStorage::loadLegacyData(size_t dataSize)
 	return std::vector<JsonNode>();
 	//it would be cool to load old rmg.txt files
 }
+
+const CRmgTemplate * CRmgTemplateStorage::getTemplate(const std::string & templateName) const
+{
+	auto iter = templates.find(templateName);
+	if(iter==templates.end())
+		return nullptr;
+	return &iter->second;
+}
+
+std::vector<const CRmgTemplate *> CRmgTemplateStorage::getTemplates() const
+{
+	std::vector<const CRmgTemplate *> result;
+	for(auto i=templates.cbegin(); i!=templates.cend(); ++i)
+	{
+		result.push_back(&i->second);
+	}
+	return result;
+}
+
+std::vector<const CRmgTemplate *> CRmgTemplateStorage::getTemplates(const int3& filterSize, si8 filterPlayers, si8 filterHumanPlayers, si8 filterCpuPlayers) const
+{
+	std::vector<const CRmgTemplate *> result;
+	for(auto i=templates.cbegin(); i!=templates.cend(); ++i)
+	{
+		auto& tmpl = i->second;
+		
+		if (!tmpl.matchesSize(filterSize))
+			continue;
+		
+		if (filterPlayers != -1)
+		{
+			if (!tmpl.getPlayers().isInRange(filterPlayers))
+				continue;
+		}
+		else
+		{
+			// Human players shouldn't be banned when playing with random player count
+			if (filterHumanPlayers > *boost::min_element(tmpl.getPlayers().getNumbers()))
+				continue;
+		}
+		
+		if(filterCpuPlayers != -1)
+		{
+			if (!tmpl.getCpuPlayers().isInRange(filterCpuPlayers))
+				continue;
+		}
+		
+		result.push_back(&i->second);
+	}
+	return result;
+}

+ 9 - 7
lib/rmg/CRmgTemplateStorage.h

@@ -11,27 +11,29 @@
 #pragma once
 
 #include "../IHandlerBase.h"
+#include "../int3.h"
+#include "CRmgTemplate.h"
 
 class JsonNode;
-class CRmgTemplate;
 
 /// The CJsonRmgTemplateLoader loads templates from a JSON file.
 class DLL_LINKAGE CRmgTemplateStorage : public IHandlerBase
 {
 public:
-	CRmgTemplateStorage();
-	~CRmgTemplateStorage();
-
-	const std::map<std::string, CRmgTemplate *> & getTemplates() const;
-
+	CRmgTemplateStorage() = default;
+	
 	std::vector<bool> getDefaultAllowed() const override;
 	std::vector<JsonNode> loadLegacyData(size_t dataSize) override;
 
 	/// loads single object into game. Scope is namespace of this object, same as name of source mod
 	virtual void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
+	
+	const CRmgTemplate * getTemplate(const std::string & templateName) const;
+	std::vector<const CRmgTemplate *> getTemplates() const;
+	std::vector<const CRmgTemplate *> getTemplates(const int3& filterSize, si8 filterPlayers, si8 filterHumanPlayers, si8 filterCpuPlayers) const;
 
 private:
-	std::map<std::string, CRmgTemplate *> templates;
+	std::map<std::string, CRmgTemplate> templates;
 };
 

+ 39 - 26
lib/rmg/CRmgTemplateZone.cpp

@@ -103,25 +103,25 @@ void CTileInfo::setRoadType(ERoadType::ERoadType value)
 }
 
 
-CRmgTemplateZone::CRmgTemplateZone()
+CRmgTemplateZone::CRmgTemplateZone(CMapGenerator * Gen)
 	: ZoneOptions(),
 	townType(ETownType::NEUTRAL),
 	terrainType (ETerrainType::GRASS),
 	minGuardedValue(0),
 	questArtZone(),
-	gen(nullptr)
+	gen(Gen)
 {
 
 }
 
-void CRmgTemplateZone::setOptions(const ZoneOptions * options)
+bool CRmgTemplateZone::isUnderground() const
 {
-	ZoneOptions::operator=(*options);
+	return getPos().z;
 }
 
-void CRmgTemplateZone::setGenPtr(CMapGenerator * Gen)
+void CRmgTemplateZone::setOptions(const ZoneOptions * options)
 {
-	gen = Gen;
+	ZoneOptions::operator=(*options);
 }
 
 void CRmgTemplateZone::setQuestArtZone(std::shared_ptr<CRmgTemplateZone> otherZone)
@@ -384,7 +384,7 @@ void CRmgTemplateZone::fractalize()
 	#define PRINT_FRACTALIZED_MAP false
 	if (PRINT_FRACTALIZED_MAP) //enable to debug
 	{
-		std::ofstream out(boost::to_string(boost::format("zone %d") % id));
+		std::ofstream out(boost::to_string(boost::format("zone_%d.txt") % id));
 		int levels = gen->map->twoLevel ? 2 : 1;
 		int width =  gen->map->width;
 		int height = gen->map->height;
@@ -618,7 +618,7 @@ bool CRmgTemplateZone::createRoad(const int3& src, const int3& dst)
 			if (!directNeighbourFound)
 			{
 				movementCost = 2.1f; //moving diagonally is penalized over moving two tiles straight
-				gen->foreachDiagonaltNeighbour(currentNode, foo);
+				gen->foreachDiagonalNeighbour(currentNode, foo);
 			}
 		}
 
@@ -805,7 +805,7 @@ bool CRmgTemplateZone::addMonster(int3 &pos, si32 strength, bool clearSurroundin
 	//precalculate actual (randomized) monster strength based on this post
 	//http://forum.vcmi.eu/viewtopic.php?p=12426#12426
 
-	int mapMonsterStrength = gen->mapGenOptions->getMonsterStrength();
+	int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength();
 	int monsterStrength = (zoneGuard ? 0 : zoneMonsterStrength) + mapMonsterStrength - 1; //array index from 0 to 4
 	static const int value1[] = {2500, 1500, 1000, 500, 0};
 	static const int value2[] = {7500, 7500, 7500, 5000, 5000};
@@ -1181,10 +1181,10 @@ void CRmgTemplateZone::initTownType ()
 		if (playerInfo.canAnyonePlay())
 		{
 			player = PlayerColor(player_id);
-			townType = gen->mapGenOptions->getPlayersSettings().find(player)->second.getStartingTown();
+			townType = gen->getMapGenOptions().getPlayersSettings().find(player)->second.getStartingTown();
 
 			if (townType == CMapGenOptions::CPlayerSettings::RANDOM_TOWN)
-				randomizeTownType();
+				randomizeTownType(true);
 		}
 		else //no player - randomize town
 		{
@@ -1261,12 +1261,25 @@ void CRmgTemplateZone::initTownType ()
 	}
 }
 
-void CRmgTemplateZone::randomizeTownType ()
+void CRmgTemplateZone::randomizeTownType(bool matchUndergroundType)
 {
-	if (townTypes.size())
-		townType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand);
-	else
-		townType = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed, we still need some
+	auto townTypesAllowed = (townTypes.size() ? townTypes : getDefaultTownTypes());
+	if(matchUndergroundType && gen->getMapGenOptions().getHasTwoLevels())
+	{
+		std::set<TFaction> townTypesVerify;
+		for(TFaction factionIdx : townTypesAllowed)
+		{
+			bool preferUnderground = (*VLC->townh)[factionIdx]->preferUndergroundPlacement;
+			if(isUnderground() ? preferUnderground : !preferUnderground)
+			{
+				townTypesVerify.insert(factionIdx);
+			}
+		}
+		if(!townTypesVerify.empty())
+			townTypesAllowed = townTypesVerify;
+	}
+	
+	townType = *RandomGeneratorUtil::nextItem(townTypesAllowed, gen->rand);
 }
 
 void CRmgTemplateZone::initTerrainType ()
@@ -1278,7 +1291,7 @@ void CRmgTemplateZone::initTerrainType ()
 		terrainType = *RandomGeneratorUtil::nextItem(terrainTypes, gen->rand);
 
 	//TODO: allow new types of terrain?
-	if (pos.z)
+	if (isUnderground())
 	{
 		if (terrainType != ETerrainType::LAVA)
 			terrainType = ETerrainType::SUBTERRANEAN;
@@ -1295,8 +1308,8 @@ void CRmgTemplateZone::initTerrainType ()
 void CRmgTemplateZone::paintZoneTerrain (ETerrainType terrainType)
 {
 	std::vector<int3> tiles(tileinfo.begin(), tileinfo.end());
-	gen->editManager->getTerrainSelection().setSelection(tiles);
-	gen->editManager->drawTerrain(terrainType, &gen->rand);
+	gen->getEditManager()->getTerrainSelection().setSelection(tiles);
+	gen->getEditManager()->drawTerrain(terrainType, &gen->rand);
 }
 
 bool CRmgTemplateZone::placeMines ()
@@ -1493,7 +1506,7 @@ bool CRmgTemplateZone::createRequiredObjects()
 
 void CRmgTemplateZone::createTreasures()
 {
-	int mapMonsterStrength = gen->mapGenOptions->getMonsterStrength();
+	int mapMonsterStrength = gen->getMapGenOptions().getMonsterStrength();
 	int monsterStrength = zoneMonsterStrength + mapMonsterStrength - 1; //array index from 0 to 4
 
 	static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
@@ -1570,8 +1583,8 @@ void CRmgTemplateZone::createObstacles1()
 				accessibleTiles.push_back(tile);
 			}
 		}
-		gen->editManager->getTerrainSelection().setSelection(accessibleTiles);
-		gen->editManager->drawTerrain(terrainType, &gen->rand);
+		gen->getEditManager()->getTerrainSelection().setSelection(accessibleTiles);
+		gen->getEditManager()->drawTerrain(terrainType, &gen->rand);
 	}
 }
 
@@ -1610,7 +1623,7 @@ void CRmgTemplateZone::createObstacles2()
 		return p1.first > p2.first; //bigger obstacles first
 	});
 
-	auto sel = gen->editManager->getTerrainSelection();
+	auto sel = gen->getEditManager()->getTerrainSelection();
 	sel.clearSelection();
 
 	auto tryToPlaceObstacleHere = [this, &possibleObstacles](int3& tile, int index)-> bool
@@ -1705,8 +1718,8 @@ void CRmgTemplateZone::drawRoads()
 			tiles.push_back(tile);
 	}
 
-	gen->editManager->getTerrainSelection().setSelection(tiles);
-	gen->editManager->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand);
+	gen->getEditManager()->getTerrainSelection().setSelection(tiles);
+	gen->getEditManager()->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand);
 }
 
 
@@ -1903,7 +1916,7 @@ void CRmgTemplateZone::checkAndPlaceObject(CGObjectInstance* object, const int3
 		object->appearance = templates.front();
 	}
 
-	gen->editManager->insertObject(object);
+	gen->getEditManager()->insertObject(object);
 }
 
 void CRmgTemplateZone::placeObject(CGObjectInstance* object, const int3 &pos, bool updateDistance)

+ 4 - 5
lib/rmg/CRmgTemplateZone.h

@@ -89,11 +89,10 @@ struct DLL_LINKAGE CTreasurePileInfo
 class DLL_LINKAGE CRmgTemplateZone : public rmg::ZoneOptions
 {
 public:
-	CRmgTemplateZone();
+	CRmgTemplateZone(CMapGenerator * Gen);
 
 	void setOptions(const rmg::ZoneOptions * options);
-
-	void setGenPtr(CMapGenerator * Gen);
+	bool isUnderground() const;
 
 	float3 getCenter() const;
 	void setCenter(const float3 &f);
@@ -119,7 +118,7 @@ public:
 	bool placeMines ();
 	void initTownType ();
 	void paintZoneTerrain (ETerrainType terrainType);
-	void randomizeTownType(); //helper function
+	void randomizeTownType(bool matchUndergroundType = false); //helper function
 	void initTerrainType ();
 	void createBorder();
 	void fractalize();
@@ -170,7 +169,7 @@ private:
 
 	std::vector<ObjectInfo> possibleObjects;
 	int minGuardedValue;
-
+	
 	//content info
 	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
 	std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;

+ 9 - 9
lib/rmg/CZonePlacer.cpp

@@ -40,15 +40,15 @@ float CZonePlacer::getDistance (float distance) const
 	return (distance ? distance * distance : 1e-6f);
 }
 
-void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenerator * rand)
+void CZonePlacer::placeZones(CRandomGenerator * rand)
 {
 	logGlobal->info("Starting zone placement");
 
-	width = mapGenOptions->getWidth();
-	height = mapGenOptions->getHeight();
+	width = gen->getMapGenOptions().getWidth();
+	height = gen->getMapGenOptions().getHeight();
 
 	auto zones = gen->getZones();
-	bool underground = mapGenOptions->getHasTwoLevels();
+	bool underground = gen->getMapGenOptions().getHasTwoLevels();
 
 	/*
 	gravity-based algorithm
@@ -174,7 +174,7 @@ void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const
 			if (boost::optional<int> owner = zone.second->getOwner())
 			{
 				auto player = PlayerColor(*owner - 1);
-				auto playerSettings = gen->mapGenOptions->getPlayersSettings();
+				auto playerSettings = gen->getMapGenOptions().getPlayersSettings();
 				si32 faction = CMapGenOptions::CPlayerSettings::RANDOM_TOWN;
 				if (vstd::contains(playerSettings, player))
 					faction = playerSettings[player].getStartingTown();
@@ -453,12 +453,12 @@ d = 0.01 * dx^3 - 0.1618 * dx^2 + 1 * dx + ...
 	return dx * (1.0f + dx * (0.1f + dx * 0.01f)) + dy * (1.618f + dy * (-0.1618f + dy * 0.01618f));
 }
 
-void CZonePlacer::assignZones(const CMapGenOptions * mapGenOptions)
+void CZonePlacer::assignZones()
 {
 	logGlobal->info("Starting zone colouring");
 
-	auto width = mapGenOptions->getWidth();
-	auto height = mapGenOptions->getHeight();
+	auto width = gen->getMapGenOptions().getWidth();
+	auto height = gen->getMapGenOptions().getHeight();
 
 	//scale to Medium map to ensure smooth results
 	scaleX = 72.f / width;
@@ -554,7 +554,7 @@ void CZonePlacer::assignZones(const CMapGenOptions * mapGenOptions)
 
 		//TODO: similiar for islands
 		#define	CREATE_FULL_UNDERGROUND true //consider linking this with water amount
-		if (zone.second->getPos().z)
+		if (zone.second->isUnderground())
 		{
 			if (!CREATE_FULL_UNDERGROUND)
 				zone.second->discardDistantTiles((float)(zone.second->getSize() + 1));

+ 4 - 2
lib/rmg/CZonePlacer.h

@@ -34,12 +34,14 @@ public:
 	float getDistance(float distance) const; //additional scaling without 0 divison
 	~CZonePlacer();
 
+	void placeZones(CRandomGenerator * rand);
+	void assignZones();
+	
+private:
 	void prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, CRandomGenerator * rand);
 	void attractConnectedZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &distances);
 	void separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps);
 	void moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDistanceVector &distances, TDistanceVector &overlaps);
-	void placeZones(const CMapGenOptions * mapGenOptions, CRandomGenerator * rand);
-	void assignZones(const CMapGenOptions * mapGenOptions);
 
 private:
 	int width;

+ 2 - 2
test/map/CMapFormatTest.cpp

@@ -59,9 +59,9 @@ TEST(MapFormat, Random)
 	opt.setPlayerTypeForStandardPlayer(PlayerColor(2), EPlayerType::AI);
 	opt.setPlayerTypeForStandardPlayer(PlayerColor(3), EPlayerType::AI);
 
-	CMapGenerator gen;
+	CMapGenerator gen(opt, TEST_RANDOM_SEED);
 
-	std::unique_ptr<CMap> initialMap = gen.generate(&opt, TEST_RANDOM_SEED);
+	std::unique_ptr<CMap> initialMap = gen.generate();
 	initialMap->name = "Test";
 	SCOPED_TRACE("MapFormat_Random generated");