2
0
Эх сурвалжийг харах

Redone RMG template serialization using JSON serializer, added tests

AlexVinS 7 жил өмнө
parent
commit
9d108d59db

+ 28 - 0
include/vstd/ContainerUtils.h

@@ -0,0 +1,28 @@
+/*
+ * ContainerUtils.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
+
+namespace vstd
+{
+	template<typename K, typename V>
+	std::map<V, K> invertMap(const std::map<K, V> & m)
+	{
+		std::map<V,K> other;
+		std::transform(m.cbegin(), m.cend(), std::inserter(other, other.begin()), [](const std::pair<K, V> & p)
+		{
+			return std::make_pair(p.second, p.first);
+		});
+		return other;
+	}
+}
+
+
+

+ 3 - 0
lib/CMakeLists.txt

@@ -165,6 +165,9 @@ set_source_files_properties(${CMAKE_BINARY_DIR}/Version.cpp
 )
 
 set(lib_HEADERS
+		${CMAKE_HOME_DIRECTORY}/include/vstd/CLoggerBase.h
+		${CMAKE_HOME_DIRECTORY}/include/vstd/ContainerUtils.h
+		${CMAKE_HOME_DIRECTORY}/include/vstd/RNG.h
 		StdInc.h
 		../Global.h
 

+ 1 - 0
lib/VCMI_lib.cbp

@@ -130,6 +130,7 @@
 		<Unit filename="../Global.h" />
 		<Unit filename="../Version.h" />
 		<Unit filename="../include/vstd/CLoggerBase.h" />
+		<Unit filename="../include/vstd/ContainerUtils.h" />
 		<Unit filename="../include/vstd/RNG.h" />
 		<Unit filename="AI_Base.h" />
 		<Unit filename="CArtHandler.cpp" />

+ 2 - 2
lib/rmg/CMapGenOptions.cpp

@@ -406,8 +406,8 @@ const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand
 	for(const auto & tplPair : tpls)
 	{
 		const auto & tpl = tplPair.second;
-		CRmgTemplate::CSize tplSize(width, height, hasTwoLevels);
-		if(tplSize >= tpl->getMinSize() && tplSize <= tpl->getMaxSize())
+		int3 tplSize(width, height, (hasTwoLevels ? 2 : 1));
+		if(tpl->matchesSize(tplSize))
 		{
 			bool isPlayerCountValid = false;
 			if (getPlayerCount() != RANDOM_SIZE)

+ 5 - 4
lib/rmg/CMapGenerator.cpp

@@ -149,6 +149,7 @@ std::unique_ptr<CMap> CMapGenerator::generate(CMapGenOptions * mapGenOptions, in
 		map->calculateGuardingGreaturePositions(); //clear map so that all tiles are unguarded
 		fillZones();
 		//updated guarded tiles will be calculated in CGameState::initMapObjects()
+		zones.clear();
 	}
 	catch (rmgException &e)
 	{
@@ -277,8 +278,8 @@ void CMapGenerator::genZones()
 	zones.clear();
 	for(const auto & option : tmpl->getZones())
 	{
-		auto zone = new CRmgTemplateZone();
-		zone->setOptions(option.second);
+		auto zone = std::make_shared<CRmgTemplateZone>();
+		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
@@ -317,7 +318,7 @@ void CMapGenerator::fillZones()
 
 	createConnections2(); //subterranean gates and monoliths
 
-	std::vector<CRmgTemplateZone*> treasureZones;
+	std::vector<std::shared_ptr<CRmgTemplateZone>> treasureZones;
 	for (auto it : zones)
 	{
 		it.second->fill();
@@ -716,7 +717,7 @@ void CMapGenerator::checkIsOnMap(const int3& tile) const
 }
 
 
-std::map<TRmgTemplateZoneId, CRmgTemplateZone*> CMapGenerator::getZones() const
+CMapGenerator::Zones & CMapGenerator::getZones()
 {
 	return zones;
 }

+ 4 - 2
lib/rmg/CMapGenerator.h

@@ -50,6 +50,8 @@ public:
 class DLL_LINKAGE CMapGenerator
 {
 public:
+	using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>;
+
 	explicit CMapGenerator();
 	~CMapGenerator(); // required due to std::unique_ptr
 
@@ -61,7 +63,7 @@ public:
 	int randomSeed;
 	CMapEditManager * editManager;
 
-	std::map<TRmgTemplateZoneId, CRmgTemplateZone*> getZones() const;
+	Zones & getZones();
 	void createDirectConnections();
 	void createConnections2();
 	void findZonesForQuestArts();
@@ -100,7 +102,7 @@ public:
 
 private:
 	std::list<rmg::ZoneConnection> connectionsLeft;
-	std::map<TRmgTemplateZoneId, CRmgTemplateZone*> zones;
+	Zones zones;
 	std::map<TFaction, ui32> zonesPerFaction;
 	ui32 zonesTotal; //zones that have their main town only
 

+ 404 - 213
lib/rmg/CRmgTemplate.cpp

@@ -9,15 +9,93 @@
  */
 
 #include "StdInc.h"
+#include <vstd/ContainerUtils.h>
 #include "CRmgTemplate.h"
 
 #include "../mapping/CMap.h"
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
+#include "../serializer/JsonSerializeFormat.h"
+#include "../StringConstants.h"
+
+namespace
+{
+	si32 decodeZoneId(const std::string & json)
+	{
+		return boost::lexical_cast<si32>(json);
+	}
+
+	std::string encodeZoneId(si32 id)
+	{
+		return boost::lexical_cast<std::string>(id);
+	}
+}
+
+CTreasureInfo::CTreasureInfo()
+	: min(0),
+	max(0),
+	density(0)
+{
+
+}
+
+bool CTreasureInfo::operator==(const CTreasureInfo & other) const
+{
+	return (min == other.min) && (max == other.max) && (density == other.density);
+}
+
+void CTreasureInfo::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeInt("min", min, 0);
+	handler.serializeInt("max", max, 0);
+	handler.serializeInt("density", density, 0);
+}
 
 namespace rmg
 {
 
+class TerrainEncoder
+{
+public:
+	static si32 decode(const std::string & identifier)
+	{
+		return vstd::find_pos(GameConstants::TERRAIN_NAMES, identifier);
+	}
+
+	static std::string encode(const si32 index)
+	{
+		return (index >=0 && index < GameConstants::TERRAIN_TYPES) ? GameConstants::TERRAIN_NAMES[index] : "<INVALID TERRAIN>";
+	}
+};
+
+class ZoneEncoder
+{
+public:
+	static si32 decode(const std::string & json)
+	{
+		return boost::lexical_cast<si32>(json);
+	}
+
+	static std::string encode(si32 id)
+	{
+		return boost::lexical_cast<std::string>(id);
+	}
+};
+
+class FactionEncoder
+{
+public:
+	static si32 decode(const std::string & json)
+	{
+		return VLC->townh->decodeFaction(json);
+	}
+
+	static std::string encode(si32 id)
+	{
+		return VLC->townh->encodeFaction(id);
+	}
+};
+
 const std::set<ETerrainType> ZoneOptions::DEFAULT_TERRAIN_TYPES =
 {
 	ETerrainType::DIRT,
@@ -30,6 +108,8 @@ const std::set<ETerrainType> ZoneOptions::DEFAULT_TERRAIN_TYPES =
 	ETerrainType::LAVA
 };
 
+const TRmgTemplateZoneId ZoneOptions::NO_ZONE = -1;
+
 ZoneOptions::CTownInfo::CTownInfo()
 	: townCount(0),
 	castleCount(0),
@@ -44,49 +124,30 @@ int ZoneOptions::CTownInfo::getTownCount() const
 	return townCount;
 }
 
-void ZoneOptions::CTownInfo::setTownCount(int value)
-{
-	if(value < 0)
-		throw std::runtime_error("Negative value for town count not allowed.");
-	townCount = value;
-}
-
 int ZoneOptions::CTownInfo::getCastleCount() const
 {
 	return castleCount;
 }
 
-void ZoneOptions::CTownInfo::setCastleCount(int value)
-{
-	if(value < 0)
-		throw std::runtime_error("Negative value for castle count not allowed.");
-	castleCount = value;
-}
-
 int ZoneOptions::CTownInfo::getTownDensity() const
 {
 	return townDensity;
 }
 
-void ZoneOptions::CTownInfo::setTownDensity(int value)
-{
-	if(value < 0)
-		throw std::runtime_error("Negative value for town density not allowed.");
-	townDensity = value;
-}
-
 int ZoneOptions::CTownInfo::getCastleDensity() const
 {
 	return castleDensity;
 }
 
-void ZoneOptions::CTownInfo::setCastleDensity(int value)
+void ZoneOptions::CTownInfo::serializeJson(JsonSerializeFormat & handler)
 {
-	if(value < 0)
-		throw std::runtime_error("Negative value for castle density not allowed.");
-	castleDensity = value;
+	handler.serializeInt("towns", townCount, 0);
+	handler.serializeInt("castles", castleCount, 0);
+	handler.serializeInt("townDensity", townDensity, 0);
+	handler.serializeInt("castleDensity", castleDensity, 0);
 }
 
+
 ZoneOptions::ZoneOptions()
 	: id(0),
 	type(ETemplateZoneType::PLAYER_START),
@@ -102,7 +163,10 @@ ZoneOptions::ZoneOptions()
 	zoneMonsterStrength(EMonsterStrength::ZONE_NORMAL),
 	mines(),
 	treasureInfo(),
-	connections()
+	connections(),
+	minesLikeZone(NO_ZONE),
+	terrainTypeLikeZone(NO_ZONE),
+	treasureLikeZone(NO_ZONE)
 {
 
 }
@@ -124,6 +188,9 @@ ZoneOptions & ZoneOptions::operator=(const ZoneOptions & other)
 	mines = other.mines;
 	treasureInfo = other.treasureInfo;
 	connections = other.connections;
+	minesLikeZone = other.minesLikeZone;
+	terrainTypeLikeZone = other.terrainTypeLikeZone;
+	treasureLikeZone = other.treasureLikeZone;
 	return *this;
 }
 
@@ -143,10 +210,6 @@ ETemplateZoneType::ETemplateZoneType ZoneOptions::getType() const
 {
 	return type;
 }
-void ZoneOptions::setType(ETemplateZoneType::ETemplateZoneType value)
-{
-	type = value;
-}
 
 int ZoneOptions::getSize() const
 {
@@ -155,8 +218,6 @@ int ZoneOptions::getSize() const
 
 void ZoneOptions::setSize(int value)
 {
-	if(value <= 0)
-		throw std::runtime_error(boost::to_string(boost::format("Zone %d size needs to be greater than 0.") % id));
 	size = value;
 }
 
@@ -165,43 +226,6 @@ boost::optional<int> ZoneOptions::getOwner() const
 	return owner;
 }
 
-void ZoneOptions::setOwner(boost::optional<int> value)
-{
-	if(value && !(*value >= 0 && *value <= PlayerColor::PLAYER_LIMIT_I))
-		throw std::runtime_error(boost::to_string(boost::format ("Owner of zone %d has to be in range 0 to max player count.") % id));
-	owner = value;
-}
-
-const ZoneOptions::CTownInfo & ZoneOptions::getPlayerTowns() const
-{
-	return playerTowns;
-}
-
-void ZoneOptions::setPlayerTowns(const CTownInfo & value)
-{
-	playerTowns = value;
-}
-
-const ZoneOptions::CTownInfo & ZoneOptions::getNeutralTowns() const
-{
-	return neutralTowns;
-}
-
-void ZoneOptions::setNeutralTowns(const CTownInfo & value)
-{
-	neutralTowns = value;
-}
-
-bool ZoneOptions::getMatchTerrainToTown() const
-{
-	return matchTerrainToTown;
-}
-
-void ZoneOptions::setMatchTerrainToTown(bool value)
-{
-	matchTerrainToTown = value;
-}
-
 const std::set<ETerrainType> & ZoneOptions::getTerrainTypes() const
 {
 	return terrainTypes;
@@ -214,16 +238,6 @@ void ZoneOptions::setTerrainTypes(const std::set<ETerrainType> & value)
 	terrainTypes = value;
 }
 
-bool ZoneOptions::getTownsAreSameType() const
-{
-	return townsAreSameType;
-}
-
-void ZoneOptions::setTownsAreSameType(bool value)
-{
-	townsAreSameType = value;
-}
-
 std::set<TFaction> ZoneOptions::getDefaultTownTypes() const
 {
 	std::set<TFaction> defaultTowns;
@@ -250,15 +264,9 @@ void ZoneOptions::setMonsterTypes(const std::set<TFaction> & value)
 	monsterTypes = value;
 }
 
-void ZoneOptions::setMonsterStrength(EMonsterStrength::EMonsterStrength val)
+void ZoneOptions::setMinesInfo(const std::map<TResource, ui16> & value)
 {
-	assert (vstd::iswithin(val, EMonsterStrength::ZONE_WEAK, EMonsterStrength::ZONE_STRONG));
-	zoneMonsterStrength = val;
-}
-
-void ZoneOptions::setMinesAmount(TResource res, ui16 amount)
-{
-	mines[res] = amount;
+	mines = value;
 }
 
 std::map<TResource, ui16> ZoneOptions::getMinesInfo() const
@@ -266,9 +274,9 @@ std::map<TResource, ui16> ZoneOptions::getMinesInfo() const
 	return mines;
 }
 
-void ZoneOptions::addTreasureInfo(const CTreasureInfo & info)
+void ZoneOptions::setTreasureInfo(const std::vector<CTreasureInfo> & value)
 {
-	treasureInfo.push_back(info);
+	treasureInfo = value;
 }
 
 const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const
@@ -276,171 +284,165 @@ const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const
 	return treasureInfo;
 }
 
-void ZoneOptions::addConnection(TRmgTemplateZoneId otherZone)
+TRmgTemplateZoneId ZoneOptions::getMinesLikeZone() const
 {
-	connections.push_back (otherZone);
+	return minesLikeZone;
 }
 
-
-ZoneConnection::ZoneConnection()
-	: zoneA(-1),
-	zoneB(-1),
-	guardStrength(0)
+TRmgTemplateZoneId ZoneOptions::getTerrainTypeLikeZone() const
 {
-
+	return terrainTypeLikeZone;
 }
 
-
-TRmgTemplateZoneId ZoneConnection::getZoneA() const
+TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const
 {
-	return zoneA;
+    return treasureLikeZone;
 }
 
-void ZoneConnection::setZoneA(TRmgTemplateZoneId value)
+void ZoneOptions::addConnection(TRmgTemplateZoneId otherZone)
 {
-	zoneA = value;
+	connections.push_back (otherZone);
 }
 
-TRmgTemplateZoneId ZoneConnection::getZoneB() const
+std::vector<TRmgTemplateZoneId> ZoneOptions::getConnections() const
 {
-	return zoneB;
+	return connections;
 }
 
-void ZoneConnection::setZoneB(TRmgTemplateZoneId value)
+void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 {
-	zoneB = value;
-}
+	static const std::vector<std::string> zoneTypes =
+	{
+		"playerStart",
+		"cpuStart",
+		"treasure",
+		"junction"
+	};
 
-int ZoneConnection::getGuardStrength() const
-{
-	return guardStrength;
-}
+	handler.serializeEnum("type", type, zoneTypes);
+	handler.serializeInt("size", size, 1);
+	handler.serializeInt("owner", owner);
+	handler.serializeStruct("playerTowns", playerTowns);
+	handler.serializeStruct("neutralTowns", neutralTowns);
+	handler.serializeBool("matchTerrainToTown", matchTerrainToTown, true);
 
-void ZoneConnection::setGuardStrength(int value)
-{
-	if(value < 0) throw std::runtime_error("Negative value for guard strength not allowed.");
-	guardStrength = value;
-}
+	#define SERIALIZE_ZONE_LINK(fieldName) handler.serializeInt(#fieldName, fieldName, NO_ZONE);
 
-}
+	SERIALIZE_ZONE_LINK(minesLikeZone);
+	SERIALIZE_ZONE_LINK(terrainTypeLikeZone);
+	SERIALIZE_ZONE_LINK(treasureLikeZone);
 
+	#undef SERIALIZE_ZONE_LINK
 
-using namespace rmg;//todo: remove
+	if(terrainTypeLikeZone == NO_ZONE)
+		handler.serializeIdArray<ETerrainType, TerrainEncoder>("terrainTypes", terrainTypes, DEFAULT_TERRAIN_TYPES);
 
+	handler.serializeBool("townsAreSameType", townsAreSameType, false);
+	handler.serializeIdArray<TFaction, FactionEncoder>("allowedMonsters", monsterTypes, VLC->townh->getAllowedFactions(false));
+	handler.serializeIdArray<TFaction, FactionEncoder>("allowedTowns", townTypes, VLC->townh->getAllowedFactions(true));
 
-CRmgTemplate::CSize::CSize() : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), under(true)
-{
+	{
+		//TODO: add support for std::map to serializeEnum
+		static const std::vector<std::string> STRENGTH =
+		{
+			"weak",
+			"normal",
+			"strong"
+		};
+
+		si32 rawStrength = 0;
+		if(handler.saving)
+		{
+			rawStrength = static_cast<decltype(rawStrength)>(zoneMonsterStrength);
+			rawStrength++;
+		}
+		handler.serializeEnum("monsters", rawStrength, STRENGTH);
+		if(!handler.saving)
+		{
+			rawStrength--;
+			zoneMonsterStrength = static_cast<decltype(zoneMonsterStrength)>(rawStrength);
+		}
+	}
 
-}
+	if(treasureLikeZone == NO_ZONE)
+	{
+		auto treasureData = handler.enterArray("treasure");
+		treasureData.serializeStruct(treasureInfo);
+	}
 
-CRmgTemplate::CSize::CSize(int width, int height, bool under) : under(under)
-{
-	setWidth(width);
-	setHeight(height);
-}
+	if((minesLikeZone == NO_ZONE) && (!handler.saving || !mines.empty()))
+	{
+		auto minesData = handler.enterStruct("mines");
 
-int CRmgTemplate::CSize::getWidth() const
-{
-	return width;
+		for(TResource idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
+		{
+			handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mines[idx], 0);
+		}
+	}
 }
 
-void CRmgTemplate::CSize::setWidth(int value)
+ZoneConnection::ZoneConnection()
+	: zoneA(-1),
+	zoneB(-1),
+	guardStrength(0)
 {
-	if(value <= 0) throw std::runtime_error("Width > 0 failed.");
-	width = value;
-}
 
-int CRmgTemplate::CSize::getHeight() const
-{
-	return height;
 }
 
-void CRmgTemplate::CSize::setHeight(int value)
+TRmgTemplateZoneId ZoneConnection::getZoneA() const
 {
-	if(value <= 0) throw std::runtime_error("Height > 0 failed.");
-	height = value;
+	return zoneA;
 }
 
-bool CRmgTemplate::CSize::getUnder() const
+TRmgTemplateZoneId ZoneConnection::getZoneB() const
 {
-	return under;
+	return zoneB;
 }
 
-void CRmgTemplate::CSize::setUnder(bool value)
+int ZoneConnection::getGuardStrength() const
 {
-	under = value;
+	return guardStrength;
 }
 
-bool CRmgTemplate::CSize::operator<=(const CSize & value) const
+void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
 {
-	if(width < value.width && height < value.height)
-	{
-		return true;
-	}
-	else if(width == value.width && height == value.height)
-	{
-		return under ? value.under : true;
-	}
-	else
-	{
-		return false;
-	}
+	handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("a", zoneA, -1);
+	handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("b", zoneB, -1);
+	handler.serializeInt("guard", guardStrength, 0);
 }
 
-bool CRmgTemplate::CSize::operator>=(const CSize & value) const
-{
-	if(width > value.width && height > value.height)
-	{
-		return true;
-	}
-	else if(width == value.width && height == value.height)
-	{
-		return under ? true : !value.under;
-	}
-	else
-	{
-		return false;
-	}
 }
 
+using namespace rmg;//todo: remove
+
 CRmgTemplate::CRmgTemplate()
+	: minSize(72, 72, 2),
+	maxSize(72, 72, 2)
 {
 
 }
 
 CRmgTemplate::~CRmgTemplate()
 {
-	for (auto & pair : zones) delete pair.second;
 }
 
-const std::string & CRmgTemplate::getName() const
+bool CRmgTemplate::matchesSize(const int3 & value) const
 {
-	return name;
-}
+	const int64_t square = value.x * value.y * value.z;
+	const int64_t minSquare = minSize.x * minSize.y * minSize.z;
+	const int64_t maxSquare = maxSize.x * maxSize.y * maxSize.z;
 
-void CRmgTemplate::setName(const std::string & value)
-{
-	name = value;
+	return minSquare <= square && square <= maxSquare;
 }
 
-const CRmgTemplate::CSize & CRmgTemplate::getMinSize() const
+void CRmgTemplate::setId(const std::string & value)
 {
-	return minSize;
-}
-
-void CRmgTemplate::setMinSize(const CSize & value)
-{
-	minSize = value;
-}
-
-const CRmgTemplate::CSize & CRmgTemplate::getMaxSize() const
-{
-	return maxSize;
+	id = value;
 }
 
-void CRmgTemplate::setMaxSize(const CSize & value)
+const std::string & CRmgTemplate::getName() const
 {
-	maxSize = value;
+	return name.empty() ? id : name;
 }
 
 const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getPlayers() const
@@ -448,41 +450,21 @@ const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getPlayers() const
 	return players;
 }
 
-void CRmgTemplate::setPlayers(const CPlayerCountRange & value)
-{
-	players = value;
-}
-
 const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getCpuPlayers() const
 {
 	return cpuPlayers;
 }
 
-void CRmgTemplate::setCpuPlayers(const CPlayerCountRange & value)
-{
-	cpuPlayers = value;
-}
-
-const std::map<TRmgTemplateZoneId, ZoneOptions *> & CRmgTemplate::getZones() const
+const CRmgTemplate::Zones & CRmgTemplate::getZones() const
 {
 	return zones;
 }
 
-void CRmgTemplate::setZones(const std::map<TRmgTemplateZoneId, ZoneOptions *> & value)
-{
-	zones = value;
-}
-
-const std::list<ZoneConnection> & CRmgTemplate::getConnections() const
+const std::vector<ZoneConnection> & CRmgTemplate::getConnections() const
 {
 	return connections;
 }
 
-void CRmgTemplate::setConnections(const std::list<ZoneConnection> & value)
-{
-	connections = value;
-}
-
 void CRmgTemplate::validate() const
 {
 	//TODO add some validation checks, throw on failure
@@ -516,3 +498,212 @@ std::set<int> CRmgTemplate::CPlayerCountRange::getNumbers() const
 	}
 	return numbers;
 }
+
+std::string CRmgTemplate::CPlayerCountRange::toString() const
+{
+	if(range.size() == 1)
+	{
+		const auto & p = range.front();
+		if((p.first == p.second) && (p.first == 0))
+			return "";
+	}
+
+	std::string ret;
+
+	bool first = true;
+
+	for(auto & p : range)
+	{
+		if(!first)
+			ret +=",";
+		else
+			first = false;
+
+		if(p.first == p.second)
+		{
+			ret += boost::lexical_cast<std::string>(p.first);
+		}
+		else
+		{
+			ret += boost::to_string(boost::format("%d-%d") % p.first % p.second);
+		}
+	}
+
+	return ret;
+}
+
+void CRmgTemplate::CPlayerCountRange::fromString(const std::string & value)
+{
+	range.clear();
+
+	if(value.empty())
+	{
+		addNumber(0);
+	}
+	else
+	{
+		std::vector<std::string> commaParts;
+		boost::split(commaParts, value, boost::is_any_of(","));
+		for(const auto & commaPart : commaParts)
+		{
+			std::vector<std::string> rangeParts;
+			boost::split(rangeParts, commaPart, boost::is_any_of("-"));
+			if(rangeParts.size() == 2)
+			{
+				auto lower = boost::lexical_cast<int>(rangeParts[0]);
+				auto upper = boost::lexical_cast<int>(rangeParts[1]);
+				addRange(lower, upper);
+			}
+			else if(rangeParts.size() == 1)
+			{
+				auto val = boost::lexical_cast<int>(rangeParts.front());
+				addNumber(val);
+			}
+		}
+	}
+}
+
+void CRmgTemplate::serializeJson(JsonSerializeFormat & handler)
+{
+	handler.serializeString("name", name);
+	serializeSize(handler, minSize, "minSize");
+	serializeSize(handler, maxSize, "maxSize");
+	serializePlayers(handler, players, "players");
+	serializePlayers(handler, cpuPlayers, "cpu");
+
+	{
+		auto connectionsData = handler.enterArray("connections");
+		connectionsData.serializeStruct(connections);
+	}
+
+	{
+		auto zonesData = handler.enterStruct("zones");
+        if(handler.saving)
+		{
+			for(auto & idAndZone : zones)
+			{
+				auto guard = handler.enterStruct(encodeZoneId(idAndZone.first));
+				idAndZone.second->serializeJson(handler);
+			}
+		}
+		else
+		{
+			for(auto & idAndZone : zonesData->getCurrent().Struct())
+			{
+				auto guard = handler.enterStruct(idAndZone.first);
+				auto zone = std::make_shared<ZoneOptions>();
+				zone->setId(decodeZoneId(idAndZone.first));
+				zone->serializeJson(handler);
+				zones[zone->getId()] = zone;
+			}
+		}
+	}
+	if(!handler.saving)
+		afterLoad();
+}
+
+void CRmgTemplate::afterLoad()
+{
+	for(auto & idAndZone : zones)
+	{
+		auto zone = idAndZone.second;
+		if(zone->getMinesLikeZone() != ZoneOptions::NO_ZONE)
+		{
+			const auto otherZone = zones.at(zone->getMinesLikeZone());
+			zone->setMinesInfo(otherZone->getMinesInfo());
+		}
+
+		if(zone->getTerrainTypeLikeZone() != ZoneOptions::NO_ZONE)
+		{
+			const auto otherZone = zones.at(zone->getTerrainTypeLikeZone());
+			zone->setTerrainTypes(otherZone->getTerrainTypes());
+		}
+
+		if(zone->getTreasureLikeZone() != ZoneOptions::NO_ZONE)
+		{
+			const auto otherZone = zones.at(zone->getTreasureLikeZone());
+			zone->setTreasureInfo(otherZone->getTreasureInfo());
+		}
+	}
+
+	for(const auto & connection : connections)
+	{
+		auto id1 = connection.getZoneA();
+		auto id2 = connection.getZoneB();
+
+		auto zone1 = zones.at(id1);
+		auto zone2 = zones.at(id2);
+
+		zone1->addConnection(id2);
+		zone2->addConnection(id1);
+	}
+}
+
+void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName)
+{
+	static const std::map<std::string, int3> sizeMapping =
+	{
+		{"s",    { 36,  36, 1}},
+		{"s+u",  { 36,  36, 2}},
+		{"m",    { 72,  72, 1}},
+		{"m+u",  { 72,  72, 2}},
+		{"l",    {108, 108, 1}},
+		{"l+u",  {108, 108, 2}},
+		{"xl",   {144, 144, 1}},
+		{"xl+u", {144, 144, 2}},
+		{"h",    {180, 180, 1}},
+		{"h+u",  {180, 180, 2}},
+		{"xh",   {216, 216, 1}},
+		{"xh+u", {216, 216, 2}},
+		{"g",    {252, 252, 1}},
+		{"g+u",  {252, 252, 2}}
+	};
+
+	static const std::map<int3, std::string> sizeReverseMapping = vstd::invertMap(sizeMapping);
+
+	std::string encodedValue;
+
+	if(handler.saving)
+	{
+		auto iter = sizeReverseMapping.find(value);
+		if(iter == sizeReverseMapping.end())
+			encodedValue = boost::str(boost::format("%dx%dx%d") % value.x % value.y % value.z);
+		else
+			encodedValue = iter->second;
+	}
+
+	handler.serializeString(fieldName, encodedValue);
+
+	if(!handler.saving)
+	{
+		auto iter = sizeMapping.find(encodedValue);
+
+		if(iter == sizeMapping.end())
+		{
+			std::vector<std::string> parts;
+			boost::split(parts, encodedValue, boost::is_any_of("x"));
+
+			value.x = (boost::lexical_cast<int>(parts.at(0)));
+			value.y = (boost::lexical_cast<int>(parts.at(1)));
+			value.z = (boost::lexical_cast<int>(parts.at(2)));
+		}
+		else
+		{
+			value = iter->second;
+		}
+	}
+}
+
+void CRmgTemplate::serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName)
+{
+	std::string encodedValue;
+
+	if(handler.saving)
+		encodedValue = value.toString();
+
+	handler.serializeString(fieldName, encodedValue);
+
+	if(!handler.saving)
+		value.fromString(encodedValue);
+}
+

+ 58 - 71
lib/rmg/CRmgTemplate.h

@@ -10,6 +10,7 @@
 
 #pragma once
 
+#include "../int3.h"
 #include "../GameConstants.h"
 #include "../ResourceSet.h"
 #include "CMapGenOptions.h"
@@ -33,6 +34,11 @@ public:
 	ui32 min;
 	ui32 max;
 	ui16 density;
+	CTreasureInfo();
+
+	bool operator ==(const CTreasureInfo & other) const;
+
+	void serializeJson(JsonSerializeFormat & handler);
 };
 
 namespace rmg
@@ -44,12 +50,10 @@ public:
 	ZoneConnection();
 
 	TRmgTemplateZoneId getZoneA() const;
-	void setZoneA(TRmgTemplateZoneId value);
 	TRmgTemplateZoneId getZoneB() const;
-	void setZoneB(TRmgTemplateZoneId value);
-	int getGuardStrength() const; /// Default: 0
-	void setGuardStrength(int value);
+	int getGuardStrength() const;
 
+	void serializeJson(JsonSerializeFormat & handler);
 private:
 	TRmgTemplateZoneId zoneA;
 	TRmgTemplateZoneId zoneB;
@@ -60,23 +64,25 @@ class DLL_LINKAGE ZoneOptions
 {
 public:
 	static const std::set<ETerrainType> DEFAULT_TERRAIN_TYPES;
+	static const TRmgTemplateZoneId NO_ZONE;
 
 	class DLL_LINKAGE CTownInfo
 	{
 	public:
 		CTownInfo();
 
-		int getTownCount() const; /// Default: 0
-		void setTownCount(int value);
-		int getCastleCount() const; /// Default: 0
-		void setCastleCount(int value);
-		int getTownDensity() const; /// Default: 0
-		void setTownDensity(int value);
-		int getCastleDensity() const; /// Default: 0
-		void setCastleDensity(int value);
+		int getTownCount() const;
+		int getCastleCount() const;
+		int getTownDensity() const;
+		int getCastleDensity() const;
+
+		void serializeJson(JsonSerializeFormat & handler);
 
 	private:
-		int townCount, castleCount, townDensity, castleDensity;
+		int townCount;
+		int castleCount;
+		int townDensity;
+		int castleDensity;
 	};
 
 	ZoneOptions();
@@ -86,44 +92,34 @@ public:
 	TRmgTemplateZoneId getId() const;
 	void setId(TRmgTemplateZoneId value);
 
-	ETemplateZoneType::ETemplateZoneType getType() const; /// Default: ETemplateZoneType::PLAYER_START
-	void setType(ETemplateZoneType::ETemplateZoneType value);
-
-	int getSize() const; /// Default: 1
+	ETemplateZoneType::ETemplateZoneType getType() const;
+	int getSize() const;
 	void setSize(int value);
-
 	boost::optional<int> getOwner() const;
-	void setOwner(boost::optional<int> value);
-
-	const CTownInfo & getPlayerTowns() const;
-	void setPlayerTowns(const CTownInfo & value);
-	const CTownInfo & getNeutralTowns() const;
-	void setNeutralTowns(const CTownInfo & value);
 
-	bool getMatchTerrainToTown() const; /// Default: true
-	void setMatchTerrainToTown(bool value);
-
-	const std::set<ETerrainType> & getTerrainTypes() const; /// Default: all
+	const std::set<ETerrainType> & getTerrainTypes() const;
 	void setTerrainTypes(const std::set<ETerrainType> & value);
 
-	bool getTownsAreSameType() const; /// Default: false
-	void setTownsAreSameType(bool value);
-
 	std::set<TFaction> getDefaultTownTypes() const;
+	const std::set<TFaction> & getTownTypes() const;
 
-	const std::set<TFaction> & getTownTypes() const; /// Default: all
 	void setTownTypes(const std::set<TFaction> & value);
 	void setMonsterTypes(const std::set<TFaction> & value);
 
-	void setMonsterStrength(EMonsterStrength::EMonsterStrength val);
-
-	void setMinesAmount (TResource res, ui16 amount);
+	void setMinesInfo(const std::map<TResource, ui16> & value);
 	std::map<TResource, ui16> getMinesInfo() const;
 
-	void addTreasureInfo(const CTreasureInfo & info);
+	void setTreasureInfo(const std::vector<CTreasureInfo> & value);
 	const std::vector<CTreasureInfo> & getTreasureInfo() const;
 
+	TRmgTemplateZoneId getMinesLikeZone() const;
+	TRmgTemplateZoneId getTerrainTypeLikeZone() const;
+	TRmgTemplateZoneId getTreasureLikeZone() const;
+
 	void addConnection(TRmgTemplateZoneId otherZone);
+	std::vector<TRmgTemplateZoneId> getConnections() const;
+
+	void serializeJson(JsonSerializeFormat & handler);
 
 protected:
 	TRmgTemplateZoneId id;
@@ -146,6 +142,10 @@ protected:
 	std::vector<CTreasureInfo> treasureInfo;
 
 	std::vector<TRmgTemplateZoneId> connections; //list of adjacent zones
+
+	TRmgTemplateZoneId minesLikeZone;
+	TRmgTemplateZoneId terrainTypeLikeZone;
+	TRmgTemplateZoneId treasureLikeZone;
 };
 
 }
@@ -154,27 +154,9 @@ protected:
 class DLL_LINKAGE CRmgTemplate
 {
 public:
-	class CSize
-	{
-	public:
-		CSize();
-		CSize(int width, int height, bool under);
-
-		int getWidth() const; /// Default: CMapHeader::MAP_SIZE_MIDDLE
-		void setWidth(int value);
-		int getHeight() const; /// Default: CMapHeader::MAP_SIZE_MIDDLE
-		void setHeight(int value);
-		bool getUnder() const; /// Default: true
-		void setUnder(bool value);
-		bool operator<=(const CSize & value) const;
-		bool operator>=(const CSize & value) const;
-
-	private:
-		int width, height;
-		bool under;
-	};
+	using Zones = std::map<TRmgTemplateZoneId, std::shared_ptr<rmg::ZoneOptions>>;
 
-	class CPlayerCountRange
+	class DLL_LINKAGE CPlayerCountRange
 	{
 	public:
 		void addRange(int lower, int upper);
@@ -182,34 +164,39 @@ public:
 		bool isInRange(int count) const;
 		std::set<int> getNumbers() const;
 
+		std::string toString() const;
+		void fromString(const std::string & value);
+
 	private:
-		std::list<std::pair<int, int> > range;
+		std::vector<std::pair<int, int> > range;
 	};
 
 	CRmgTemplate();
 	~CRmgTemplate();
 
+	bool matchesSize(const int3 & value) const;
+
+	void setId(const std::string & value);
 	const std::string & getName() const;
-	void setName(const std::string & value);
-	const CSize & getMinSize() const;
-	void setMinSize(const CSize & value);
-	const CSize & getMaxSize() const;
-	void setMaxSize(const CSize & value);
+
 	const CPlayerCountRange & getPlayers() const;
-	void setPlayers(const CPlayerCountRange & value);
 	const CPlayerCountRange & getCpuPlayers() const;
-	void setCpuPlayers(const CPlayerCountRange & value);
-	const std::map<TRmgTemplateZoneId, rmg::ZoneOptions *> & getZones() const;
-	void setZones(const std::map<TRmgTemplateZoneId, rmg::ZoneOptions *> & value);
-	const std::list<rmg::ZoneConnection> & getConnections() const;
-	void setConnections(const std::list<rmg::ZoneConnection> & value);
+	const Zones & getZones() const;
+	const std::vector<rmg::ZoneConnection> & getConnections() const;
 
 	void validate() const; /// Tests template on validity and throws exception on failure
 
+	void serializeJson(JsonSerializeFormat & handler);
+
 private:
+	std::string id;
 	std::string name;
-	CSize minSize, maxSize;
+	int3 minSize, maxSize;
 	CPlayerCountRange players, cpuPlayers;
-	std::map<TRmgTemplateZoneId, rmg::ZoneOptions *> zones;
-	std::list<rmg::ZoneConnection> connections;
+	Zones zones;
+	std::vector<rmg::ZoneConnection> connections;
+
+	void afterLoad();
+	void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName);
+	void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName);
 };

+ 8 - 333
lib/rmg/CRmgTemplateStorage.cpp

@@ -9,16 +9,11 @@
  */
 
 #include "StdInc.h"
+
 #include "CRmgTemplateStorage.h"
+#include "CRmgTemplate.h"
 
-#include "../filesystem/Filesystem.h"
-#include "../JsonNode.h"
-#include "../mapping/CMap.h"
-#include "../VCMI_Lib.h"
-#include "../CModHandler.h"
-#include "../CTownHandler.h"
-#include "../GameConstants.h"
-#include "../StringConstants.h"
+#include "../serializer/JsonDeserializer.h"
 
 using namespace rmg;
 
@@ -38,198 +33,12 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 	auto tpl = new CRmgTemplate();
 	try
 	{
-		const auto & templateNode = data;
-		if (!templateNode["name"].isNull())
-			tpl->setName(templateNode["name"].String()); //name can be customised. Allow duplicated names for different template versions.
-		else
-			tpl->setName(name); //identifier becomes default name
-
-		// Parse main template data
-		tpl->setMinSize(parseMapTemplateSize(templateNode["minSize"].String()));
-		tpl->setMaxSize(parseMapTemplateSize(templateNode["maxSize"].String()));
-		tpl->setPlayers(parsePlayers(templateNode["players"].String()));
-		tpl->setCpuPlayers(parsePlayers(templateNode["cpu"].String()));
-
-		// Parse zones
-		std::map<TRmgTemplateZoneId, ZoneOptions *> zones;
-		for (const auto & zonePair : templateNode["zones"].Struct())
-		{
-			auto zone = new ZoneOptions();
-			auto zoneId = boost::lexical_cast<TRmgTemplateZoneId>(zonePair.first);
-			zone->setId(zoneId);
-
-			const auto & zoneNode = zonePair.second;
-			zone->setType(parseZoneType(zoneNode["type"].String()));
-			zone->setSize(zoneNode["size"].Float());
-			if (!zoneNode["owner"].isNull()) zone->setOwner(zoneNode["owner"].Float());
-
-			zone->setPlayerTowns(parseTemplateZoneTowns(zoneNode["playerTowns"]));
-			zone->setNeutralTowns(parseTemplateZoneTowns(zoneNode["neutralTowns"]));
-			if (!zoneNode["matchTerrainToTown"].isNull()) //default : true
-				zone->setMatchTerrainToTown(zoneNode["matchTerrainToTown"].Bool());
-			zone->setTerrainTypes(parseTerrainTypes(zoneNode["terrainTypes"].Vector(), ZoneOptions::DEFAULT_TERRAIN_TYPES));
-
-			if (!zoneNode["townsAreSameType"].isNull()) //default : false
-				zone->setTownsAreSameType((zoneNode["townsAreSameType"].Bool()));
-
-			for (int i = 0; i < 2; ++i)
-			{
-				std::set<TFaction> allowedTownTypes;
-				if (i)
-				{
-					if (zoneNode["allowedTowns"].isNull())
-						allowedTownTypes = zone->getDefaultTownTypes();
-				}
-				else
-				{
-					if (zoneNode["allowedMonsters"].isNull())
-						allowedTownTypes = VLC->townh->getAllowedFactions(false);
-				}
-
-				if (allowedTownTypes.empty())
-				{
-					for (const JsonNode & allowedTown : zoneNode[i ? "allowedTowns" : "allowedMonsters"].Vector())
-					{
-						//complain if the town type is not present in our game
-						if (auto id = VLC->modh->identifiers.getIdentifier("faction", allowedTown, false))
-							allowedTownTypes.insert(id.get());
-					}
-				}
-
-				if (!zoneNode[i ? "bannedTowns" : "bannedMonsters"].isNull())
-				{
-					for (const JsonNode & bannedTown : zoneNode[i ? "bannedTowns" : "bannedMonsters"].Vector())
-					{
-						//erase unindentified towns silently
-						if (auto id = VLC->modh->identifiers.getIdentifier("faction", bannedTown, true))
-							vstd::erase_if_present(allowedTownTypes, id.get());
-					}
-				}
-				if (i)
-					zone->setTownTypes(allowedTownTypes);
-				else
-					zone->setMonsterTypes(allowedTownTypes);
-			}
-
-			const std::string monsterStrength = zoneNode["monsters"].String();
-			if (monsterStrength == "weak")
-				zone->setMonsterStrength(EMonsterStrength::ZONE_WEAK);
-			else if (monsterStrength == "normal")
-				zone->setMonsterStrength(EMonsterStrength::ZONE_NORMAL);
-			else if (monsterStrength == "strong")
-				zone->setMonsterStrength(EMonsterStrength::ZONE_STRONG);
-			else
-			{
-				delete zone;
-				throw (std::runtime_error("incorrect monster power"));
-			}
-
-			if (!zoneNode["mines"].isNull())
-			{
-				auto mines = zoneNode["mines"].Struct();
-				//FIXME: maybe there is a smarter way to parse it already?
-				zone->setMinesAmount(Res::WOOD, mines["wood"].Float());
-				zone->setMinesAmount(Res::ORE, mines["ore"].Float());
-				zone->setMinesAmount(Res::GEMS, mines["gems"].Float());
-				zone->setMinesAmount(Res::CRYSTAL, mines["crystal"].Float());
-				zone->setMinesAmount(Res::SULFUR, mines["sulfur"].Float());
-				zone->setMinesAmount(Res::MERCURY, mines["mercury"].Float());
-				zone->setMinesAmount(Res::GOLD, mines["gold"].Float());
-				//TODO: Mithril
-			}
-
-			//treasures
-			if (!zoneNode["treasure"].isNull())
-			{
-				//TODO: parse vector of different treasure settings
-				if (zoneNode["treasure"].getType() == JsonNode::JsonType::DATA_STRUCT)
-				{
-					auto treasureInfo = zoneNode["treasure"].Struct();
-					{
-						CTreasureInfo ti;
-						ti.min = treasureInfo["min"].Float();
-						ti.max = treasureInfo["max"].Float();
-						ti.density = treasureInfo["density"].Float(); //TODO: use me
-						zone->addTreasureInfo(ti);
-					}
-				}
-				else if (zoneNode["treasure"].getType() == JsonNode::JsonType::DATA_VECTOR)
-				{
-					for (auto treasureInfo : zoneNode["treasure"].Vector())
-					{
-						CTreasureInfo ti;
-						ti.min = treasureInfo["min"].Float();
-						ti.max = treasureInfo["max"].Float();
-						ti.density = treasureInfo["density"].Float();
-						zone->addTreasureInfo(ti);
-					}
-				}
-			}
-
-			zones[zone->getId()] = zone;
-		}
-
-		//copy settings from already parsed zones
-		for (const auto & zonePair : templateNode["zones"].Struct())
-		{
-			auto zoneId = boost::lexical_cast<TRmgTemplateZoneId>(zonePair.first);
-			auto zone = zones[zoneId];
-
-			const auto & zoneNode = zonePair.second;
-
-			if (!zoneNode["terrainTypeLikeZone"].isNull())
-			{
-				int id = zoneNode["terrainTypeLikeZone"].Float();
-				zone->setTerrainTypes(zones[id]->getTerrainTypes());
-				zone->setMatchTerrainToTown(zones[id]->getMatchTerrainToTown());
-			}
-
-			if (!zoneNode["townTypeLikeZone"].isNull())
-				zone->setTownTypes (zones[zoneNode["townTypeLikeZone"].Float()]->getTownTypes());
-
-			if (!zoneNode["treasureLikeZone"].isNull())
-			{
-				for (auto treasureInfo : zones[zoneNode["treasureLikeZone"].Float()]->getTreasureInfo())
-				{
-					zone->addTreasureInfo(treasureInfo);
-				}
-			}
-
-			if (!zoneNode["minesLikeZone"].isNull())
-			{
-				for (auto mineInfo : zones[zoneNode["minesLikeZone"].Float()]->getMinesInfo())
-				{
-					zone->setMinesAmount (mineInfo.first, mineInfo.second);
-				}
-
-			}
-		}
-
-		tpl->setZones(zones);
-
-		// Parse connections
-		std::list<ZoneConnection> connections;
-		for(const auto & connPair : templateNode["connections"].Vector())
-		{
-			ZoneConnection conn;
-			conn.setZoneA(zones.find(boost::lexical_cast<TRmgTemplateZoneId>(connPair["a"].String()))->second->getId());
-			conn.setZoneB(zones.find(boost::lexical_cast<TRmgTemplateZoneId>(connPair["b"].String()))->second->getId());
-			conn.setGuardStrength(connPair["guard"].Float());
-			connections.push_back(conn);
-		}
-		tpl->setConnections(connections);
-		{
-			auto zones = tpl->getZones();
-			for (auto con : tpl->getConnections())
-			{
-				auto idA = con.getZoneA();
-				auto idB = con.getZoneB();
-				zones[idA]->addConnection(idB);
-				zones[idB]->addConnection(idA);
-			}
-		}
+		JsonDeserializer handler(nullptr, data);
+		auto fullKey = normalizeIdentifier(scope, "core", name);
+		tpl->setId(name);
+		tpl->serializeJson(handler);
 		tpl->validate();
-		templates[tpl->getName()] = tpl;
+		templates[fullKey] = tpl;
 	}
 	catch(const std::exception & e)
 	{
@@ -237,142 +46,8 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 	}
 }
 
-CRmgTemplate::CSize CRmgTemplateStorage::parseMapTemplateSize(const std::string & text) const
-{
-	CRmgTemplate::CSize size;
-	if(text.empty()) return size;
-
-	std::vector<std::string> parts;
-	boost::split(parts, text, boost::is_any_of("+"));
-	static const std::map<std::string, int> mapSizeMapping =
-	{
-		{"s", CMapHeader::MAP_SIZE_SMALL},
-		{"m", CMapHeader::MAP_SIZE_MIDDLE},
-		{"l", CMapHeader::MAP_SIZE_LARGE},
-		{"xl", CMapHeader::MAP_SIZE_XLARGE},
-	};
-	auto it = mapSizeMapping.find(parts[0]);
-	if(it == mapSizeMapping.end())
-	{
-		// Map size is given as a number representation
-		const std::string numericalRep = parts[0];
-		parts.clear();
-		boost::split(parts, numericalRep, boost::is_any_of("x"));
-		assert(parts.size() == 3);
-		size.setWidth(boost::lexical_cast<int>(parts[0]));
-		size.setHeight(boost::lexical_cast<int>(parts[1]));
-		size.setUnder(boost::lexical_cast<int>(parts[2]) == 1);
-	}
-	else
-	{
-		size.setWidth(it->second);
-		size.setHeight(it->second);
-		size.setUnder(parts.size() > 1 ? parts[1] == std::string("u") : false);
-	}
-	return size;
-}
-
-ETemplateZoneType::ETemplateZoneType CRmgTemplateStorage::parseZoneType(const std::string & type) const
-{
-	static const std::map<std::string, ETemplateZoneType::ETemplateZoneType> zoneTypeMapping =
-	{
-		{"playerStart", ETemplateZoneType::PLAYER_START},
-		{"cpuStart", ETemplateZoneType::CPU_START},
-		{"treasure", ETemplateZoneType::TREASURE},
-		{"junction", ETemplateZoneType::JUNCTION},
-	};
-	auto it = zoneTypeMapping.find(type);
-	if(it == zoneTypeMapping.end()) throw std::runtime_error("Zone type unknown.");
-	return it->second;
-}
-
-ZoneOptions::CTownInfo CRmgTemplateStorage::parseTemplateZoneTowns(const JsonNode & node) const
-{
-	ZoneOptions::CTownInfo towns;
-	towns.setTownCount(node["towns"].Float());
-	towns.setCastleCount(node["castles"].Float());
-	towns.setTownDensity(node["townDensity"].Float());
-	towns.setCastleDensity(node["castleDensity"].Float());
-	return towns;
-}
-
-std::set<TFaction> CRmgTemplateStorage::parseTownTypes(const JsonVector & townTypesVector, const std::set<TFaction> & defaultTownTypes) const
-{
-	std::set<TFaction> townTypes;
-	for(const auto & townTypeNode : townTypesVector)
-	{
-		auto townTypeStr = townTypeNode.String();
-		if(townTypeStr == "all") return defaultTownTypes;
-
-		bool foundFaction = false;
-		for(auto factionPtr : VLC->townh->factions)
-		{
-			if(factionPtr->town != nullptr && townTypeStr == factionPtr->name)
-			{
-				townTypes.insert(factionPtr->index);
-				foundFaction = true;
-			}
-		}
-		if(!foundFaction) throw std::runtime_error("Given faction is invalid.");
-	}
-	return townTypes;
-}
-
-std::set<ETerrainType> CRmgTemplateStorage::parseTerrainTypes(const JsonVector & terTypeStrings, const std::set<ETerrainType> & defaultTerrainTypes) const
-{
-	std::set<ETerrainType> terTypes;
-	if (terTypeStrings.empty()) //nothing was specified
-		return defaultTerrainTypes;
-
-	for(const auto & node : terTypeStrings)
-	{
-		const auto & terTypeStr = node.String();
-		if(terTypeStr == "all") return defaultTerrainTypes;
-		auto pos = vstd::find_pos(GameConstants::TERRAIN_NAMES, terTypeStr);
-		if (pos != -1)
-		{
-			terTypes.insert(ETerrainType(pos));
-		}
-		else
-		{
-			throw std::runtime_error("Terrain type is invalid.");
-		}
-	}
-	return terTypes;
-}
-
-CRmgTemplate::CPlayerCountRange CRmgTemplateStorage::parsePlayers(const std::string & players) const
-{
-	CRmgTemplate::CPlayerCountRange playerRange;
-	if(players.empty())
-	{
-		playerRange.addNumber(0);
-		return playerRange;
-	}
-	std::vector<std::string> commaParts;
-	boost::split(commaParts, players, boost::is_any_of(","));
-	for(const auto & commaPart : commaParts)
-	{
-		std::vector<std::string> rangeParts;
-		boost::split(rangeParts, commaPart, boost::is_any_of("-"));
-		if(rangeParts.size() == 2)
-		{
-			auto lower = boost::lexical_cast<int>(rangeParts[0]);
-			auto upper = boost::lexical_cast<int>(rangeParts[1]);
-			playerRange.addRange(lower, upper);
-		}
-		else if(rangeParts.size() == 1)
-		{
-			auto val = boost::lexical_cast<int>(rangeParts.front());
-			playerRange.addNumber(val);
-		}
-	}
-	return playerRange;
-}
-
 CRmgTemplateStorage::CRmgTemplateStorage()
 {
-	//TODO: load all
 }
 
 CRmgTemplateStorage::~CRmgTemplateStorage()

+ 1 - 11
lib/rmg/CRmgTemplateStorage.h

@@ -10,12 +10,10 @@
 
 #pragma once
 
-#include "CRmgTemplate.h"
 #include "../IHandlerBase.h"
 
 class JsonNode;
-
-typedef std::vector<JsonNode> JsonVector;
+class CRmgTemplate;
 
 /// The CJsonRmgTemplateLoader loads templates from a JSON file.
 class DLL_LINKAGE CRmgTemplateStorage : public IHandlerBase
@@ -34,14 +32,6 @@ public:
 	virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
 
 private:
-	CRmgTemplate::CSize parseMapTemplateSize(const std::string & text) const;
-	rmg::ZoneOptions::CTownInfo parseTemplateZoneTowns(const JsonNode & node) const;
-	ETemplateZoneType::ETemplateZoneType parseZoneType(const std::string & type) const;
-	std::set<TFaction> parseTownTypes(const JsonVector & townTypesVector, const std::set<TFaction> & defaultTownTypes) const;
-	std::set<ETerrainType> parseTerrainTypes(const JsonVector & terTypeStrings, const std::set<ETerrainType> & defaultTerrainTypes) const;
-	CRmgTemplate::CPlayerCountRange parsePlayers(const std::string & players) const;
-
-protected:
 	std::map<std::string, CRmgTemplate *> templates;
 };
 

+ 6 - 11
lib/rmg/CRmgTemplateZone.cpp

@@ -108,7 +108,7 @@ CRmgTemplateZone::CRmgTemplateZone()
 	townType(ETownType::NEUTRAL),
 	terrainType (ETerrainType::GRASS),
 	minGuardedValue(0),
-	questArtZone(nullptr),
+	questArtZone(),
 	gen(nullptr)
 {
 
@@ -124,16 +124,11 @@ void CRmgTemplateZone::setGenPtr(CMapGenerator * Gen)
 	gen = Gen;
 }
 
-void CRmgTemplateZone::setQuestArtZone(CRmgTemplateZone * otherZone)
+void CRmgTemplateZone::setQuestArtZone(std::shared_ptr<CRmgTemplateZone> otherZone)
 {
 	questArtZone = otherZone;
 }
 
-std::vector<TRmgTemplateZoneId> CRmgTemplateZone::getConnections() const
-{
-	return connections;
-}
-
 std::set<int3>* CRmgTemplateZone::getFreePaths()
 {
 	return &freePaths;
@@ -2488,7 +2483,7 @@ void CRmgTemplateZone::addAllPossibleObjects()
 
 	//seer huts with creatures or generic rewards
 
-	if (questArtZone) //we won't be placing seer huts if there is no zone left to place arties
+	if(questArtZone.lock()) //we won't be placing seer huts if there is no zone left to place arties
 	{
 		static const int genericSeerHuts = 8;
 		int seerHutsPerType = 0;
@@ -2549,7 +2544,7 @@ void CRmgTemplateZone::addAllPossibleObjects()
 
 				gen->banQuestArt(artid);
 
-				this->questArtZone->possibleObjects.push_back (generateArtInfo(artid));
+				this->questArtZone.lock()->possibleObjects.push_back (generateArtInfo(artid));
 
 				return obj;
 			};
@@ -2587,7 +2582,7 @@ void CRmgTemplateZone::addAllPossibleObjects()
 
 				gen->banQuestArt(artid);
 
-				this->questArtZone->possibleObjects.push_back(generateArtInfo(artid));
+				this->questArtZone.lock()->possibleObjects.push_back(generateArtInfo(artid));
 
 				return obj;
 			};
@@ -2610,7 +2605,7 @@ void CRmgTemplateZone::addAllPossibleObjects()
 
 				gen->banQuestArt(artid);
 
-				this->questArtZone->possibleObjects.push_back(generateArtInfo(artid));
+				this->questArtZone.lock()->possibleObjects.push_back(generateArtInfo(artid));
 
 				return obj;
 			};

+ 2 - 3
lib/rmg/CRmgTemplateZone.h

@@ -136,8 +136,7 @@ public:
 	std::vector<int3> getAccessibleOffsets (const CGObjectInstance* object);
 	bool areAllTilesAvailable(CGObjectInstance* obj, int3& tile, std::set<int3>& tilesBlockedByObject) const;
 
-	void setQuestArtZone(CRmgTemplateZone * otherZone);
-	std::vector<TRmgTemplateZoneId> getConnections() const;
+	void setQuestArtZone(std::shared_ptr<CRmgTemplateZone> otherZone);
 	std::set<int3>* getFreePaths();
 
 	ObjectInfo getRandomObject (CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue);
@@ -166,7 +165,7 @@ private:
 
 	si32 townType;
 	ETerrainType terrainType;
-	CRmgTemplateZone * questArtZone; //artifacts required for Seer Huts will be placed here - or not if null
+	std::weak_ptr<CRmgTemplateZone> questArtZone; //artifacts required for Seer Huts will be placed here - or not if null
 
 	std::vector<ObjectInfo> possibleObjects;
 	int minGuardedValue;

+ 5 - 10
lib/rmg/CZonePlacer.cpp

@@ -18,11 +18,6 @@
 
 class CRandomGenerator;
 
-CPlacedZone::CPlacedZone(const CRmgTemplateZone * zone)
-{
-
-}
-
 CZonePlacer::CZonePlacer(CMapGenerator * Gen)
 	: width(0), height(0), scaleX(0), scaleY(0), mapSize(0), gravityConstant(0), stiffnessConstant(0),
 	gen(Gen)
@@ -78,7 +73,7 @@ void CZonePlacer::placeZones(const CMapGenOptions * mapGenOptions, CRandomGenera
 	float bestTotalDistance = 1e10;
 	float bestTotalOverlap = 1e10;
 
-	std::map<CRmgTemplateZone *, float3> bestSolution;
+	std::map<std::shared_ptr<CRmgTemplateZone>, float3> bestSolution;
 
 	TForceVector forces;
 	TForceVector totalForces; //  both attraction and pushback, overcomplicated?
@@ -359,7 +354,7 @@ void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDista
 {
 	float maxRatio = 0;
 	const int maxDistanceMovementRatio = zones.size() * zones.size(); //experimental - the more zones, the greater total distance expected
-	CRmgTemplateZone * misplacedZone = nullptr;
+	std::shared_ptr<CRmgTemplateZone> misplacedZone;
 
 	float totalDistance = 0;
 	float totalOverlap = 0;
@@ -379,7 +374,7 @@ void CZonePlacer::moveOneZone(TZoneMap &zones, TForceVector &totalForces, TDista
 
 	if (maxRatio > maxDistanceMovementRatio && misplacedZone)
 	{
-		CRmgTemplateZone * targetZone = nullptr;
+		std::shared_ptr<CRmgTemplateZone> targetZone;
 		float3 ourCenter = misplacedZone->getCenter();
 
 		if (totalDistance > totalOverlap)
@@ -471,7 +466,7 @@ void CZonePlacer::assignZones(const CMapGenOptions * mapGenOptions)
 
 	auto zones = gen->getZones();
 
-	typedef std::pair<CRmgTemplateZone *, float> Dpair;
+	typedef std::pair<std::shared_ptr<CRmgTemplateZone>, float> Dpair;
 	std::vector <Dpair> distances;
 	distances.reserve(zones.size());
 
@@ -483,7 +478,7 @@ void CZonePlacer::assignZones(const CMapGenOptions * mapGenOptions)
 		return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize();
 	};
 
-	auto moveZoneToCenterOfMass = [](CRmgTemplateZone * zone) -> void
+	auto moveZoneToCenterOfMass = [](std::shared_ptr<CRmgTemplateZone> zone) -> void
 	{
 		int3 total(0, 0, 0);
 		auto tiles = zone->getTileInfo();

+ 4 - 16
lib/rmg/CZonePlacer.h

@@ -20,22 +20,10 @@ class CRandomGenerator;
 class CRmgTemplateZone;
 class CMapGenerator;
 
-typedef std::vector<std::pair<TRmgTemplateZoneId, CRmgTemplateZone*>> TZoneVector;
-typedef std::map <TRmgTemplateZoneId, CRmgTemplateZone*> TZoneMap;
-typedef std::map <CRmgTemplateZone *, float3> TForceVector;
-typedef std::map <CRmgTemplateZone *, float> TDistanceVector;
-
-class CPlacedZone
-{
-public:
-	explicit CPlacedZone(const CRmgTemplateZone * Zone);
-
-private:
-    //const CRmgTemplateZone * zone;
-
-	//TODO exact outline data of zone
-	//TODO perhaps further zone data, guards, obstacles, etc...
-};
+typedef std::vector<std::pair<TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>>> TZoneVector;
+typedef std::map <TRmgTemplateZoneId, std::shared_ptr<CRmgTemplateZone>> TZoneMap;
+typedef std::map <std::shared_ptr<CRmgTemplateZone>, float3> TForceVector;
+typedef std::map <std::shared_ptr<CRmgTemplateZone>, float> TDistanceVector;
 
 class CZonePlacer
 {

+ 88 - 8
lib/serializer/JsonSerializeFormat.h

@@ -232,6 +232,7 @@ public:
 	};
 
 	///Anything int64-convertible <-> Json integer
+	///no default value
 	template <typename T>
 	void serializeInt(const std::string & fieldName, T & value)
 	{
@@ -239,12 +240,21 @@ public:
 	};
 
 	///Anything int64-convertible <-> Json integer
+	///custom default value
 	template <typename T, typename U>
 	void serializeInt(const std::string & fieldName, T & value, const U & defaultValue)
 	{
 		doSerializeInternal<T, U, si64>(fieldName, value, defaultValue);
 	};
 
+	///Anything int64-convertible <-> Json integer
+	///default value is boost::none
+	template <typename T>
+	void serializeInt(const std::string & fieldName, boost::optional<T> & value)
+	{
+		dispatchOptional<T, si64>(fieldName, value);
+	};
+
 	///si32-convertible identifier <-> Json string
 	template <typename T, typename U>
 	void serializeId(const std::string & fieldName, T & value, const U & defaultValue, const TDecoder & decoder, const TEncoder & encoder)
@@ -253,14 +263,14 @@ public:
 	}
 
 	///si32-convertible identifier <-> Json string
-	template <typename T, typename U>
+	template <typename T, typename U, typename E = T>
 	void serializeId(const std::string & fieldName, T & value, const U & defaultValue)
 	{
-		doSerializeInternal<T, U, si32>(fieldName, value, defaultValue, &T::decode, &T::encode);
+		doSerializeInternal<T, U, si32>(fieldName, value, defaultValue, &E::decode, &E::encode);
 	}
 
 	///si32-convertible identifier vector <-> Json array of string
-	template <typename T, typename U = T>
+	template <typename T, typename E = T>
 	void serializeIdArray(const std::string & fieldName, std::vector<T> & value)
 	{
 		std::vector<si32> temp;
@@ -276,7 +286,7 @@ public:
 			}
 		}
 
-		serializeInternal(fieldName, temp, &U::decode, &U::encode);
+		serializeInternal(fieldName, temp, &E::decode, &E::encode);
 		if(!saving)
 		{
 			value.clear();
@@ -320,6 +330,43 @@ public:
 		}
 	}
 
+	///si32-convertible identifier set <-> Json array of string
+	template <typename T, typename U = T>
+	void serializeIdArray(const std::string & fieldName, std::set<T> & value, const std::set<T> & defaultValue)
+	{
+		std::vector<si32> temp;
+
+		if(saving && value != defaultValue)
+		{
+			temp.reserve(value.size());
+
+			for(const T & vitem : value)
+			{
+				si32 item = static_cast<si32>(vitem);
+				temp.push_back(item);
+			}
+		}
+
+		serializeInternal(fieldName, temp, &U::decode, &U::encode);
+		if(!saving)
+		{
+			if(temp.empty())
+			{
+				value = defaultValue;
+			}
+			else
+			{
+				value.clear();
+
+				for(const si32 item : temp)
+				{
+					T vitem = static_cast<T>(item);
+					value.insert(vitem);
+				}
+			}
+		}
+	}
+
 	///bitmask <-> Json array of string
 	template <typename T, int Size>
 	void serializeIdArray(const std::string & fieldName, T & value, const T & defaultValue, const TDecoder & decoder, const TEncoder & encoder)
@@ -397,6 +444,7 @@ protected:
 	virtual void pushStruct(const std::string & fieldName) = 0;
 	virtual void pushArray(const std::string & fieldName) = 0;
 	virtual void pushArrayElement(const size_t index) = 0;
+	virtual void pushField(const std::string & fieldName) = 0;
 
 	virtual void resizeCurrent(const size_t newSize, JsonNode::JsonType type){};
 
@@ -406,9 +454,9 @@ protected:
 private:
 	const IInstanceResolver * instanceResolver;
 
-    template <typename VType, typename DVType, typename IType, typename... Args>
-    void doSerializeInternal(const std::string & fieldName, VType & value, const boost::optional<DVType> & defaultValue, Args ... args)
-    {
+	template <typename VType, typename DVType, typename IType, typename... Args>
+	void doSerializeInternal(const std::string & fieldName, VType & value, const boost::optional<DVType> & defaultValue, Args ... args)
+	{
 		const boost::optional<IType> tempDefault = defaultValue ? boost::optional<IType>(static_cast<IType>(defaultValue.get())) : boost::none;
 		IType temp = static_cast<IType>(value);
 
@@ -416,7 +464,39 @@ private:
 
 		if(!saving)
 			value = static_cast<VType>(temp);
-    }
+	}
+
+	template <typename VType, typename IType, typename... Args>
+	void dispatchOptional(const std::string & fieldName, boost::optional<VType> & value, Args ... args)
+	{
+		if(saving)
+		{
+			if(value)
+			{
+				IType temp = static_cast<IType>(value.get());
+				pushField(fieldName);
+				serializeInternal(temp, args...);
+				pop();
+			}
+		}
+		else
+		{
+			pushField(fieldName);
+
+			if(getCurrent().getType() == JsonNode::JsonType::DATA_NULL)
+			{
+				value = boost::none;
+			}
+			else
+			{
+				IType temp = IType();
+				serializeInternal(temp, args...);
+				value = boost::make_optional(temp);
+			}
+
+			pop();
+		}
+	}
 
 	friend class JsonSerializeHelper;
 	friend class JsonStructSerializer;

+ 2 - 4
lib/serializer/JsonSerializer.cpp

@@ -69,14 +69,12 @@ void JsonSerializer::serializeInternal(const std::string & fieldName, std::vecto
 
 void JsonSerializer::serializeInternal(std::string & value)
 {
-	if(value != "")
-		currentObject->String() = value;
+	currentObject->String() = value;
 }
 
 void JsonSerializer::serializeInternal(int64_t & value)
 {
-	if(value != 0)
-		currentObject->Integer() = value;
+	currentObject->Integer() = value;
 }
 
 void JsonSerializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector<bool> & standard, std::vector<bool> & value)

+ 5 - 0
lib/serializer/JsonTreeSerializer.h

@@ -54,6 +54,11 @@ protected:
 		pushObject(&(currentObject->Vector().at(index)));
 	}
 
+	void pushField(const std::string & fieldName) override
+	{
+		pushObject(fieldName);
+	}
+
 private:
 	void pushObject(const std::string & fieldName)
 	{

+ 2 - 0
test/JsonComparer.cpp

@@ -46,6 +46,8 @@ bool JsonComparer::isEmpty(const JsonNode & value)
 		return !value.Bool();
 	case JsonNode::JsonType::DATA_FLOAT:
 		return value.Float() == 0;
+	case JsonNode::JsonType::DATA_INTEGER:
+		return value.Integer() == 0;
 	case JsonNode::JsonType::DATA_STRING:
 		return value.String() == "";
 	case JsonNode::JsonType::DATA_VECTOR:

+ 2 - 0
test/Test.cbp

@@ -111,6 +111,7 @@
 		<Unit filename="mock/mock_spells_Problem.h" />
 		<Unit filename="mock/mock_spells_Spell.h" />
 		<Unit filename="mock/mock_vstd_RNG.h" />
+		<Unit filename="rmg/CRmgTemplateTest.cpp" />
 		<Unit filename="spells/AbilityCasterTest.cpp" />
 		<Unit filename="spells/TargetConditionTest.cpp" />
 		<Unit filename="spells/effects/CatapultTest.cpp" />
@@ -137,6 +138,7 @@
 		<Unit filename="spells/targetConditions/SpellEffectConditionTest.cpp" />
 		<Unit filename="spells/targetConditions/TargetConditionItemFixture.cpp" />
 		<Unit filename="spells/targetConditions/TargetConditionItemFixture.h" />
+		<Unit filename="testdata/rmg/1.json" />
 		<Extensions>
 			<code_completion />
 			<envvars />

+ 7 - 7
test/map/CMapFormatTest.cpp

@@ -9,15 +9,15 @@
  */
 #include "StdInc.h"
 
-#include "../lib/JsonDetail.h"
+#include "../../lib/JsonDetail.h"
 
-#include "../lib/filesystem/CMemoryBuffer.h"
-#include "../lib/filesystem/Filesystem.h"
+#include "../../lib/filesystem/CMemoryBuffer.h"
+#include "../../lib/filesystem/Filesystem.h"
 
-#include "../lib/mapping/CMap.h"
-#include "../lib/rmg/CMapGenOptions.h"
-#include "../lib/rmg/CMapGenerator.h"
-#include "../lib/mapping/MapFormatJson.h"
+#include "../../lib/mapping/CMap.h"
+#include "../../lib/rmg/CMapGenOptions.h"
+#include "../../lib/rmg/CMapGenerator.h"
+#include "../../lib/mapping/MapFormatJson.h"
 
 #include "../lib/VCMIDirs.h"
 

+ 0 - 1
test/map/MapComparer.cpp

@@ -144,7 +144,6 @@ void checkEqual(const TerrainTile & actual, const TerrainTile & expected)
 
 	VCMI_REQUIRE_FIELD_EQUAL(visitable);
 	VCMI_REQUIRE_FIELD_EQUAL(blocked);
-
 }
 
 //MapComparer

+ 116 - 0
test/rmg/CRmgTemplateTest.cpp

@@ -0,0 +1,116 @@
+/*
+ * CRmgTemplateTest.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 "../../lib/JsonNode.h"
+#include "../../lib/filesystem/ResourceID.h"
+
+#include "../../lib/rmg/CRmgTemplate.h"
+
+#include "../../lib/serializer/JsonSerializer.h"
+#include "../../lib/serializer/JsonDeserializer.h"
+
+#include "../JsonComparer.h"
+
+
+namespace test
+{
+using namespace ::rmg;
+using namespace ::testing;
+
+class CRmgTemplateTest : public Test
+{
+public:
+	const std::string TEST_DATA_PATH = "test/rmg/";
+
+protected:
+
+	void testLoadSave(const std::string & id, const JsonNode & config)
+	{
+		std::shared_ptr<CRmgTemplate> subject = std::make_shared<CRmgTemplate>();
+		subject->setId(id);
+
+		{
+			JsonDeserializer handler(nullptr, config);
+			subject->serializeJson(handler);
+		}
+
+		EXPECT_FALSE(subject->getName().empty());
+
+		for(const auto & idAndZone : subject->getZones())
+		{
+			auto thisZone = idAndZone.second;
+			if(thisZone->getMinesLikeZone() != ZoneOptions::NO_ZONE)
+			{
+				auto otherZoneId = thisZone->getMinesLikeZone();
+
+				const auto otherZone = subject->getZones().at(otherZoneId);
+				GTEST_ASSERT_NE(otherZone, nullptr);
+				EXPECT_THAT(thisZone->getMinesInfo(), ContainerEq(otherZone->getMinesInfo()));
+			}
+
+			if(thisZone->getTerrainTypeLikeZone() != ZoneOptions::NO_ZONE)
+			{
+				auto otherZoneId = thisZone->getTerrainTypeLikeZone();
+
+				const auto otherZone = subject->getZones().at(otherZoneId);
+				GTEST_ASSERT_NE(otherZone, nullptr);
+				EXPECT_THAT(thisZone->getTerrainTypes(), ContainerEq(otherZone->getTerrainTypes()));
+			}
+
+			if(thisZone->getTreasureLikeZone() != ZoneOptions::NO_ZONE)
+			{
+				auto otherZoneId = thisZone->getTreasureLikeZone();
+
+				const auto otherZone = subject->getZones().at(otherZoneId);
+				GTEST_ASSERT_NE(otherZone, nullptr);
+				EXPECT_THAT(thisZone->getTreasureInfo(), ContainerEq(otherZone->getTreasureInfo()));;
+			}
+		}
+
+		for(const auto & connection : subject->getConnections())
+		{
+			auto id1 = connection.getZoneA();
+			auto id2 = connection.getZoneB();
+
+			auto zone1 = subject->getZones().at(id1);
+			auto zone2 = subject->getZones().at(id2);
+
+			EXPECT_THAT(zone1->getConnections(), Contains(id2));
+			EXPECT_THAT(zone2->getConnections(), Contains(id1));
+		}
+
+		JsonNode actual(JsonNode::JsonType::DATA_STRUCT);
+
+		{
+			JsonSerializer handler(nullptr, actual);
+			subject->serializeJson(handler);
+		}
+
+		JsonComparer cmp(false);
+		cmp.compare(id, actual, config);
+	}
+
+
+};
+
+TEST_F(CRmgTemplateTest, SerializeCycle)
+{
+	const std::string testFilePath = TEST_DATA_PATH + "1.json";
+	ResourceID testFileRes(testFilePath);
+	JsonNode testData(testFileRes);
+
+	ASSERT_FALSE((testData.Struct().empty()));
+
+	for(const auto & idAndConfig : testData.Struct())
+		testLoadSave(idAndConfig.first, idAndConfig.second);
+}
+
+}

+ 2 - 2
test/spells/AbilityCasterTest.cpp

@@ -13,8 +13,8 @@
 #include "mock/mock_BonusBearer.h"
 #include "mock/mock_spells_Spell.h"
 
-#include "../../../lib/NetPacksBase.h"
-#include "../../../lib/spells/AbilityCaster.h"
+#include "../../lib/NetPacksBase.h"
+#include "../../lib/spells/AbilityCaster.h"
 
 namespace test
 {

+ 3 - 3
test/spells/TargetConditionTest.cpp

@@ -11,9 +11,9 @@
 
 #include <vstd/RNG.h>
 
-#include "../../../lib/NetPacksBase.h"
-#include "../../../lib/spells/TargetCondition.h"
-#include "../../../lib/serializer/JsonDeserializer.h"
+#include "../../lib/NetPacksBase.h"
+#include "../../lib/spells/TargetCondition.h"
+#include "../../lib/serializer/JsonDeserializer.h"
 
 #include "mock/mock_spells_Mechanics.h"
 #include "mock/mock_BonusBearer.h"

+ 216 - 0
test/testdata/rmg/1.json

@@ -0,0 +1,216 @@
+{
+	"2SM2a" :
+	{
+		"minSize" : "s", "maxSize" : "s+u",
+		"players" : "2",
+		"zones" :
+		{
+			"1" :
+			{
+				"type" : "playerStart",
+				"size" : 11,
+				"owner" : 1,
+				"monsters" : "normal",
+				"playerTowns" : { "castles" : 1 },
+				"mines" : { "wood" : 1, "ore" : 1 },
+				"treasure" :
+				[
+					{ "min" : 10000, "max" : 15000, "density" : 1 },
+					{ "min" : 3000, "max" : 6000, "density" : 6 }
+				]
+			},
+			"2" :
+			{
+				"type" : "playerStart",
+				"size" : 59,
+				"owner" : 2,
+				"monsters" : "normal",
+				"playerTowns" : { "castles" : 7 },
+				"minesLikeZone" : 1,
+				"treasureLikeZone" : 1
+			},
+			"3" :
+			{
+				"type" : "treasure",
+				"size" : 11,
+				"monsters" : "normal",
+				"neutralTowns" : { "towns" : 1 },
+				"matchTerrainToTown" : false,
+				"mines" : { "mercury" : 1, "sulfur" : 1, "gold" : 1 },
+				"treasure" :
+				[
+					{ "min" : 15000, "max" : 20000, "density" : 1 },
+					{ "min" : 10000, "max" : 15000, "density" : 6 },
+					{ "min" : 3000, "max" : 6000, "density" : 9 }
+				]
+			},
+			"4" :
+			{
+				"type" : "junction",
+				"size" : 11,
+				"monsters" : "normal",
+				"neutralTowns" : { "towns" : 1 },
+				"matchTerrainToTown" : false,
+				"mines" : { "crystal" : 1, "gems" : 1, "gold" : 1 },
+				"treasureLikeZone" : 3
+			}
+		},
+		"connections" :
+		[
+			{ "a" : "1", "b" : "3", "guard" : 3000 },
+			{ "a" : "2", "b" : "3", "guard" : 12500 },
+			{ "a" : "2", "b" : "4", "guard" : 123456 }
+		]
+	},
+
+	"Midnight Mix" :
+	{
+		"name" : "test name",
+		"minSize" : "l", "maxSize" : "l",
+		"players" : "1,2-4",
+		"cpu" : "1,2",
+		"zones" :
+		{
+			"1" :
+			{
+				"type" : "treasure",
+				"size" : 12,
+				"monsters" : "strong",
+				"neutralTowns" : { "castles" : 1 },
+				"allowedMonsters" : [ "castle", "rampart", "tower", "inferno", "necropolis", "dungeon", "stronghold", "fortress", "neutral" ],
+				"terrainTypes" : [ "dirt", "grass", "snow", "swamp", "rough", "subterra", "lava" ],
+				"mines" : { "gold" : 1 },
+				"treasure" :
+				[
+					{ "min" : 8000, "max" : 9300, "density" : 4 },
+					{ "min" : 6000, "max" : 8000, "density" : 8 },
+					{ "min" : 800, "max" : 800, "density" : 4 }
+				]
+			},
+			"2" :
+			{
+				"type" : "treasure",
+				"size" : 12,
+				"monsters" : "strong",
+				"neutralTowns" : { "castles" : 1 },
+				"terrainTypes" : [ "dirt", "grass", "snow", "swamp", "rough", "subterra", "lava" ],
+				"minesLikeZone" : 1,
+				"treasureLikeZone" : 1
+			},
+			"4" :
+			{
+				"type" : "playerStart",
+				"size" : 12,
+				"owner" : 1,
+				"monsters" : "normal",
+				"playerTowns" : { "castles" : 1 },
+				"allowedTowns" : [ "castle", "rampart", "tower", "inferno", "dungeon", "stronghold", "fortress" ],
+				"terrainTypes" : [ "dirt", "grass", "snow", "swamp", "rough", "subterra", "lava" ],
+				"mines" : { "wood" : 1, "mercury" : 3, "ore" : 5, "sulfur" : 7, "crystal" : 10, "gems" : 34 },
+				"treasure" :
+				[
+					{ "min" : 4500, "max" : 6000, "density" : 1 },
+					{ "min" : 3500, "max" : 4500, "density" : 4 },
+					{ "min" : 300, "max" : 2000, "density" : 12 }
+				]
+			},
+			"5" :
+			{
+				"type" : "treasure",
+				"size" : 10,
+				"monsters" : "strong",
+				"neutralTowns" : { "towns" : 1 },
+				"matchTerrainToTown" : false,
+				"terrainTypes" : [ "dirt", "grass", "subterra", "lava" ],
+				"treasure" :
+				[
+					{ "min" : 6000, "max" : 7999, "density" : 6 },
+					{ "min" : 4000, "max" : 6000, "density" : 8 },
+					{ "min" : 1200, "max" : 2000, "density" : 5 }
+				]
+			},
+			"6" :
+			{
+				"type" : "treasure",
+				"size" : 10,
+				"monsters" : "strong",
+				"neutralTowns" : { "towns" : 5 },
+				"townsAreSameType" : true,
+				"terrainTypeLikeZone" : 5,
+				"treasureLikeZone" : 5
+			},
+			"7" :
+			{
+				"type" : "playerStart",
+				"size" : 12,
+				"owner" : 2,
+				"monsters" : "normal",
+				"playerTowns" : { "castles" : 1 },
+				"allowedTowns" : [ "castle", "rampart", "tower", "inferno", "dungeon", "stronghold", "fortress" ],
+				"terrainTypes" : [ "dirt", "grass", "snow", "swamp", "rough", "subterra", "lava" ],
+				"minesLikeZone" : 4,
+				"treasureLikeZone" : 4
+			},
+			"8" :
+			{
+				"type" : "treasure",
+				"size" : 10,
+				"monsters" : "strong",
+				"neutralTowns" : { "towns" : 1 },
+				"terrainTypeLikeZone" : 5,
+				"treasureLikeZone" : 5
+			},
+			"10" :
+			{
+				"type" : "playerStart",
+				"size" : 12,
+				"owner" : 3,
+				"monsters" : "normal",
+				"playerTowns" : { "castles" : 1 },
+				"allowedTowns" : [ "castle", "rampart", "tower", "inferno", "dungeon", "stronghold", "fortress" ],
+				"allowedMonsters" : [ "castle", "rampart", "tower", "inferno", "necropolis", "dungeon", "stronghold", "fortress", "neutral" ],
+				"terrainTypes" : [ "dirt", "grass", "snow", "swamp", "rough", "subterra", "lava" ],
+				"minesLikeZone" : 4,
+				"treasureLikeZone" : 4
+			},
+			"11" :
+			{
+				"type" : "treasure",
+				"size" : 10,
+				"monsters" : "strong",
+				"neutralTowns" : { "towns" : 1 },
+				"terrainTypeLikeZone" : 5,
+				"treasureLikeZone" : 5
+			},
+			"13" :
+			{
+				"type" : "treasure",
+				"size" : 25,
+				"monsters" : "strong",
+				"neutralTowns" : { "castles" : 1 },
+				"allowedTowns" : [ "tower" ],
+				"terrainTypes" : [ "snow" ],
+				"mines" : { "mercury" : 1, "sulfur" : 1, "crystal" : 1, "gems" : 1, "gold" : 4 },
+				"treasure" :
+				[
+					{ "min" : 6000, "max" : 40000, "density" : 8 },
+					{ "min" : 9000, "max" : 9500, "density" : 8 },
+					{ "min" : 2500, "max" : 3000, "density" : 4 }
+				]
+			}
+		},
+		"connections" :
+		[
+			{ "a" : "1", "b" : "5", "guard" : 9000 },
+			{ "a" : "4", "b" : "5", "guard" : 5000 },
+			{ "a" : "4", "b" : "6", "guard" : 5000 },
+			{ "a" : "5", "b" : "6", "guard" : 5000 },
+			{ "a" : "1", "b" : "8", "guard" : 9000 },
+			{ "a" : "7", "b" : "8", "guard" : 5000 },
+			{ "a" : "10", "b" : "11", "guard" : 5000 },
+			{ "a" : "1", "b" : "2", "guard" : 10000 },
+			{ "a" : "1", "b" : "13", "guard" : 20000 },
+			{ "a" : "2", "b" : "13", "guard" : 20000 }
+		]
+	}
+}