浏览代码

Merge pull request #146 from vcmi/feature/VCMIMapFormat1

Ok let's try it.
DjWarmonger 9 年之前
父节点
当前提交
8f1fba9551
共有 89 个文件被更改,包括 4504 次插入550 次删除
  1. 1 1
      CMakeLists.txt
  2. 7 0
      Mods/vcmi/Sprites/ScSelC.json
  3. 二进制
      Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png
  4. 20 10
      client/CPreGame.cpp
  5. 1 1
      client/CPreGame.h
  6. 108 23
      lib/CArtHandler.cpp
  7. 26 1
      lib/CArtHandler.h
  8. 56 22
      lib/CCreatureHandler.cpp
  9. 9 1
      lib/CCreatureHandler.h
  10. 69 1
      lib/CCreatureSet.cpp
  11. 14 2
      lib/CCreatureSet.h
  12. 3 3
      lib/CGameState.cpp
  13. 36 8
      lib/CHeroHandler.cpp
  14. 19 3
      lib/CHeroHandler.h
  15. 7 0
      lib/CMakeLists.txt
  16. 37 6
      lib/CModHandler.cpp
  17. 2 0
      lib/CModHandler.h
  18. 14 5
      lib/CPathfinder.cpp
  19. 20 6
      lib/CTownHandler.cpp
  20. 10 4
      lib/CTownHandler.h
  21. 1 1
      lib/Connection.h
  22. 5 0
      lib/IHandlerBase.cpp
  23. 4 3
      lib/IHandlerBase.h
  24. 20 7
      lib/JsonDetail.cpp
  25. 1 1
      lib/LogicalExpression.h
  26. 2 1
      lib/NetPacksLib.cpp
  27. 15 1
      lib/StringConstants.h
  28. 16 0
      lib/VCMI_lib.cbp
  29. 15 0
      lib/VCMI_lib.vcxproj
  30. 48 0
      lib/VCMI_lib.vcxproj.filters
  31. 1 0
      lib/filesystem/CFileInputStream.cpp
  32. 9 0
      lib/filesystem/CInputOutputStream.h
  33. 3 31
      lib/filesystem/CInputStream.h
  34. 73 0
      lib/filesystem/CMemoryBuffer.cpp
  35. 88 0
      lib/filesystem/CMemoryBuffer.h
  36. 2 1
      lib/filesystem/CMemoryStream.h
  37. 34 0
      lib/filesystem/COutputStream.h
  38. 50 0
      lib/filesystem/CStream.h
  39. 15 6
      lib/filesystem/CZipLoader.cpp
  40. 6 9
      lib/filesystem/CZipLoader.h
  41. 120 0
      lib/filesystem/CZipSaver.cpp
  42. 57 0
      lib/filesystem/CZipSaver.h
  43. 254 0
      lib/filesystem/MinizipExtensions.cpp
  44. 88 0
      lib/filesystem/MinizipExtensions.h
  45. 2 1
      lib/filesystem/ResourceID.cpp
  46. 1 1
      lib/mapObjects/CArmedInstance.cpp
  47. 44 0
      lib/mapObjects/CGHeroInstance.cpp
  48. 1 0
      lib/mapObjects/CGHeroInstance.h
  49. 66 0
      lib/mapObjects/CGTownInstance.cpp
  50. 4 0
      lib/mapObjects/CGTownInstance.h
  51. 54 24
      lib/mapObjects/CObjectClassesHandler.cpp
  52. 27 13
      lib/mapObjects/CObjectClassesHandler.h
  53. 63 0
      lib/mapObjects/CObjectHandler.cpp
  54. 22 2
      lib/mapObjects/CObjectHandler.h
  55. 1 0
      lib/mapObjects/CRewardableConstructor.cpp
  56. 1 2
      lib/mapObjects/CommonConstructors.h
  57. 252 8
      lib/mapObjects/MiscObjects.cpp
  58. 25 5
      lib/mapObjects/MiscObjects.h
  59. 97 8
      lib/mapObjects/ObjectTemplate.cpp
  60. 2 1
      lib/mapObjects/ObjectTemplate.h
  61. 59 7
      lib/mapping/CMap.cpp
  62. 41 8
      lib/mapping/CMap.h
  63. 0 1
      lib/mapping/CMapDefines.h
  64. 6 11
      lib/mapping/CMapEditManager.cpp
  65. 26 15
      lib/mapping/CMapService.cpp
  66. 14 1
      lib/mapping/CMapService.h
  67. 21 55
      lib/mapping/MapFormatH3M.cpp
  68. 0 9
      lib/mapping/MapFormatH3M.h
  69. 937 95
      lib/mapping/MapFormatJson.cpp
  70. 197 24
      lib/mapping/MapFormatJson.h
  71. 4 5
      lib/rmg/CMapGenerator.cpp
  72. 64 70
      lib/rmg/CRmgTemplateZone.cpp
  73. 163 0
      lib/serializer/JsonDeserializer.cpp
  74. 34 0
      lib/serializer/JsonDeserializer.h
  75. 90 0
      lib/serializer/JsonSerializeFormat.cpp
  76. 148 0
      lib/serializer/JsonSerializeFormat.h
  77. 94 0
      lib/serializer/JsonSerializer.cpp
  78. 35 0
      lib/serializer/JsonSerializer.h
  79. 24 2
      lib/spells/CSpellHandler.cpp
  80. 8 2
      lib/spells/CSpellHandler.h
  81. 2 0
      test/CMakeLists.txt
  82. 18 18
      test/CMapEditManagerTest.cpp
  83. 97 0
      test/CMapFormatTest.cpp
  84. 52 0
      test/CMemoryBufferTest.cpp
  85. 8 0
      test/CVcmiTestConfig.cpp
  86. 274 0
      test/MapComparer.cpp
  87. 31 0
      test/MapComparer.h
  88. 6 0
      test/Test.cbp
  89. 3 4
      vcmi.workspace

+ 1 - 1
CMakeLists.txt

@@ -19,7 +19,7 @@ set(VCMI_VERSION_PATCH 0)
 option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
 option(ENABLE_EDITOR "Enable compilation of map editor" OFF)
 option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
-option(ENABLE_TEST "Enable compilation of unit tests" OFF)
+option(ENABLE_TEST "Enable compilation of unit tests" ON)
 option(ENABLE_PCH "Enable compilation using precompiled headers" ON)
 
 ############################################

+ 7 - 0
Mods/vcmi/Sprites/ScSelC.json

@@ -0,0 +1,7 @@
+{
+        "basepath" : "mapFormatIcons/",
+	"images" :
+	[
+		{ "group" : 1, "frame" : 0, "file" : "vcmi1.png"}
+	]
+}

二进制
Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png


+ 20 - 10
client/CPreGame.cpp

@@ -1117,8 +1117,9 @@ void SelectionTab::parseMaps(const std::unordered_set<ResourceID> &files)
 			CMapInfo mapInfo;
 			mapInfo.mapInit(file.getName());
 
-			// ignore unsupported map versions (e.g. WoG maps without WoG
-			if (mapInfo.mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float())
+			// ignore unsupported map versions (e.g. WoG maps without WoG)
+			// but accept VCMI maps
+			if((mapInfo.mapHeader->version >= EMapFormat::VCMI) || (mapInfo.mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float()))
 				allItems.push_back(std::move(mapInfo));
 		}
 		catch(std::exception & e)
@@ -1283,7 +1284,9 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CM
 
 	slider = new CSlider(Point(372, 86), tabType != CMenuScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, CSlider::BLUE);
 	slider->addUsedEvents(WHEEL);
-	format =  CDefHandler::giveDef("SCSELC.DEF");
+
+	formatIcons = new CAnimation("SCSELC.DEF");
+	formatIcons->load();
 
 	sortingBy = _format;
 	ascending = true;
@@ -1312,7 +1315,7 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CM
 
 SelectionTab::~SelectionTab()
 {
-	delete format;
+	delete formatIcons;
 }
 
 void SelectionTab::sortBy( int criteria )
@@ -1437,27 +1440,34 @@ void SelectionTab::printMaps(SDL_Surface *to)
 			}
 			printAtMiddleLoc(temp2, 70, 128 + line * 25, FONT_SMALL, itemColor, to);
 
-			int temp=-1;
+			int frame = -1, group = 0;
 			switch (currentItem->mapHeader->version)
 			{
 			case EMapFormat::ROE:
-				temp=0;
+				frame = 0;
 				break;
 			case EMapFormat::AB:
-				temp=1;
+				frame = 1;
 				break;
 			case EMapFormat::SOD:
-				temp=2;
+				frame = 2;
 				break;
 			case EMapFormat::WOG:
-				temp=3;
+				frame = 3;
+				break;
+			case EMapFormat::VCMI:
+				frame = 0;
+				group = 1;
 				break;
 			default:
 				// Unknown version. Be safe and ignore that map
                 logGlobal->warnStream() << "Warning: " << currentItem->fileURI << " has wrong version!";
 				continue;
 			}
-			blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to);
+			IImage * icon = formatIcons->getImage(frame,group);
+			if(icon)
+				icon->draw(to, pos.x + 88, pos.y + 117 + line * 25);
+
 
 			//victory conditions
 			blitAtLoc(CGP->victory->ourImages[currentItem->mapHeader->victoryIconIndex].bitmap, 306, 117 + line * 25, to);

+ 1 - 1
client/CPreGame.h

@@ -149,7 +149,7 @@ public:
 class SelectionTab : public CIntObject
 {
 private:
-	CDefHandler *format; //map size
+	CAnimation * formatIcons;
 
     void parseMaps(const std::unordered_set<ResourceID> &files);
 	void parseGames(const std::unordered_set<ResourceID> &files, bool multi);

+ 108 - 23
lib/CArtHandler.cpp

@@ -19,9 +19,11 @@
 #include "mapObjects/MapObjects.h"
 #include "NetPacksBase.h"
 #include "GameConstants.h"
+#include "StringConstants.h"
 #include "CRandomGenerator.h"
 
 #include "mapObjects/CObjectClassesHandler.h"
+#include "mapping/CMap.h"
 
 // Note: list must match entries in ArtTraits.txt
 #define ART_POS_LIST    \
@@ -201,28 +203,71 @@ std::vector<JsonNode> CArtHandler::loadLegacyData(size_t dataSize)
 
 void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->id = ArtifactID(artifacts.size());
 	object->iconIndex = object->id + 5;
 
 	artifacts.push_back(object);
 
-	VLC->modh->identifiers.registerObject(scope, "artifact", name, object->id);
+	VLC->modh->identifiers.requestIdentifier(scope, "object", "artifact", [=](si32 index)
+	{
+		JsonNode conf;
+		conf.setMeta(scope);
+
+		VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::ARTIFACT, object->id.num);
+
+		if (!object->advMapDef.empty())
+		{
+			JsonNode templ;
+			templ.setMeta(scope);
+			templ["animation"].String() = object->advMapDef;
+
+			// add new template.
+			// Necessary for objects added via mods that don't have any templates in H3
+			VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->addTemplate(templ);
+		}
+		// object does not have any templates - this is not usable object (e.g. pseudo-art like lock)
+		if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->getTemplates().empty())
+			VLC->objtypeh->removeSubObject(Obj::ARTIFACT, object->id);
+	});
+
+	registerObject(scope, "artifact", name, object->id);
 }
 
 void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->id = ArtifactID(index);
 	object->iconIndex = object->id;
 
 	assert(artifacts[index] == nullptr); // ensure that this id was not loaded before
 	artifacts[index] = object;
 
-	VLC->modh->identifiers.registerObject(scope, "artifact", name, object->id);
+	VLC->modh->identifiers.requestIdentifier(scope, "object", "artifact", [=](si32 index)
+	{
+		JsonNode conf;
+		conf.setMeta(scope);
+
+		VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::ARTIFACT, object->id.num);
+
+		if (!object->advMapDef.empty())
+		{
+			JsonNode templ;
+			templ.setMeta(scope);
+			templ["animation"].String() = object->advMapDef;
+
+			// add new template.
+			// Necessary for objects added via mods that don't have any templates in H3
+			VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->addTemplate(templ);
+		}
+		// object does not have any templates - this is not usable object (e.g. pseudo-art like lock)
+		if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->getTemplates().empty())
+			VLC->objtypeh->removeSubObject(Obj::ARTIFACT, object->id);
+	});
+	registerObject(scope, "artifact", name, object->id);
 }
 
-CArtifact * CArtHandler::loadFromJson(const JsonNode & node)
+CArtifact * CArtHandler::loadFromJson(const JsonNode & node, const std::string & identifier)
 {
 	CArtifact * art;
 
@@ -234,7 +279,7 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node)
 		loadGrowingArt(growing, node);
 		art = growing;
 	}
-
+	art->identifier = identifier;
 	const JsonNode & text = node["text"];
 	art->name        = text["name"].String();
 	art->description = text["description"].String();
@@ -629,7 +674,7 @@ std::vector<bool> CArtHandler::getDefaultAllowed() const
 	std::vector<bool> allowedArtifacts;
 	allowedArtifacts.resize(127, true);
 	allowedArtifacts.resize(141, false);
-	allowedArtifacts.resize(GameConstants::ARTIFACTS_QUANTITY, true);
+	allowedArtifacts.resize(artifacts.size(), true);
 	return allowedArtifacts;
 }
 
@@ -695,24 +740,20 @@ void CArtHandler::afterLoadFinalization()
 		}
 	}
 	CBonusSystemNode::treeHasChanged();
+}
 
-	for (CArtifact * art : artifacts)
-	{
-		VLC->objtypeh->loadSubObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num);
-
-		if (!art->advMapDef.empty())
-		{
-			JsonNode templ;
-			templ["animation"].String() = art->advMapDef;
+si32 CArtHandler::decodeArfifact(const std::string& identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
 
-			// add new template.
-			// Necessary for objects added via mods that don't have any templates in H3
-			VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(templ);
-		}
-		// object does not have any templates - this is not usable object (e.g. pseudo-art like lock)
-		if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->getTemplates().empty())
-			VLC->objtypeh->removeSubObject(Obj::ARTIFACT, art->id);
-	}
+std::string CArtHandler::encodeArtifact(const si32 index)
+{
+	return VLC->arth->artifacts[index]->identifier;
 }
 
 CArtifactInstance::CArtifactInstance()
@@ -937,6 +978,40 @@ CArtifactInstance * CArtifactInstance::createNewArtifactInstance(int aid)
 	return createNewArtifactInstance(VLC->arth->artifacts[aid]);
 }
 
+CArtifactInstance * CArtifactInstance::createArtifact(CMap * map, int aid, int spellID)
+{
+	CArtifactInstance * a = nullptr;
+	if(aid >= 0)
+	{
+		if(spellID < 0)
+		{
+			a = CArtifactInstance::createNewArtifactInstance(aid);
+		}
+		else
+		{
+			a = CArtifactInstance::createScroll(SpellID(spellID).toSpell());
+		}
+	}
+	else //FIXME: create combined artifact instance for random combined artifacts, just in case
+	{
+		a = new CArtifactInstance(); //random, empty
+	}
+
+	map->addNewArtifactInstance(a);
+
+	//TODO make it nicer
+	if(a->artType && (!!a->artType->constituents))
+	{
+		CCombinedArtifactInstance * comb = dynamic_cast<CCombinedArtifactInstance *>(a);
+		for(CCombinedArtifactInstance::ConstituentInfo & ci : comb->constituentsInfo)
+		{
+			map->addNewArtifactInstance(ci.art);
+		}
+	}
+	return a;
+}
+
+
 void CArtifactInstance::deserializationFix()
 {
 	setType(artType);
@@ -1313,3 +1388,13 @@ void CArtifactSet::artDeserializationFix(CBonusSystemNode *node)
 		if(elem.second.artifact && !elem.second.locked)
 			node->attachTo(elem.second.artifact);
 }
+
+void CArtifactSet::writeJson(JsonNode& json) const
+{
+
+}
+
+void CArtifactSet::readJson(const JsonNode& json)
+{
+
+}

+ 26 - 1
lib/CArtHandler.h

@@ -25,6 +25,7 @@ struct ArtifactLocation;
 class CArtifactSet;
 class CArtifactInstance;
 class CRandomGenerator;
+class CMap;
 
 #define ART_BEARER_LIST \
 	ART_BEARER(HERO)\
@@ -49,6 +50,7 @@ protected:
 public:
 	enum EartClass {ART_SPECIAL=1, ART_TREASURE=2, ART_MINOR=4, ART_MAJOR=8, ART_RELIC=16}; //artifact classes
 
+	std::string identifier;
 	std::string image;
 	std::string large; // big image for cutom artifacts, used in drag & drop
 	std::string advMapDef; //used for adventure map object
@@ -79,6 +81,10 @@ public:
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & name & description & eventText & image & large & advMapDef & iconIndex &
 			price & possibleSlots & constituents & constituentOf & aClass & id;
+		if(version>=759)
+		{
+			h & identifier;
+		}
 	}
 
 	CArtifact();
@@ -147,6 +153,15 @@ public:
 	static CArtifactInstance *createScroll(SpellID sid);
 	static CArtifactInstance *createNewArtifactInstance(CArtifact *Art);
 	static CArtifactInstance *createNewArtifactInstance(int aid);
+
+	/**
+	 * Creates an artifact instance.
+	 *
+	 * @param aid the id of the artifact
+	 * @param spellID optional. the id of a spell if a spell scroll object should be created
+	 * @return the created artifact instance
+	 */
+	static CArtifactInstance * createArtifact(CMap * map, int aid, int spellID = -1);
 };
 
 class DLL_LINKAGE CCombinedArtifactInstance : public CArtifactInstance
@@ -240,6 +255,12 @@ public:
 
 	std::vector<bool> getDefaultAllowed() const override;
 
+	///json serialization helper
+	static si32 decodeArfifact(const std::string & identifier);
+
+	///json serialization helper
+	static std::string encodeArtifact(const si32 index);
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & artifacts & allowedArtifacts & treasures & minors & majors & relics
@@ -248,7 +269,7 @@ public:
 	}
 
 private:
-	CArtifact * loadFromJson(const JsonNode & node);
+	CArtifact * loadFromJson(const JsonNode & node, const std::string & identifier);
 
 	void addSlot(CArtifact * art, const std::string & slotID);
 	void loadSlots(CArtifact * art, const JsonNode & node);
@@ -313,6 +334,10 @@ public:
 
 	void artDeserializationFix(CBonusSystemNode *node);
 
+protected:
+	void writeJson(JsonNode & json) const;
+	void readJson(const JsonNode & json);
+
 protected:
 	std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> searchForConstituent(int aid) const;
 };

+ 56 - 22
lib/CCreatureHandler.cpp

@@ -167,12 +167,12 @@ static void AddAbility(CCreature *cre, const JsonVector &ability_vec)
 	}
 
 	nsf->type = it->second;
-	
+
 	JsonUtils::parseTypedBonusShort(ability_vec,nsf);
 
 	nsf->source = Bonus::CREATURE_ABILITY;
 	nsf->sid = cre->idNumber;
-	
+
 	cre->addNewBonus(nsf);
 }
 
@@ -188,6 +188,16 @@ CCreatureHandler::CCreatureHandler()
 	loadCommanders();
 }
 
+const CCreature * CCreatureHandler::getCreature(const std::string & scope, const std::string & identifier) const
+{
+	boost::optional<si32> index = VLC->modh->identifiers.getIdentifier(scope, "creature", identifier);
+
+	if(!index)
+		throw std::runtime_error("Creature not found "+identifier);
+
+	return creatures[*index];
+}
+
 void CCreatureHandler::loadCommanders()
 {
 	JsonNode data(ResourceID("config/commanders.json"));
@@ -358,23 +368,41 @@ std::vector<JsonNode> CCreatureHandler::loadLegacyData(size_t dataSize)
 
 void CCreatureHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->setId(CreatureID(creatures.size()));
 	object->iconIndex = object->idNumber + 2;
 
 	creatures.push_back(object);
 
-	VLC->modh->identifiers.registerObject(scope, "creature", name, object->idNumber);
+	VLC->modh->identifiers.requestIdentifier(scope, "object", "monster", [=](si32 index)
+	{
+		JsonNode conf;
+		conf.setMeta(scope);
+
+		VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::MONSTER, object->idNumber.num);
+		if (!object->advMapDef.empty())
+		{
+			JsonNode templ;
+			templ["animation"].String() = object->advMapDef;
+			VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->addTemplate(templ);
+		}
+
+		// object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
+		if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->getTemplates().empty())
+			VLC->objtypeh->removeSubObject(Obj::MONSTER, object->idNumber.num);
+	});
+
+	registerObject(scope, "creature", name, object->idNumber);
 
 	for(auto node : data["extraNames"].Vector())
 	{
-		VLC->modh->identifiers.registerObject(scope, "creature", node.String(), object->idNumber);
+		registerObject(scope, "creature", node.String(), object->idNumber);
 	}
 }
 
 void CCreatureHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->setId(CreatureID(index));
 	object->iconIndex = object->idNumber + 2;
 
@@ -386,10 +414,28 @@ void CCreatureHandler::loadObject(std::string scope, std::string name, const Jso
 	assert(creatures[index] == nullptr); // ensure that this id was not loaded before
 	creatures[index] = object;
 
-	VLC->modh->identifiers.registerObject(scope, "creature", name, object->idNumber);
+	VLC->modh->identifiers.requestIdentifier(scope, "object", "monster", [=](si32 index)
+	{
+		JsonNode conf;
+		conf.setMeta(scope);
+
+		VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::MONSTER, object->idNumber.num);
+		if (!object->advMapDef.empty())
+		{
+			JsonNode templ;
+			templ["animation"].String() = object->advMapDef;
+			VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->addTemplate(templ);
+		}
+
+		// object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
+		if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->getTemplates().empty())
+			VLC->objtypeh->removeSubObject(Obj::MONSTER, object->idNumber.num);
+	});
+
+	registerObject(scope, "creature", name, object->idNumber);
 	for(auto & node : data["extraNames"].Vector())
 	{
-		VLC->modh->identifiers.registerObject(scope, "creature", node.String(), object->idNumber);
+		registerObject(scope, "creature", node.String(), object->idNumber);
 	}
 }
 
@@ -568,11 +614,12 @@ void CCreatureHandler::loadUnitAnimInfo(JsonNode & graphics, CLegacyConfigParser
 		graphics.Struct().erase("missile");
 }
 
-CCreature * CCreatureHandler::loadFromJson(const JsonNode & node)
+CCreature * CCreatureHandler::loadFromJson(const JsonNode & node, const std::string & identifier)
 {
 	auto  cre = new CCreature();
 
 	const JsonNode & name = node["name"];
+	cre->identifier = identifier;
 	cre->nameSing = name["singular"].String();
 	cre->namePl = name["plural"].String();
 
@@ -1130,20 +1177,7 @@ void CCreatureHandler::buildBonusTreeForTiers()
 
 void CCreatureHandler::afterLoadFinalization()
 {
-	for (CCreature * crea : creatures)
-	{
-		VLC->objtypeh->loadSubObject(crea->nameSing, JsonNode(), Obj::MONSTER, crea->idNumber.num);
-		if (!crea->advMapDef.empty())
-		{
-			JsonNode templ;
-			templ["animation"].String() = crea->advMapDef;
-			VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber)->addTemplate(templ);
-		}
 
-		// object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower)
-		if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber.num)->getTemplates().empty())
-			VLC->objtypeh->removeSubObject(Obj::MONSTER, crea->idNumber.num);
-	}
 }
 
 void CCreatureHandler::deserializationFix()

+ 9 - 1
lib/CCreatureHandler.h

@@ -26,6 +26,8 @@ class CCreature;
 class DLL_LINKAGE CCreature : public CBonusSystemNode
 {
 public:
+	std::string identifier;
+
 	std::string nameRef; // reference name, stringID
 	std::string nameSing;// singular name, e.g. Centaur
 	std::string namePl;  // plural name, e.g. Centaurs
@@ -136,6 +138,10 @@ public:
 		h & idNumber & faction & sounds & animation;
 
 		h & doubleWide & special;
+		if(version>=759)
+		{
+			h & identifier;
+		}
 	}
 
 	CCreature();
@@ -148,7 +154,7 @@ private:
 	CBonusSystemNode creaturesOfLevel[GameConstants::CREATURES_PER_TOWN + 1];//index 0 is used for creatures of unknown tier or outside <1-7> range
 
 	/// load one creature from json config
-	CCreature * loadFromJson(const JsonNode & node);
+	CCreature * loadFromJson(const JsonNode & node, const std::string & identifier);
 
 	void loadJsonAnimation(CCreature * creature, const JsonNode & graphics);
 	void loadStackExperience(CCreature * creature, const JsonNode &input);
@@ -186,6 +192,8 @@ public:
 	std::vector< std::vector <ui8> > skillLevels; //how much of a bonus will be given to commander with every level. SPELL_POWER also gives CASTS and RESISTANCE
 	std::vector <std::pair <Bonus*, std::pair <ui8, ui8> > > skillRequirements; // first - Bonus, second - which two skills are needed to use it
 
+	const CCreature * getCreature(const std::string & scope, const std::string & identifier) const;
+
 	void deserializationFix();
 	CreatureID pickRandomMonster(CRandomGenerator & rand, int tier = -1) const; //tier <1 - CREATURES_PER_TOWN> or -1 for any
 	void addBonusForTier(int tier, Bonus *b); //tier must be <1-7>

+ 69 - 1
lib/CCreatureSet.cpp

@@ -11,6 +11,7 @@
 #include "spells/CSpellHandler.h"
 #include "CHeroHandler.h"
 #include "IBonusTypeHandler.h"
+#include "serializer/JsonSerializeFormat.h"
 
 /*
  * CCreatureSet.cpp, part of VCMI engine
@@ -479,6 +480,38 @@ CCreatureSet & CCreatureSet::operator=(const CCreatureSet&cs)
 
 void CCreatureSet::armyChanged()
 {
+
+}
+
+void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
+{
+	if(handler.saving && stacks.empty())
+		return;
+	JsonNode & json = handler.getCurrent()[fieldName];
+
+	if(handler.saving)
+	{
+		for(const auto & p : stacks)
+		{
+			JsonNode stack_node;
+			p.second->writeJson(stack_node);
+			json.Vector()[p.first.getNum()] = stack_node;
+		}
+	}
+	else
+	{
+		for(size_t idx = 0; idx < json.Vector().size(); idx++)
+		{
+			if(json.Vector()[idx]["amount"].Float() > 0)
+			{
+				CStackInstance * new_stack = new CStackInstance();
+
+				new_stack->readJson(json.Vector()[idx]);
+
+				putStack(SlotID(idx), new_stack);
+			}
+		}
+	}
 }
 
 CStackInstance::CStackInstance()
@@ -605,7 +638,7 @@ std::string CStackInstance::bonusToString(const Bonus *bonus, bool description)
 	{
 		return VLC->getBth()->bonusToString(bonus, this, description);
 	}
-	
+
 }
 
 std::string CStackInstance::bonusToGraphics(const Bonus *bonus) const
@@ -700,6 +733,25 @@ ArtBearer::ArtBearer CStackInstance::bearerType() const
 	return ArtBearer::CREATURE;
 }
 
+void CStackInstance::writeJson(JsonNode& json) const
+{
+	if(idRand > -1)
+	{
+		json["level"].Float() = (int)idRand / 2;
+		json["upgraded"].Bool() = (idRand % 2) > 0;
+	}
+	CStackBasicDescriptor::writeJson(json);
+}
+
+void CStackInstance::readJson(const JsonNode& json)
+{
+	if(json["type"].String() == "")
+	{
+		idRand = json["level"].Float() * 2 + (int)json["upgraded"].Bool();
+	}
+	CStackBasicDescriptor::readJson(json);
+}
+
 CCommanderInstance::CCommanderInstance()
 {
 	init();
@@ -792,6 +844,22 @@ CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count
 {
 }
 
+void CStackBasicDescriptor::writeJson(JsonNode& json) const
+{
+	json.setType(JsonNode::DATA_STRUCT);
+	if(type)
+		json["type"].String() = type->identifier;
+	json["amount"].Float() = count;
+}
+
+void CStackBasicDescriptor::readJson(const JsonNode& json)
+{
+	auto typeName = json["type"].String();
+	if(typeName != "")
+		type = VLC->creh->getCreature("core", json["type"].String());
+	count = json["amount"].Float();
+}
+
 DLL_LINKAGE std::ostream & operator<<(std::ostream & str, const CStackInstance & sth)
 {
 	if(!sth.valid(true))

+ 14 - 2
lib/CCreatureSet.h

@@ -14,11 +14,12 @@
  * Full text of license available in license.txt file, in main folder
  *
  */
-
+class JsonNode;
 class CCreature;
 class CGHeroInstance;
 class CArmedInstance;
 class CCreatureArtifactSet;
+class JsonSerializeFormat;
 
 class DLL_LINKAGE CStackBasicDescriptor
 {
@@ -34,6 +35,10 @@ public:
 	{
 		h & type & count;
 	}
+
+	void writeJson(JsonNode & json) const;
+
+	void readJson(const JsonNode & json);
 };
 
 class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet
@@ -59,6 +64,10 @@ public:
 		BONUS_TREE_DESERIALIZATION_FIX
 	}
 
+	void writeJson(JsonNode & json) const;
+
+	void readJson(const JsonNode & json);
+
 	//overrides CBonusSystemNode
 	std::string bonusToString(const Bonus *bonus, bool description) const override; // how would bonus description look for this particular type of node
 	std::string bonusToGraphics(const Bonus *bonus) const; //file name of graphics from StackSkills , in future possibly others
@@ -110,7 +119,7 @@ public:
 	bool gainsLevel() const; //true if commander has lower level than should upon his experience
 	ui64 getPower() const override {return 0;};
 	int getExpRank() const override;
-	int getLevel() const override; 
+	int getLevel() const override;
 	ArtBearer::ArtBearer bearerType() const override; //from CArtifactSet
 
 	template <typename Handler> void serialize(Handler &h, const int version)
@@ -211,6 +220,9 @@ public:
 	{
 		h & stacks & formation;
 	}
+
+	void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName);
+
 	operator bool() const
 	{
 		return !stacks.empty();

+ 3 - 3
lib/CGameState.cpp

@@ -736,7 +736,7 @@ BattleInfo * CGameState::setupBattle(int3 tile, const CArmedInstance *armies[2],
 {
 	const TerrainTile &t = map->getTile(tile);
 	ETerrainType terrain = t.terType;
-	if(t.isCoastal() && !t.isWater())
+	if(map->isCoastalTile(tile)) //coastal tile is always ground
 		terrain = ETerrainType::SAND;
 
 	BFieldType terType = battleGetBattlefieldType(tile);
@@ -1852,7 +1852,7 @@ void CGameState::initMapObjects()
 	{
 		if(obj)
 		{
-			//logGlobal->traceStream() << boost::format ("Calling Init for object %d, %d") % obj->ID % obj->subID;
+			logGlobal->traceStream() << boost::format ("Calling Init for object %d, %s, %s") % obj->id.getNum() % obj->typeName % obj->subTypeName;
 			obj->initObj();
 		}
 	}
@@ -1956,7 +1956,7 @@ BFieldType CGameState::battleGetBattlefieldType(int3 tile)
 		}
 	}
 
-	if(!t.isWater() && t.isCoastal())
+	if(map->isCoastalTile(tile)) //coastal tile is always ground
 		return BFieldType::SAND_SHORE;
 
 	switch(t.terType)

+ 36 - 8
lib/CHeroHandler.cpp

@@ -96,12 +96,12 @@ bool CObstacleInfo::isAppropriate(ETerrainType terrainType, int specialBattlefie
 	return vstd::contains(allowedTerrains, terrainType);
 }
 
-CHeroClass *CHeroClassHandler::loadFromJson(const JsonNode & node)
+CHeroClass * CHeroClassHandler::loadFromJson(const JsonNode & node, const std::string & identifier)
 {
 	std::string affinityStr[2] = { "might", "magic" };
 
 	auto  heroClass = new CHeroClass();
-
+	heroClass->identifier = identifier;
 	heroClass->imageBattleFemale = node["animation"]["battle"]["female"].String();
 	heroClass->imageBattleMale   = node["animation"]["battle"]["male"].String();
 	//MODS COMPATIBILITY FOR 0.96
@@ -192,7 +192,7 @@ std::vector<JsonNode> CHeroClassHandler::loadLegacyData(size_t dataSize)
 
 void CHeroClassHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->id = heroClasses.size();
 
 	heroClasses.push_back(object);
@@ -210,7 +210,7 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js
 
 void CHeroClassHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->id = index;
 
 	assert(heroClasses[index] == nullptr); // ensure that this id was not loaded before
@@ -292,10 +292,10 @@ CHeroHandler::CHeroHandler()
 	loadExperience();
 }
 
-CHero * CHeroHandler::loadFromJson(const JsonNode & node)
+CHero * CHeroHandler::loadFromJson(const JsonNode & node, const std::string & identifier)
 {
 	auto  hero = new CHero;
-
+	hero->identifier = identifier;
 	hero->sex = node["female"].Bool();
 	hero->special = node["special"].Bool();
 
@@ -540,7 +540,7 @@ std::vector<JsonNode> CHeroHandler::loadLegacyData(size_t dataSize)
 
 void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->ID = HeroTypeID(heroes.size());
 	object->imageIndex = heroes.size() + 30; // 2 special frames + some extra portraits
 
@@ -551,7 +551,7 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod
 
 void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->ID = HeroTypeID(index);
 	object->imageIndex = index;
 
@@ -611,3 +611,31 @@ std::vector<bool> CHeroHandler::getDefaultAllowedAbilities() const
 	allowedAbilities.resize(GameConstants::SKILL_QUANTITY, true);
 	return allowedAbilities;
 }
+
+si32 CHeroHandler::decodeHero(const std::string & identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string CHeroHandler::encodeHero(const si32 index)
+{
+	return VLC->heroh->heroes.at(index)->identifier;
+}
+
+si32 CHeroHandler::decodeSkill(const std::string & identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "skill", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string CHeroHandler::encodeSkill(const si32 index)
+{
+	return NSecondarySkill::names[index];
+}

+ 19 - 3
lib/CHeroHandler.h

@@ -59,7 +59,7 @@ public:
 			h & minAmount & maxAmount & creature;
 		}
 	};
-
+	std::string identifier;
 	HeroTypeID ID;
 	si32 imageIndex;
 
@@ -92,6 +92,10 @@ public:
 		h & ID & imageIndex & initialArmy & heroClass & secSkillsInit & spec & specialty & spells & haveSpellBook & sex & special;
 		h & name & biography & specName & specDescr & specTooltip;
 		h & iconSpecSmall & iconSpecLarge & portraitSmall & portraitLarge;
+		if(version>=759)
+		{
+			h & identifier;
+		}
 	}
 };
 
@@ -169,7 +173,7 @@ struct DLL_LINKAGE CObstacleInfo
 
 class DLL_LINKAGE CHeroClassHandler : public IHandlerBase
 {
-	CHeroClass *loadFromJson(const JsonNode & node);
+	CHeroClass *loadFromJson(const JsonNode & node, const std::string & identifier);
 public:
 	std::vector< ConstTransitivePtr<CHeroClass> > heroClasses;
 
@@ -207,7 +211,7 @@ class DLL_LINKAGE CHeroHandler : public IHandlerBase
 	void loadObstacles();
 
 	/// Load single hero from json
-	CHero * loadFromJson(const JsonNode & node);
+	CHero * loadFromJson(const JsonNode & node, const std::string & identifier);
 
 public:
 	CHeroClassHandler classes;
@@ -253,6 +257,18 @@ public:
 	 */
 	std::vector<bool> getDefaultAllowedAbilities() const;
 
+	///json serialization helper
+	static si32 decodeHero(const std::string & identifier);
+
+	///json serialization helper
+	static std::string encodeHero(const si32 index);
+
+	///json serialization helper
+	static si32 decodeSkill(const std::string & identifier);
+
+	///json serialization helper
+	static std::string encodeSkill(const si32 index);
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & classes & heroes & expPerLevel & ballistics & terrCosts;

+ 7 - 0
lib/CMakeLists.txt

@@ -11,14 +11,17 @@ set(lib_SRCS
 		filesystem/CCompressedStream.cpp
 		filesystem/CFilesystemLoader.cpp
 		filesystem/CArchiveLoader.cpp
+		filesystem/CMemoryBuffer.cpp
 		filesystem/CMemoryStream.cpp
 		filesystem/CBinaryReader.cpp
 		filesystem/CFileInputStream.cpp
 		filesystem/CZipLoader.cpp
+		filesystem/CZipSaver.cpp
 		filesystem/FileInfo.cpp
 		filesystem/Filesystem.cpp
 		filesystem/FileStream.cpp
 		filesystem/ResourceID.cpp
+		filesystem/MinizipExtensions.cpp
 
 		mapObjects/CArmedInstance.cpp
 		mapObjects/CBank.cpp
@@ -101,6 +104,10 @@ set(lib_SRCS
 		Connection.cpp
 		NetPacksLib.cpp
 
+		serializer/JsonSerializer.cpp
+		serializer/JsonDeserializer.cpp
+		serializer/JsonSerializeFormat.cpp
+
 		registerTypes/RegisterTypes.cpp
 		registerTypes/TypesClientPacks1.cpp
 		registerTypes/TypesClientPacks2.cpp

+ 37 - 6
lib/CModHandler.cpp

@@ -205,10 +205,21 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
 	else
 	{
 		//...unless destination mod was specified explicitly
-		auto myDeps = VLC->modh->getModData(request.localScope).dependencies;
-		if (request.remoteScope == "core" ||   // allow only available to all core mod
-		    myDeps.count(request.remoteScope)) // or dependencies
+		//note: getModData does not work for "core" by design
+
+		//for map format support core mod has access to any mod
+		//TODO: better solution for access from map?
+		if(request.localScope == "core" || request.localScope == "")
+		{
 			allowedScopes.insert(request.remoteScope);
+		}
+		else
+		{
+			// allow only available to all core mod or dependencies
+			auto myDeps = VLC->modh->getModData(request.localScope).dependencies;
+			if (request.remoteScope == "core" || myDeps.count(request.remoteScope))
+				allowedScopes.insert(request.remoteScope);
+		}
 	}
 
 	std::string fullID = request.type + '.' + request.name;
@@ -844,9 +855,16 @@ void CModHandler::loadModFilesystems()
 
 CModInfo & CModHandler::getModData(TModID modId)
 {
-	CModInfo & mod = allMods.at(modId);
-	assert(vstd::contains(activeMods, modId)); // not really necessary but won't hurt
-	return mod;
+	auto it = allMods.find(modId);
+
+	if(it == allMods.end())
+	{
+		throw std::runtime_error("Mod not found '" + modId+"'");
+	}
+	else
+	{
+		return it->second;
+	}
 }
 
 void CModHandler::initializeConfig()
@@ -905,3 +923,16 @@ void CModHandler::afterLoad()
 	FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc);
 	file << modSettings;
 }
+
+std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const
+{
+	auto p = splitString(identifier, ':');
+
+	if(p.first.empty())
+		p.first = scope;
+
+	if(p.first == remoteScope)
+		p.first.clear();
+
+	return p.first.empty() ? p.second : p.first +":"+p.second;
+}

+ 2 - 0
lib/CModHandler.h

@@ -292,6 +292,8 @@ public:
 
 	CModHandler();
 
+	std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & allMods & activeMods & settings & modules & identifiers;

+ 14 - 5
lib/CPathfinder.cpp

@@ -133,6 +133,7 @@ void CPathfinder::calculatePaths()
 				if(!hlp->isLayerAvailable(i))
 					continue;
 
+				/// Check transition without tile accessability rules
 				if(cp->layer != i && !isLayerTransitionPossible(i))
 					continue;
 
@@ -143,6 +144,7 @@ void CPathfinder::calculatePaths()
 				if(dp->accessible == CGPathNode::NOT_SET)
 					continue;
 
+				/// Check transition using tile accessability rules
 				if(cp->layer != i && !isLayerTransitionPossible())
 					continue;
 
@@ -302,16 +304,23 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const
 	switch(cp->layer)
 	{
 	case ELayer::LAND:
-		if(destLayer != ELayer::AIR)
-			return true;
-
-		if(!options.lightweightFlyingMode || isSourceInitialPosition())
+		if(destLayer == ELayer::AIR)
+		{
+			if(!options.lightweightFlyingMode || isSourceInitialPosition())
+				return true;
+		}
+		else if(destLayer == ELayer::SAIL)
+		{
+			if(dt->isWater())
+				return true;
+		}
+		else
 			return true;
 
 		break;
 
 	case ELayer::SAIL:
-		if(destLayer == ELayer::LAND && dt->isCoastal())
+		if(destLayer == ELayer::LAND && !dt->isWater())
 			return true;
 
 		break;

+ 20 - 6
lib/CTownHandler.cpp

@@ -526,7 +526,7 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source)
 	    info.tavernVideo = source["tavernVideo"].String();
 	else
 		info.tavernVideo = "TAVERN.BIK";
-	//end of legacy assignment 
+	//end of legacy assignment
 
 	loadTownHall(town,   source["hallSlots"]);
 	loadStructures(town, source["structures"]);
@@ -646,7 +646,7 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source)
 	assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES);
 }
 
-CFaction * CTownHandler::loadFromJson(const JsonNode &source, std::string identifier)
+CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string & identifier)
 {
 	auto  faction = new CFaction();
 
@@ -682,7 +682,7 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source, std::string identi
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data, name);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 
 	object->index = factions.size();
 	factions.push_back(object);
@@ -699,7 +699,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 		{
 			// register town once objects are loaded
 			JsonNode config = data["town"]["mapObject"];
-			config["faction"].String() = object->identifier;
+			config["faction"].String() = name;
 			config["faction"].meta = scope;
 			if (config.meta.empty())// MODS COMPATIBILITY FOR 0.96
 				config.meta = scope;
@@ -722,7 +722,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 
 void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data, name);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	object->index = index;
 	assert(factions[index] == nullptr); // ensure that this id was not loaded before
 	factions[index] = object;
@@ -739,7 +739,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 		{
 			// register town once objects are loaded
 			JsonNode config = data["town"]["mapObject"];
-			config["faction"].String() = object->identifier;
+			config["faction"].String() = name;
 			config["faction"].meta = scope;
 			VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
 		});
@@ -795,3 +795,17 @@ std::set<TFaction> CTownHandler::getAllowedFactions(bool withTown /*=true*/) con
 
 	return allowedFactions;
 }
+
+si32 CTownHandler::decodeFaction(const std::string & identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "faction", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string CTownHandler::encodeFaction(const si32 index)
+{
+	return VLC->townh->factions[index]->identifier;
+}

+ 10 - 4
lib/CTownHandler.h

@@ -122,8 +122,8 @@ public:
 
 	std::string creatureBg120;
 	std::string creatureBg130;
-	
-	
+
+
 
 	std::vector<SPuzzleInfo> puzzleMap;
 
@@ -142,7 +142,7 @@ public:
 	static std::vector<BattleHex> defaultMoatHexes();
 
 	CFaction * faction;
-	
+
 	std::vector<std::string> names; //names of the town instances
 
 	/// level -> list of creatures on this tier
@@ -264,7 +264,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase
 
 	void loadPuzzle(CFaction & faction, const JsonNode & source);
 
-	CFaction * loadFromJson(const JsonNode & data, std::string identifier);
+	CFaction * loadFromJson(const JsonNode & data, const std::string & identifier);
 
 public:
 	std::vector<ConstTransitivePtr<CFaction> > factions;
@@ -282,6 +282,12 @@ public:
 	std::vector<bool> getDefaultAllowed() const override;
 	std::set<TFaction> getAllowedFactions(bool withTown = true) const;
 
+	//json serialization helper
+	static si32 decodeFaction(const std::string & identifier);
+
+	//json serialization helper
+	static std::string encodeFaction(const si32 index);
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & factions;

+ 1 - 1
lib/Connection.h

@@ -27,7 +27,7 @@
 #include "mapping/CCampaignHandler.h" //for CCampaignState
 #include "rmg/CMapGenerator.h" // for CMapGenOptions
 
-const ui32 version = 758;
+const ui32 version = 759;
 const ui32 minSupportedVersion = 753;
 
 class CISer;

+ 5 - 0
lib/IHandlerBase.cpp

@@ -17,3 +17,8 @@ void IHandlerBase::registerObject(std::string scope, std::string type_name, std:
 {
 	return VLC->modh->identifiers.registerObject(scope, type_name, name, index);
 }
+
+std::string IHandlerBase::normalizeIdentifier(const std::string& scope, const std::string& remoteScope, const std::string& identifier) const
+{
+	return VLC->modh->normalizeIdentifier(scope, remoteScope, identifier);
+}

+ 4 - 3
lib/IHandlerBase.h

@@ -25,6 +25,7 @@ class DLL_LINKAGE IHandlerBase
 protected:
 	/// Calls modhandler. Mostly needed to avoid large number of includes in headers
 	void registerObject(std::string scope, std::string type_name, std::string name, si32 index);
+	std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const;
 
 public:
 	/// loads all original game data in vector of json nodes
@@ -65,7 +66,7 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override
 	{
 		auto type_name = getTypeName();
-		auto object = loadFromJson(data);
+		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 		object->id = _ObjectID(objects.size());
 
 		objects.push_back(object);
@@ -75,7 +76,7 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override
 	{
 		auto type_name = getTypeName();
-		auto object = loadFromJson(data);
+		auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 		object->id = _ObjectID(index);
 
 
@@ -99,7 +100,7 @@ public:
 		return objects[raw_id];
 	}
 protected:
-	virtual _Object * loadFromJson(const JsonNode & json) = 0;
+	virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier) = 0;
 	virtual const std::string getTypeName() const = 0;
 public: //todo: make private
 	std::vector<ConstTransitivePtr<_Object>> objects;

+ 20 - 7
lib/JsonDetail.cpp

@@ -61,19 +61,32 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry)
 void JsonWriter::writeString(const std::string &string)
 {
 	static const std::string escaped = "\"\\\b\f\n\r\t";
-
+	
+	static const std::array<char, 7> escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't'};
+	
 	out <<'\"';
 	size_t pos=0, start=0;
 	for (; pos<string.size(); pos++)
 	{
-		size_t escapedChar = escaped.find(string[pos]);
-
-		if (escapedChar != std::string::npos)
+		//we need to check if special character was been already escaped		
+		if((string[pos] == '\\') 
+			&& (pos+1 < string.size()) 
+			&& (std::find(escaped_code.begin(), escaped_code.end(), string[pos+1]) != escaped_code.end()) )
+		{
+			pos++; //write unchanged, next simbol also checked
+		}
+		else
 		{
-			out.write(string.data()+start, pos - start);
-			out << '\\' << escaped[escapedChar];
-			start = pos;
+			size_t escapedPos = escaped.find(string[pos]);
+
+			if (escapedPos != std::string::npos)
+			{
+				out.write(string.data()+start, pos - start);
+				out << '\\' << escaped_code[escapedPos];
+				start = pos+1;
+			}			
 		}
+
 	}
 	out.write(string.data()+start, pos - start);
 	out <<'\"';

+ 1 - 1
lib/LogicalExpression.h

@@ -262,7 +262,7 @@ namespace LogicalExpressionDetail
 		}
 	};
 
-	/// Prints expression in human-readable format
+	/// Serializes expression in JSON format. Part of map format.
 	template <typename ContainedClass>
 	class Writer : public boost::static_visitor<JsonNode>
 	{

+ 2 - 1
lib/NetPacksLib.cpp

@@ -386,6 +386,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
 		//If hero on Boat is removed, the Boat disappears
 		if(h->boat)
 		{
+			gs->map->instanceNames.erase(h->boat->instanceName);
 			gs->map->objects[h->boat->id.getNum()].dellNull();
 			h->boat = nullptr;
 		}
@@ -430,7 +431,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
 		};
 		event.trigger = event.trigger.morph(patcher);
 	}
-
+	gs->map->instanceNames.erase(obj->instanceName);
 	gs->map->objects[id.getNum()].dellNull();
 	gs->map->calculateGuardingGreaturePositions();
 }

+ 15 - 1
lib/StringConstants.h

@@ -21,7 +21,7 @@ namespace GameConstants
 	const std::string TERRAIN_NAMES [TERRAIN_TYPES] = {
 	    "dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock"
 	};
-	
+
 	const std::string RESOURCE_NAMES [RESOURCE_QUANTITY] = {
 	    "wood", "mercury", "ore", "sulfur", "crystal", "gems", "gold", "mithril"
 	};
@@ -84,3 +84,17 @@ namespace ETownType
 		"stronghold",   "fortress",     "conflux"
 	};
 }
+
+namespace NArtifactPosition
+{
+	const std::string names [19] =
+	{
+		"head", "shoulders", "neck", "rightHand", "leftHand", "torso", //5
+		"rightRing", "leftRing", "feet", //8
+		"misc1", "misc2", "misc3", "misc4", //12
+		"mach1", "mach2", "mach3", "mach4", //16
+		"spellbook", "misc5" //18
+	};
+
+	const std::string backpack = "backpack";
+}

+ 16 - 0
lib/VCMI_lib.cbp

@@ -152,6 +152,7 @@
 		<Unit filename="CGeneralTextHandler.h" />
 		<Unit filename="CHeroHandler.cpp" />
 		<Unit filename="CHeroHandler.h" />
+		<Unit filename="CMakeLists.txt" />
 		<Unit filename="CModHandler.cpp" />
 		<Unit filename="CModHandler.h" />
 		<Unit filename="CObstacleInstance.cpp" />
@@ -217,11 +218,18 @@
 		<Unit filename="filesystem/CFileInputStream.h" />
 		<Unit filename="filesystem/CFilesystemLoader.cpp" />
 		<Unit filename="filesystem/CFilesystemLoader.h" />
+		<Unit filename="filesystem/CInputOutputStream.h" />
 		<Unit filename="filesystem/CInputStream.h" />
+		<Unit filename="filesystem/CMemoryBuffer.cpp" />
+		<Unit filename="filesystem/CMemoryBuffer.h" />
 		<Unit filename="filesystem/CMemoryStream.cpp" />
 		<Unit filename="filesystem/CMemoryStream.h" />
+		<Unit filename="filesystem/COutputStream.h" />
+		<Unit filename="filesystem/CStream.h" />
 		<Unit filename="filesystem/CZipLoader.cpp" />
 		<Unit filename="filesystem/CZipLoader.h" />
+		<Unit filename="filesystem/CZipSaver.cpp" />
+		<Unit filename="filesystem/CZipSaver.h" />
 		<Unit filename="filesystem/FileInfo.cpp" />
 		<Unit filename="filesystem/FileInfo.h" />
 		<Unit filename="filesystem/FileStream.cpp" />
@@ -229,6 +237,8 @@
 		<Unit filename="filesystem/Filesystem.cpp" />
 		<Unit filename="filesystem/Filesystem.h" />
 		<Unit filename="filesystem/ISimpleResourceLoader.h" />
+		<Unit filename="filesystem/MinizipExtensions.cpp" />
+		<Unit filename="filesystem/MinizipExtensions.h" />
 		<Unit filename="filesystem/ResourceID.cpp" />
 		<Unit filename="filesystem/ResourceID.h" />
 		<Unit filename="int3.h" />
@@ -306,6 +316,12 @@
 		<Unit filename="rmg/CZoneGraphGenerator.h" />
 		<Unit filename="rmg/CZonePlacer.cpp" />
 		<Unit filename="rmg/CZonePlacer.h" />
+		<Unit filename="serializer/JsonDeserializer.cpp" />
+		<Unit filename="serializer/JsonDeserializer.h" />
+		<Unit filename="serializer/JsonSerializeFormat.cpp" />
+		<Unit filename="serializer/JsonSerializeFormat.h" />
+		<Unit filename="serializer/JsonSerializer.cpp" />
+		<Unit filename="serializer/JsonSerializer.h" />
 		<Unit filename="spells/AdventureSpellMechanics.cpp" />
 		<Unit filename="spells/AdventureSpellMechanics.h" />
 		<Unit filename="spells/BattleSpellMechanics.cpp" />

+ 15 - 0
lib/VCMI_lib.vcxproj

@@ -188,8 +188,14 @@
     <ClCompile Include="CThreadHelper.cpp" />
     <ClCompile Include="CTownHandler.cpp" />
     <ClCompile Include="CRandomGenerator.cpp" />
+    <ClCompile Include="filesystem\CMemoryBuffer.cpp" />
+    <ClCompile Include="filesystem\CZipSaver.cpp" />
     <ClCompile Include="filesystem\FileInfo.cpp" />
     <ClCompile Include="filesystem\FileStream.cpp" />
+    <ClCompile Include="filesystem\MinizipExtensions.cpp" />
+    <ClCompile Include="serializer\JsonDeserializer.cpp" />
+    <ClCompile Include="serializer\JsonSerializeFormat.cpp" />
+    <ClCompile Include="serializer\JsonSerializer.cpp" />
     <ClCompile Include="spells\CSpellHandler.cpp" />
     <ClCompile Include="spells\ISpellMechanics.cpp" />
     <ClCompile Include="spells\AdventureSpellMechanics.cpp" />
@@ -308,13 +314,19 @@
     <ClInclude Include="filesystem\CFileInfo.h" />
     <ClInclude Include="filesystem\CFileInputStream.h" />
     <ClInclude Include="filesystem\CFilesystemLoader.h" />
+    <ClInclude Include="filesystem\CInputOutputStream.h" />
     <ClInclude Include="filesystem\CInputStream.h" />
+    <ClInclude Include="filesystem\CMemoryBuffer.h" />
     <ClInclude Include="filesystem\CMemoryStream.h" />
+    <ClInclude Include="filesystem\COutputStream.h" />
+    <ClInclude Include="filesystem\CStream.h" />
     <ClInclude Include="filesystem\CZipLoader.h" />
+    <ClInclude Include="filesystem\CZipSaver.h" />
     <ClInclude Include="filesystem\FileInfo.h" />
     <ClInclude Include="filesystem\FileStream.h" />
     <ClInclude Include="filesystem\Filesystem.h" />
     <ClInclude Include="filesystem\ISimpleResourceLoader.h" />
+    <ClInclude Include="filesystem\MinizipExtensions.h" />
     <ClInclude Include="filesystem\ResourceID.h" />
     <ClInclude Include="FunctionList.h" />
     <ClInclude Include="IBonusTypeHandler.h" />
@@ -370,6 +382,9 @@
     <ClInclude Include="rmg\CZonePlacer.h" />
     <ClInclude Include="rmg\float3.h" />
     <ClInclude Include="ScopeGuard.h" />
+    <ClInclude Include="serializer\JsonDeserializer.h" />
+    <ClInclude Include="serializer\JsonSerializeFormat.h" />
+    <ClInclude Include="serializer\JsonSerializer.h" />
     <ClInclude Include="spells\AdventureSpellMechanics.h" />
     <ClInclude Include="spells\BattleSpellMechanics.h" />
     <ClInclude Include="spells\CDefaultSpellMechanics.h" />

+ 48 - 0
lib/VCMI_lib.vcxproj.filters

@@ -26,6 +26,9 @@
     <Filter Include="spells">
       <UniqueIdentifier>{bda963b1-00e1-412a-9b44-f5cd3f8e9e33}</UniqueIdentifier>
     </Filter>
+    <Filter Include="serializer">
+      <UniqueIdentifier>{2f582170-d8a6-42f3-8da3-8255bac28f5a}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="BattleAction.cpp" />
@@ -234,6 +237,24 @@
       <Filter>filesystem</Filter>
     </ClCompile>
     <ClCompile Include="filesystem\FileInfo.cpp" />
+    <ClCompile Include="serializer\JsonDeserializer.cpp">
+      <Filter>serializer</Filter>
+    </ClCompile>
+    <ClCompile Include="serializer\JsonSerializeFormat.cpp">
+      <Filter>serializer</Filter>
+    </ClCompile>
+    <ClCompile Include="serializer\JsonSerializer.cpp">
+      <Filter>serializer</Filter>
+    </ClCompile>
+    <ClCompile Include="filesystem\CMemoryBuffer.cpp">
+      <Filter>filesystem</Filter>
+    </ClCompile>
+    <ClCompile Include="filesystem\CZipSaver.cpp">
+      <Filter>filesystem</Filter>
+    </ClCompile>
+    <ClCompile Include="filesystem\MinizipExtensions.cpp">
+      <Filter>filesystem</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="CCreatureSet.h">
@@ -581,5 +602,32 @@
     <ClInclude Include="filesystem\FileInfo.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="serializer\JsonDeserializer.h">
+      <Filter>serializer</Filter>
+    </ClInclude>
+    <ClInclude Include="serializer\JsonSerializeFormat.h">
+      <Filter>serializer</Filter>
+    </ClInclude>
+    <ClInclude Include="serializer\JsonSerializer.h">
+      <Filter>serializer</Filter>
+    </ClInclude>
+    <ClInclude Include="filesystem\CInputOutputStream.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
+    <ClInclude Include="filesystem\CMemoryBuffer.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
+    <ClInclude Include="filesystem\COutputStream.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
+    <ClInclude Include="filesystem\CStream.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
+    <ClInclude Include="filesystem\CZipSaver.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
+    <ClInclude Include="filesystem\MinizipExtensions.h">
+      <Filter>filesystem</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>

+ 1 - 0
lib/filesystem/CFileInputStream.cpp

@@ -18,6 +18,7 @@ CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 st
 	fileStream.seekg(dataStart, std::ios::beg);
 }
 
+
 si64 CFileInputStream::read(ui8 * data, si64 size)
 {
 	si64 origin = tell();

+ 9 - 0
lib/filesystem/CInputOutputStream.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "CInputStream.h"
+#include "COutputStream.h"
+
+class CInputOutputStream: public CInputStream, public COutputStream
+{
+	
+};

+ 3 - 31
lib/filesystem/CInputStream.h

@@ -1,5 +1,7 @@
 #pragma once
 
+#include "CStream.h"
+
 /*
  * CInputStream.h, part of VCMI engine
  *
@@ -13,7 +15,7 @@
 /**
  * Abstract class which provides method definitions for reading from a stream.
  */
-class DLL_LINKAGE CInputStream : private boost::noncopyable
+class DLL_LINKAGE CInputStream : public virtual CStream
 {
 public:
 	/**
@@ -30,36 +32,6 @@ public:
 	 */
 	virtual si64 read(ui8 * data, si64 size) = 0;
 
-	/**
-	 * Seeks the internal read pointer to the specified position.
-	 *
-	 * @param position The read position from the beginning.
-	 * @return the position actually moved to, -1 on error.
-	 */
-	virtual si64 seek(si64 position) = 0;
-
-	/**
-	 * Gets the current read position in the stream.
-	 *
-	 * @return the read position.
-	 */
-	virtual si64 tell() = 0;
-
-	/**
-	 * Skips delta numbers of bytes.
-	 *
-	 * @param delta The count of bytes to skip.
-	 * @return the count of bytes skipped actually.
-	 */
-	virtual si64 skip(si64 delta) = 0;
-
-	/**
-	 * Gets the length in bytes of the stream.
-	 *
-	 * @return the length in bytes of the stream.
-	 */
-	virtual si64 getSize() = 0;
-
 	/**
 	 * @brief for convenience, reads whole stream at once
 	 *

+ 73 - 0
lib/filesystem/CMemoryBuffer.cpp

@@ -0,0 +1,73 @@
+/*
+ * CMemoryBuffer.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 "CMemoryBuffer.h"
+
+///CMemoryBuffer
+CMemoryBuffer::CMemoryBuffer():
+	position(0)
+{
+	buffer.reserve(4096);
+}
+
+si64 CMemoryBuffer::write(const ui8 * data, si64 size)
+{
+	//do not shrink
+	const si64 newSize = tell()+size;
+	if(newSize>getSize())
+		buffer.resize(newSize);
+	
+	std::copy(data, data + size, buffer.data() + position);
+	position += size;
+	
+	return size;		
+}
+
+si64 CMemoryBuffer::read(ui8 * data, si64 size)
+{
+	si64 toRead = std::min(getSize() - tell(), size);
+	
+	if(toRead > 0)
+	{
+		std::copy(buffer.data() + position, buffer.data() + position + toRead, data);
+		position += toRead;		
+	}
+	
+	
+	return toRead;	
+}
+
+si64 CMemoryBuffer::seek(si64 position)
+{
+	this->position = position;
+	if (this->position >getSize())
+		this->position = getSize();
+	return this->position;
+}
+
+si64 CMemoryBuffer::tell()
+{
+	return position;
+}
+
+si64 CMemoryBuffer::skip(si64 delta)
+{
+	auto old_position = tell();
+	
+	return seek(old_position + delta) - old_position; 
+}
+
+si64 CMemoryBuffer::getSize()
+{
+	return buffer.size();
+}
+
+

+ 88 - 0
lib/filesystem/CMemoryBuffer.h

@@ -0,0 +1,88 @@
+#pragma once
+
+/*
+ * CMemoryBuffer.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
+ *
+ */
+ 
+
+#include "CInputOutputStream.h"
+
+/**
+ * A class which provides IO memory buffer.
+ */
+ 
+class DLL_LINKAGE CMemoryBuffer : public CInputOutputStream
+{
+public:
+	typedef std::vector<ui8> TBuffer;
+
+	/**
+	 * C-tor.
+	 *
+	 */
+	CMemoryBuffer();
+	
+	/**
+	 * Write n bytes from the stream into the data buffer.
+	 *
+	 * @param data A pointer to the destination data array.
+	 * @param size The number of bytes to write.
+	 * @return the number of bytes written actually.
+	 */
+	si64 write(const ui8 * data, si64 size) override;	
+
+	/**
+	 * Reads n bytes from the stream into the data buffer.
+	 *
+	 * @param data A pointer to the destination data array.
+	 * @param size The number of bytes to read.
+	 * @return the number of bytes read actually.
+	 */
+	si64 read(ui8 * data, si64 size) override;
+
+	/**
+	 * Seeks the internal read pointer to the specified position.
+	 *
+	 * @param position The read position from the beginning.
+	 * @return the position actually moved to, -1 on error.
+	 */
+	si64 seek(si64 position) override;
+
+	/**
+	 * Gets the current read position in the stream.
+	 *
+	 * @return the read position.
+	 */
+	si64 tell() override;
+
+	/**
+	 * Skips delta numbers of bytes.
+	 *
+	 * @param delta The count of bytes to skip.
+	 * @return the count of bytes skipped actually.
+	 */
+	si64 skip(si64 delta) override;
+
+	/**
+	 * Gets the length in bytes of the stream.
+	 *
+	 * @return the length in bytes of the stream.
+	 */
+	si64 getSize() override;
+	
+	const TBuffer & getBuffer(){return buffer;}
+
+private:
+	/** Actual data. */
+	TBuffer buffer;
+	
+	/** Current reading position of the stream. */
+	si64 position;
+};
+

+ 2 - 1
lib/filesystem/CMemoryStream.h

@@ -14,7 +14,8 @@
 
 /**
  * A class which provides method definitions for reading from memory.
- */
+ * @deprecated use CMemoryBuffer
+ */ 
 class DLL_LINKAGE CMemoryStream : public CInputStream
 {
 public:

+ 34 - 0
lib/filesystem/COutputStream.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include "CStream.h"
+
+/*
+ * COutputStream.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
+ *
+ */
+
+/**
+ * Abstract class which provides method definitions for writing into a stream.
+ */
+class DLL_LINKAGE COutputStream : public virtual CStream
+{
+public:
+	/**
+	 * D-tor.
+	 */
+	virtual ~COutputStream() {}
+
+	/**
+	 * Write n bytes from the stream into the data buffer.
+	 *
+	 * @param data A pointer to the destination data array.
+	 * @param size The number of bytes to write.
+	 * @return the number of bytes written actually.
+	 */
+	virtual si64 write(const ui8 * data, si64 size) = 0;
+};

+ 50 - 0
lib/filesystem/CStream.h

@@ -0,0 +1,50 @@
+#pragma once
+
+/*
+ * CStream.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
+ *
+ */
+
+class DLL_LINKAGE CStream : private boost::noncopyable
+{
+public:
+	/**
+	 * D-tor.
+	 */
+	virtual ~CStream() {}
+
+	/**
+	 * Seeks to the specified position.
+	 *
+	 * @param position The position from the beginning.
+	 * @return the position actually moved to, -1 on error.
+	 */
+	virtual si64 seek(si64 position) = 0;
+
+	/**
+	 * Gets the current position in the stream.
+	 *
+	 * @return the position.
+	 */
+	virtual si64 tell() = 0;
+
+	/**
+	 * Relative seeks to the specified position.
+	 *
+	 * @param delta The count of bytes to seek from current position.
+	 * @return the count of bytes skipped actually.
+	 */
+	virtual si64 skip(si64 delta) = 0;
+
+	/**
+	 * Gets the length of the stream.
+	 *
+	 * @return the length in bytes
+	 */
+	virtual si64 getSize() = 0;	
+};

+ 15 - 6
lib/filesystem/CZipLoader.cpp

@@ -1,5 +1,4 @@
 #include "StdInc.h"
-//#include "../../Global.h"
 #include "CZipLoader.h"
 #include "FileStream.h"
 
@@ -15,9 +14,13 @@
  *
  */
 
-CZipStream::CZipStream(const boost::filesystem::path & archive, unz64_file_pos filepos)
+CZipStream::CZipStream(std::shared_ptr<CIOApi> api, const boost::filesystem::path & archive, unz64_file_pos filepos)
 {
-	file = unzOpen2_64(archive.c_str(), FileStream::GetMinizipFilefunc());
+	zlib_filefunc64_def zlibApi;
+
+	zlibApi = api->getApiStructure();
+
+	file = unzOpen2_64(archive.c_str(), &zlibApi);
 	unzGoToFilePos64(file, &filepos);
 	unzOpenCurrentFile(file);
 }
@@ -47,7 +50,10 @@ ui32 CZipStream::calculateCRC32()
 	return info.crc;
 }
 
-CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive):
+///CZipLoader
+CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr<CIOApi> api):
+	ioApi(api),
+    zlibApi(ioApi->getApiStructure()),
     archiveName(archive),
     mountPoint(mountPoint),
     files(listFiles(mountPoint, archive))
@@ -59,7 +65,10 @@ std::unordered_map<ResourceID, unz64_file_pos> CZipLoader::listFiles(const std::
 {
 	std::unordered_map<ResourceID, unz64_file_pos> ret;
 
-	unzFile file = unzOpen2_64(archive.c_str(), FileStream::GetMinizipFilefunc());
+	unzFile file = unzOpen2_64(archive.c_str(), &zlibApi);
+
+	if(file == nullptr)
+		logGlobal->errorStream() << archive << " failed to open";
 
 	if (unzGoToFirstFile(file) == UNZ_OK)
 	{
@@ -86,7 +95,7 @@ std::unordered_map<ResourceID, unz64_file_pos> CZipLoader::listFiles(const std::
 
 std::unique_ptr<CInputStream> CZipLoader::load(const ResourceID & resourceName) const
 {
-	return make_unique<CZipStream>(archiveName, files.at(resourceName));
+	return std::unique_ptr<CInputStream>(new CZipStream(ioApi, archiveName, files.at(resourceName)));
 }
 
 bool CZipLoader::existsResource(const ResourceID & resourceName) const

+ 6 - 9
lib/filesystem/CZipLoader.h

@@ -15,12 +15,7 @@
 #include "ResourceID.h"
 #include "CCompressedStream.h"
 
-// Necessary here in order to get all types
-#ifdef USE_SYSTEM_MINIZIP
-#include <minizip/unzip.h>
-#else
-#include "../minizip/unzip.h"
-#endif
+#include "MinizipExtensions.h"
 
 class DLL_LINKAGE CZipStream : public CBufferedStream
 {
@@ -29,10 +24,11 @@ class DLL_LINKAGE CZipStream : public CBufferedStream
 public:
 	/**
 	 * @brief constructs zip stream from already opened file
+	 * @param api virtual filesystem interface
 	 * @param archive path to archive to open
 	 * @param filepos position of file to open
 	 */
-	CZipStream(const boost::filesystem::path & archive, unz64_file_pos filepos);
+	CZipStream(std::shared_ptr<CIOApi> api, const boost::filesystem::path & archive, unz64_file_pos filepos);
 	~CZipStream();
 
 	si64 getSize() override;
@@ -44,6 +40,8 @@ protected:
 
 class DLL_LINKAGE CZipLoader : public ISimpleResourceLoader
 {
+	std::shared_ptr<CIOApi> ioApi;
+	zlib_filefunc64_def zlibApi;
 	boost::filesystem::path archiveName;
 	std::string mountPoint;
 
@@ -51,7 +49,7 @@ class DLL_LINKAGE CZipLoader : public ISimpleResourceLoader
 
 	std::unordered_map<ResourceID, unz64_file_pos> listFiles(const std::string & mountPoint, const boost::filesystem::path &archive);
 public:
-	CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive);
+	CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr<CIOApi> api = std::shared_ptr<CIOApi>(new CDefaultIOApi()));
 
 	/// Interface implementation
 	/// @see ISimpleResourceLoader
@@ -61,7 +59,6 @@ public:
 	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
 };
 
-
 namespace ZipArchive
 {
 	/// List all files present in archive

+ 120 - 0
lib/filesystem/CZipSaver.cpp

@@ -0,0 +1,120 @@
+/*
+ * CZipSaver.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 "CZipSaver.h"
+
+///CZipOutputStream
+CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename):
+	handle(archive),
+	owner(owner_)
+{
+	zip_fileinfo fileInfo;
+	
+	std::time_t t = time(nullptr);
+	fileInfo.dosDate = 0;	
+	
+	struct tm * localTime = std::localtime(&t);
+	fileInfo.tmz_date.tm_hour = localTime->tm_hour;
+	fileInfo.tmz_date.tm_mday = localTime->tm_mday;
+	fileInfo.tmz_date.tm_min  = localTime->tm_min;
+	fileInfo.tmz_date.tm_mon  = localTime->tm_mon;
+	fileInfo.tmz_date.tm_sec  = localTime->tm_sec;
+	fileInfo.tmz_date.tm_year = localTime->tm_year;
+		
+	fileInfo.external_fa = 0; //??? 
+	fileInfo.internal_fa = 0;	
+	
+	int status = zipOpenNewFileInZip4_64(
+						handle,
+						archiveFilename.c_str(),
+						&fileInfo,
+						nullptr,//extrafield_local
+						0,
+						nullptr,//extrafield_global
+						0,
+						nullptr,//comment
+						Z_DEFLATED,
+						Z_DEFAULT_COMPRESSION,
+						0,//raw
+						-15,//windowBits
+						9,//memLevel
+						Z_DEFAULT_STRATEGY,//strategy
+						nullptr,//password
+						0,//crcForCrypting
+						20,//versionMadeBy
+						0,//flagBase
+						0//zip64
+						);
+    
+    if(status != ZIP_OK)
+		throw new std::runtime_error("CZipOutputStream: zipOpenNewFileInZip failed");
+	
+	owner->activeStream = this;
+}
+
+CZipOutputStream::~CZipOutputStream()
+{
+	int status = zipCloseFileInZip(handle);
+	if (status != ZIP_OK)
+		logGlobal->errorStream() << "CZipOutputStream: stream finalize failed: "<<status;
+	owner->activeStream = nullptr;
+}
+
+si64 CZipOutputStream::write(const ui8 * data, si64 size)
+{
+	int ret = zipWriteInFileInZip(handle, (const void*)data, (unsigned)size);
+	
+	if (ret == ZIP_OK)
+		return size;
+	else
+		return 0;
+}
+
+///CZipSaver
+CZipSaver::CZipSaver(std::shared_ptr<CIOApi> api, const std::string & path):
+	ioApi(api),
+	zipApi(ioApi->getApiStructure()),
+	handle(nullptr),
+	activeStream(nullptr)
+{
+	handle = zipOpen2_64(path.c_str(), APPEND_STATUS_CREATE, nullptr, &zipApi);
+	
+	if (handle == nullptr)
+		throw new std::runtime_error("CZipSaver: Failed to create archive");	
+}
+
+CZipSaver::~CZipSaver()
+{
+	if(activeStream != nullptr)
+	{
+		logGlobal->error("CZipSaver::~CZipSaver: active stream found");	
+		zipCloseFileInZip(handle);
+	}
+		
+	
+	if(handle != nullptr)
+	{
+		int status = zipClose(handle, nullptr);
+		if (status != ZIP_OK)
+			logGlobal->errorStream() << "CZipSaver: archive finalize failed: "<<status;		
+	}
+		
+}
+
+std::unique_ptr<COutputStream> CZipSaver::addFile(const std::string & archiveFilename)
+{
+	if(activeStream != nullptr)
+		throw new std::runtime_error("CZipSaver::addFile: stream already opened");
+	
+	std::unique_ptr<COutputStream> stream(new CZipOutputStream(this, handle, archiveFilename));
+	return std::move(stream);
+}
+

+ 57 - 0
lib/filesystem/CZipSaver.h

@@ -0,0 +1,57 @@
+#pragma once
+
+/*
+ * CZipSaver.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
+ *
+ */
+
+#include "COutputStream.h"
+
+#include "MinizipExtensions.h"
+
+class CZipSaver;
+
+class DLL_LINKAGE CZipOutputStream: public COutputStream
+{
+public:
+	/**
+	 * @brief constructs zip stream from already opened file
+	 * @param archive archive handle, must be opened
+	 * @param archiveFilename name of file to write
+	 */	
+	explicit CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename);
+	~CZipOutputStream();
+	
+	si64 write(const ui8 * data, si64 size) override;
+
+	si64 seek(si64 position) override {return -1;};
+	si64 tell() override {return 0;};
+	si64 skip(si64 delta) override {return 0;};
+	si64 getSize() override {return 0;};	
+private:
+	zipFile handle;
+	CZipSaver * owner;
+};
+
+class DLL_LINKAGE CZipSaver
+{
+public:	
+	explicit CZipSaver(std::shared_ptr<CIOApi> api, const std::string & path);
+	virtual ~CZipSaver();
+	
+	std::unique_ptr<COutputStream> addFile(const std::string & archiveFilename);
+private:
+	std::shared_ptr<CIOApi> ioApi;
+	zlib_filefunc64_def zipApi;	
+	
+	zipFile handle;
+	
+	///due to minizip design only one file stream may opened at a time
+	COutputStream * activeStream;
+	friend class CZipOutputStream;
+};

+ 254 - 0
lib/filesystem/MinizipExtensions.cpp

@@ -0,0 +1,254 @@
+/*
+ * MinizipExtensions.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 "MinizipExtensions.h"
+
+#include "CMemoryBuffer.h"
+#include "FileStream.h"
+
+template <class _Stream> inline uLong streamRead(voidpf opaque, voidpf stream, void * buf, uLong size)
+{
+	assert(opaque != nullptr);
+	assert(stream != nullptr);
+
+	_Stream * actualStream = static_cast<_Stream *>(stream);
+
+	return actualStream->read((ui8 *)buf, size);
+}
+
+template <class _Stream> inline ZPOS64_T streamTell(voidpf opaque, voidpf stream)
+{
+	assert(opaque != nullptr);
+	assert(stream != nullptr);
+
+	_Stream * actualStream = static_cast<_Stream *>(stream);
+    return actualStream->tell();
+}
+
+template <class _Stream> inline long streamSeek(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+	assert(opaque != nullptr);
+	assert(stream != nullptr);
+
+	_Stream * actualStream = static_cast<_Stream *>(stream);
+
+    long ret = 0;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        if(actualStream->skip(offset) != offset)
+			ret = -1;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END:
+    	{
+    		const si64 pos = actualStream->getSize() - offset;
+    		if(actualStream->seek(pos) != pos)
+				ret = -1;
+    	}
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+		if(actualStream->seek(offset) != offset)
+			ret = -1;
+        break;
+    default: ret = -1;
+    }
+    if(ret == -1)
+		logGlobal->error("Stream seek failed");
+    return ret;
+}
+
+template <class _Stream> inline int streamProxyClose(voidpf opaque, voidpf stream)
+{
+	assert(opaque != nullptr);
+	assert(stream != nullptr);
+
+	_Stream * actualStream = static_cast<_Stream *>(stream);
+
+	logGlobal->traceStream() << "Proxy stream closed";
+
+	actualStream->seek(0);
+
+    return 0;
+}
+
+///CDefaultIOApi
+CDefaultIOApi::CDefaultIOApi()
+{
+
+}
+
+CDefaultIOApi::~CDefaultIOApi()
+{
+
+}
+
+zlib_filefunc64_def CDefaultIOApi::getApiStructure()
+{
+	return * FileStream::GetMinizipFilefunc();
+}
+
+///CProxyIOApi
+CProxyIOApi::CProxyIOApi(CInputOutputStream * buffer):
+	data(buffer)
+{
+
+}
+
+CProxyIOApi::~CProxyIOApi()
+{
+
+}
+
+zlib_filefunc64_def CProxyIOApi::getApiStructure()
+{
+	zlib_filefunc64_def api;
+	api.opaque = this;
+	api.zopen64_file = &openFileProxy;
+	api.zread_file = &readFileProxy;
+	api.zwrite_file = &writeFileProxy;
+	api.ztell64_file = &tellFileProxy;
+	api.zseek64_file = &seekFileProxy;
+	api.zclose_file = &closeFileProxy;
+	api.zerror_file = &errorFileProxy;
+
+	return api;
+}
+
+voidpf ZCALLBACK CProxyIOApi::openFileProxy(voidpf opaque, const void * filename, int mode)
+{
+	assert(opaque != nullptr);
+
+	boost::filesystem::path path;
+
+	if(filename != nullptr)
+		path =  static_cast<const boost::filesystem::path::value_type *>(filename);
+
+	return ((CProxyIOApi *)opaque)->openFile(path, mode);
+}
+
+uLong ZCALLBACK CProxyIOApi::readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size)
+{
+	return streamRead<CInputOutputStream>(opaque, stream, buf, size);
+}
+
+uLong ZCALLBACK CProxyIOApi::writeFileProxy(voidpf opaque, voidpf stream, const void * buf, uLong size)
+{
+	assert(opaque != nullptr);
+	assert(stream != nullptr);
+
+	CInputOutputStream * actualStream = static_cast<CInputOutputStream *>(stream);
+    return (uLong)actualStream->write((const ui8 *)buf, size);
+}
+
+ZPOS64_T ZCALLBACK CProxyIOApi::tellFileProxy(voidpf opaque, voidpf stream)
+{
+	return streamTell<CInputOutputStream>(opaque, stream);
+}
+
+long ZCALLBACK CProxyIOApi::seekFileProxy(voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+	return streamSeek<CInputOutputStream>(opaque, stream, offset, origin);
+}
+
+int ZCALLBACK CProxyIOApi::closeFileProxy(voidpf opaque, voidpf stream)
+{
+	return streamProxyClose<CInputOutputStream>(opaque, stream);
+}
+
+int ZCALLBACK CProxyIOApi::errorFileProxy(voidpf opaque, voidpf stream)
+{
+    return 0;
+}
+
+CInputOutputStream * CProxyIOApi::openFile(const boost::filesystem::path & filename, int mode)
+{
+	logGlobal->traceStream() << "CProxyIOApi: stream opened for " <<filename.string() <<" with mode "<<mode;
+
+	data->seek(0);
+	return data;
+}
+
+///CProxyROIOApi
+CProxyROIOApi::CProxyROIOApi(CInputStream * buffer):
+	data(buffer)
+{
+
+}
+
+CProxyROIOApi::~CProxyROIOApi()
+{
+
+}
+
+zlib_filefunc64_def CProxyROIOApi::getApiStructure()
+{
+	zlib_filefunc64_def api;
+	api.opaque = this;
+	api.zopen64_file = &openFileProxy;
+	api.zread_file = &readFileProxy;
+	api.zwrite_file = &writeFileProxy;
+	api.ztell64_file = &tellFileProxy;
+	api.zseek64_file = &seekFileProxy;
+	api.zclose_file = &closeFileProxy;
+	api.zerror_file = &errorFileProxy;
+
+	return api;
+}
+
+CInputStream * CProxyROIOApi::openFile(const boost::filesystem::path& filename, int mode)
+{
+	logGlobal->traceStream() << "CProxyIOApi: stream opened for " <<filename.string() <<" with mode "<<mode;
+
+	data->seek(0);
+	return data;
+}
+
+voidpf ZCALLBACK CProxyROIOApi::openFileProxy(voidpf opaque, const void* filename, int mode)
+{
+	assert(opaque != nullptr);
+
+	boost::filesystem::path path;
+
+	if(filename != nullptr)
+		path =  static_cast<const boost::filesystem::path::value_type *>(filename);
+
+	return ((CProxyROIOApi *)opaque)->openFile(path, mode);
+}
+
+uLong ZCALLBACK CProxyROIOApi::readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size)
+{
+	return streamRead<CInputStream>(opaque, stream, buf, size);
+}
+
+uLong ZCALLBACK CProxyROIOApi::writeFileProxy(voidpf opaque, voidpf stream, const void* buf, uLong size)
+{
+	logGlobal->errorStream() << "Attempt to write to read-only stream";
+	return 0;
+}
+
+ZPOS64_T ZCALLBACK CProxyROIOApi::tellFileProxy(voidpf opaque, voidpf stream)
+{
+	return streamTell<CInputStream>(opaque, stream);
+}
+
+long ZCALLBACK CProxyROIOApi::seekFileProxy(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+	return streamSeek<CInputStream>(opaque, stream, offset, origin);
+}
+
+int ZCALLBACK CProxyROIOApi::closeFileProxy(voidpf opaque, voidpf stream)
+{
+	return streamProxyClose<CInputStream>(opaque, stream);
+}
+
+int ZCALLBACK CProxyROIOApi::errorFileProxy(voidpf opaque, voidpf stream)
+{
+	return 0;
+}

+ 88 - 0
lib/filesystem/MinizipExtensions.h

@@ -0,0 +1,88 @@
+#pragma once
+
+/*
+ * MinizipExtensions.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
+ *
+ */
+
+#ifdef USE_SYSTEM_MINIZIP
+#include <minizip/unzip.h>
+#include <minizip/zip.h>
+#include <minizip/ioapi.h>
+#else
+#include "../minizip/unzip.h"
+#include "../minizip/zip.h"
+#include "../minizip/ioapi.h"
+#endif
+class CInputStream;
+class CInputOutputStream;
+class CMemoryBuffer;
+
+class DLL_LINKAGE CIOApi
+{
+public:
+	virtual ~CIOApi(){};
+
+	virtual zlib_filefunc64_def getApiStructure() = 0;
+};
+
+///redirects back to minizip ioapi
+//todo: replace with Virtual FileSystem interface
+class DLL_LINKAGE CDefaultIOApi: public CIOApi
+{
+public:
+	CDefaultIOApi();
+	~CDefaultIOApi();
+
+	zlib_filefunc64_def getApiStructure() override;
+};
+
+///redirects all file IO to single stream
+class DLL_LINKAGE CProxyIOApi: public CIOApi
+{
+public:
+	CProxyIOApi(CInputOutputStream * buffer);
+	~CProxyIOApi();
+
+	zlib_filefunc64_def getApiStructure() override;
+private:
+	CInputOutputStream * openFile(const boost::filesystem::path & filename, int mode);
+
+	CInputOutputStream * data;
+
+	static voidpf ZCALLBACK openFileProxy(voidpf opaque, const void * filename, int mode);
+	static uLong ZCALLBACK readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size);
+	static uLong ZCALLBACK writeFileProxy(voidpf opaque, voidpf stream, const void * buf, uLong size);
+	static ZPOS64_T ZCALLBACK tellFileProxy(voidpf opaque, voidpf stream);
+	static long ZCALLBACK seekFileProxy(voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin);
+	static int ZCALLBACK closeFileProxy(voidpf opaque, voidpf stream);
+	static int ZCALLBACK errorFileProxy(voidpf opaque, voidpf stream);
+};
+
+///redirects all file IO to single stream read-only
+class DLL_LINKAGE CProxyROIOApi: public CIOApi
+{
+public:
+	CProxyROIOApi(CInputStream * buffer);
+	~CProxyROIOApi();
+
+	zlib_filefunc64_def getApiStructure() override;
+private:
+	CInputStream * openFile(const boost::filesystem::path & filename, int mode);
+
+	CInputStream * data;
+
+	static voidpf ZCALLBACK openFileProxy(voidpf opaque, const void * filename, int mode);
+	static uLong ZCALLBACK readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size);
+	static uLong ZCALLBACK writeFileProxy(voidpf opaque, voidpf stream, const void * buf, uLong size);
+	static ZPOS64_T ZCALLBACK tellFileProxy(voidpf opaque, voidpf stream);
+	static long ZCALLBACK seekFileProxy(voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin);
+	static int ZCALLBACK closeFileProxy(voidpf opaque, voidpf stream);
+	static int ZCALLBACK errorFileProxy(voidpf opaque, voidpf stream);
+};
+

+ 2 - 1
lib/filesystem/ResourceID.cpp

@@ -136,7 +136,8 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension)
 		{".VSGM1", EResType::SERVER_SAVEGAME},
 		{".ERM",   EResType::ERM},
 		{".ERT",   EResType::ERT},
-		{".ERS",   EResType::ERS}
+		{".ERS",   EResType::ERS},
+		{".VMAP",  EResType::MAP}
 	};
 
 	auto iter = stringToRes.find(extension);

+ 1 - 1
lib/mapObjects/CArmedInstance.cpp

@@ -108,7 +108,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 			undeadModifier = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]);
 			undeadModifier->description = undeadModifier->description.substr(0, undeadModifier->description.size()-2);//trim value
 			addNewBonus(undeadModifier);
-		}			
+		}
 	}
 	else if(undeadModifier)
 		removeBonus(undeadModifier);

+ 44 - 0
lib/mapObjects/CGHeroInstance.cpp

@@ -25,6 +25,7 @@
 #include "../CTownHandler.h"
 #include "../mapping/CMap.h"
 #include "CGTownInstance.h"
+#include "../serializer/JsonSerializeFormat.h"
 
 ///helpers
 static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID)
@@ -1470,6 +1471,49 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subty
 	return (distance < visionsRange) && (target->pos.z == pos.z);
 }
 
+void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	serializeJsonOwner(handler);
+
+	if(handler.saving)
+	{
+		if(type)
+		{
+			handler.serializeString("type", type->identifier);
+		}
+		else
+		{
+			auto temp = VLC->heroh->heroes[subID]->identifier;
+			handler.serializeString("type", temp);
+		}
+	}
+	else
+	{
+		if(ID == Obj::HERO || ID == Obj::PRISON)
+		{
+			std::string typeName;
+			handler.serializeString("type", typeName);
+
+			auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", typeName);
+
+			if(rawId)
+				subID = rawId.get();
+			else
+				subID = 0; //fallback to Orrin, throw error instead?
+		}
+	}
+	CCreatureSet::serializeJson(handler, "army");
+
+	{
+		auto artifacts = handler.enterStruct("artifacts");
+		if(handler.saving)
+			CArtifactSet::writeJson(handler.getCurrent());
+		else
+			CArtifactSet::readJson(handler.getCurrent());
+	}
+
+}
+
 bool CGHeroInstance::isMissionCritical() const
 {
 	for(const TriggeredEvent & event : IObjectInterface::cb->getMapHeader()->triggeredEvents)

+ 1 - 0
lib/mapObjects/CGHeroInstance.h

@@ -251,6 +251,7 @@ public:
 	std::string getObjectName() const override;
 protected:
 	void setPropertyDer(ui8 what, ui32 val) override;//synchr
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 private:
 	void levelUpAutomatically();

+ 66 - 0
lib/mapObjects/CGTownInstance.cpp

@@ -11,6 +11,7 @@
 #include "StdInc.h"
 #include "CGTownInstance.h"
 #include "CObjectClassesHandler.h"
+#include "../spells/CSpellHandler.h"
 
 #include "../NetPacks.h"
 #include "../CGeneralTextHandler.h"
@@ -19,6 +20,7 @@
 #include "../CGameState.h"
 #include "../mapping/CMapDefines.h"
 #include "../CPlayerState.h"
+#include "../serializer/JsonSerializeFormat.h"
 
 std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
 std::vector<int> CGTownInstance::universitySkills;
@@ -314,6 +316,13 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 	}
 }
 
+void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	//todo: CGDwelling::serializeJsonOptions
+	if(ID != Obj::WAR_MACHINE_FACTORY && ID != Obj::REFUGEE_CAMP)
+		serializeJsonOwner(handler);
+}
+
 int CGTownInstance::getSightRadius() const //returns sight distance
 {
 	if (subID == ETownType::TOWER)
@@ -1181,6 +1190,63 @@ void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResu
 	}
 }
 
+void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	CGObjectInstance::serializeJsonOwner(handler);
+	CCreatureSet::serializeJson(handler, "army");
+	handler.serializeBool<ui8>("tightFormation", 1, 0, formation);
+	handler.serializeString("name", name);
+
+
+
+	if(!handler.saving)
+	{
+		builtBuildings.insert(BuildingID::DEFAULT);//just in case
+	}
+
+	//todo: serialize buildings
+//	{
+//		std::vector<bool> standard;
+//		standard.resize(44, true);
+//
+//
+//		JsonSerializeFormat::LIC buildingsLIC(, CTownHandler::decodeBuilding, CTownHandler::encodeBuilding);
+//	}
+
+	{
+		JsonSerializeFormat::LIC spellsLIC(VLC->spellh->getDefaultAllowed(), CSpellHandler::decodeSpell, CSpellHandler::encodeSpell);
+
+		for(SpellID id : possibleSpells)
+			spellsLIC.any[id.num] = true;
+
+		for(SpellID id : obligatorySpells)
+			spellsLIC.all[id.num] = true;
+
+		handler.serializeLIC("spells", spellsLIC);
+
+		if(!handler.saving)
+		{
+			possibleSpells.clear();
+			for(si32 idx = 0; idx < spellsLIC.any.size(); idx++)
+			{
+				if(spellsLIC.any[idx])
+				{
+					possibleSpells.push_back(SpellID(idx));
+				}
+			}
+
+			obligatorySpells.clear();
+			for(si32 idx = 0; idx < spellsLIC.all.size(); idx++)
+			{
+				if(spellsLIC.all[idx])
+				{
+					obligatorySpells.push_back(SpellID(idx));
+				}
+			}
+		}
+	}
+}
+
 COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN)
 {
 	ID = index;

+ 4 - 0
lib/mapObjects/CGTownInstance.h

@@ -52,6 +52,9 @@ public:
 	CSpecObjInfo * info; //h3m info about dewlling
 	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
 
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
+
 private:
 	void initObj() override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
@@ -254,4 +257,5 @@ public:
 	std::string getObjectName() const override;
 protected:
 	void setPropertyDer(ui8 what, ui32 val) override;
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };

+ 54 - 24
lib/mapObjects/CObjectClassesHandler.cpp

@@ -74,7 +74,6 @@ CObjectClassesHandler::CObjectClassesHandler()
 	SET_HANDLER("pandora", CGPandoraBox);
 	SET_HANDLER("pickable", CGPickable);
 	SET_HANDLER("prison", CGHeroInstance);
-	SET_HANDLER("prison", CGHeroInstance);
 	SET_HANDLER("questGuard", CGQuestGuard);
 	SET_HANDLER("resource", CGResource);
 	SET_HANDLER("scholar", CGScholar);
@@ -148,17 +147,21 @@ si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID)
 	return defaultID; // some H3M objects loaded, first modded found
 }
 
-void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj)
+void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj)
 {
 	if (!handlerConstructors.count(obj->handlerName))
 	{
 		logGlobal->errorStream() << "Handler with name " << obj->handlerName << " was not found!";
 		return;
 	}
-	si32 id = selectNextID(entry["index"], obj->objects, 1000);
+
+	std::string convertedId = VLC->modh->normalizeIdentifier(entry.meta, "core", identifier);
+
+	si32 id = selectNextID(entry["index"], obj->subObjects, 1000);
 
 	auto handler = handlerConstructors.at(obj->handlerName)();
 	handler->setType(obj->id, id);
+	handler->setTypeName(obj->identifier, convertedId);
 
 	if (customNames.count(obj->id) && customNames.at(obj->id).size() > id)
 		handler->init(entry, customNames.at(obj->id).at(id));
@@ -175,50 +178,49 @@ void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContai
 		legacyTemplates.erase(range.first, range.second);
 	}
 
-	logGlobal->debugStream() << "Loaded object " << obj->id << ":" << id;
-	assert(!obj->objects.count(id)); // DO NOT override
-	obj->objects[id] = handler;
+	logGlobal->debugStream() << "Loaded object " << obj->identifier << "(" << obj->id << ")" << ":" << convertedId << "(" << id << ")" ;
+	assert(!obj->subObjects.count(id)); // DO NOT override
+	obj->subObjects[id] = handler;
+	obj->subIds[convertedId] = id;
 }
 
-CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json)
+CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json, const std::string & name)
 {
 	auto obj = new ObjectContainter();
+	obj->identifier = name;
 	obj->name = json["name"].String();
 	obj->handlerName = json["handler"].String();
 	obj->base = json["base"];
 	obj->id = selectNextID(json["index"], objects, 256);
 	for (auto entry : json["types"].Struct())
 	{
-		loadObjectEntry(entry.second, obj);
+		loadObjectEntry(entry.first, entry.second, obj);
 	}
 	return obj;
 }
 
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
 {
-	auto object = loadFromJson(data);
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	objects[object->id] = object;
-
 	VLC->modh->identifiers.registerObject(scope, "object", name, object->id);
 }
 
 void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
 {
-	auto object = loadFromJson(data);
-
+	auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name));
 	assert(objects[index] == nullptr); // ensure that this id was not loaded before
 	objects[index] = object;
-
 	VLC->modh->identifiers.registerObject(scope, "object", name, object->id);
 }
 
-void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional<si32> subID)
+void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional<si32> subID)
 {
 	config.setType(JsonNode::DATA_STRUCT); // ensure that input is not NULL
 	assert(objects.count(ID));
 	if (subID)
 	{
-		assert(objects.at(ID)->objects.count(subID.get()) == 0);
+		assert(objects.at(ID)->subObjects.count(subID.get()) == 0);
 		assert(config["index"].isNull());
 		config["index"].Float() = subID.get();
 	}
@@ -227,14 +229,14 @@ void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si3
 	JsonUtils::inherit(config, objects.at(ID)->base);
 	config.setMeta(oldMeta);
 
-	loadObjectEntry(config, objects[ID]);
+	loadObjectEntry(identifier, config, objects[ID]);
 }
 
 void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID)
 {
 	assert(objects.count(ID));
-	assert(objects.at(ID)->objects.count(subID));
-	objects.at(ID)->objects.erase(subID);
+	assert(objects.at(ID)->subObjects.count(subID));
+	objects.at(ID)->subObjects.erase(subID); //TODO: cleanup string id map
 }
 
 std::vector<bool> CObjectClassesHandler::getDefaultAllowed() const
@@ -246,14 +248,28 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype)
 {
 	if (objects.count(type))
 	{
-		if (objects.at(type)->objects.count(subtype))
-			return objects.at(type)->objects.at(subtype);
+		if (objects.at(type)->subObjects.count(subtype))
+			return objects.at(type)->subObjects.at(subtype);
+	}
+	logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype;
+	throw std::runtime_error("Object type handler not found");
+	return nullptr;
+}
+
+TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string type, std::string subtype) const
+{
+	boost::optional<si32> id = VLC->modh->identifiers.getIdentifier("core", "object", type, false);
+	if(id)
+	{
+		si32 subId = objects.at(id.get())->subIds.at(subtype);
+		return objects.at(id.get())->subObjects.at(subId);
 	}
 	logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype;
-	assert(0); // FIXME: throw error?
+	throw std::runtime_error("Object type handler not found");
 	return nullptr;
 }
 
+
 std::set<si32> CObjectClassesHandler::knownObjects() const
 {
 	std::set<si32> ret;
@@ -270,7 +286,7 @@ std::set<si32> CObjectClassesHandler::knownSubObjects(si32 primaryID) const
 
 	if (objects.count(primaryID))
 	{
-		for (auto entry : objects.at(primaryID)->objects)
+		for (auto entry : objects.at(primaryID)->subObjects)
 			ret.insert(entry.first);
 	}
 	return ret;
@@ -292,7 +308,7 @@ void CObjectClassesHandler::afterLoadFinalization()
 {
 	for (auto entry : objects)
 	{
-		for (auto obj : entry.second->objects)
+		for (auto obj : entry.second->subObjects)
 		{
 			obj.second->afterLoadFinalization();
 			if (obj.second->getTemplates().empty())
@@ -301,7 +317,7 @@ void CObjectClassesHandler::afterLoadFinalization()
 	}
 
 	//duplicate existing two-way portals to make reserve for RMG
-	auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->objects;
+	auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->subObjects;
 	size_t portalCount = portalVec.size();
 	size_t currentIndex = portalCount;
 	while (portalVec.size() < 100)
@@ -341,6 +357,12 @@ void AObjectTypeHandler::setType(si32 type, si32 subtype)
 	this->subtype = subtype;
 }
 
+void AObjectTypeHandler::setTypeName(std::string type, std::string subtype)
+{
+	this->typeName = type;
+	this->subTypeName = subtype;
+}
+
 static ui32 loadJsonOrMax(const JsonNode & input)
 {
 	if (input.isNull())
@@ -387,6 +409,14 @@ bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemp
 	return false; // by default there are no overrides
 }
 
+void AObjectTypeHandler::preInitObject(CGObjectInstance * obj) const
+{
+	obj->ID = Obj(type);
+	obj->subID = subtype;
+	obj->typeName = typeName;
+	obj->subTypeName = subTypeName;
+}
+
 void AObjectTypeHandler::initTypeData(const JsonNode & input)
 {
 	// empty implementation for overrides

+ 27 - 13
lib/mapObjects/CObjectClassesHandler.h

@@ -101,22 +101,26 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable
 	/// Human-readable name of this object, used for objects like banks and dwellings, if set
 	boost::optional<std::string> objectName;
 
-	si32 type;
-	si32 subtype;
-
 	JsonNode base; /// describes base template
 
 	std::vector<ObjectTemplate> templates;
 protected:
-
+	void preInitObject(CGObjectInstance * obj) const;
 	virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const;
 
 	/// initialization for classes that inherit this one
 	virtual void initTypeData(const JsonNode & input);
 public:
+	std::string typeName;
+	std::string subTypeName;
+
+	si32 type;
+	si32 subtype;
+
 	virtual ~AObjectTypeHandler(){}
 
 	void setType(si32 type, si32 subtype);
+	void setTypeName(std::string type, std::string subtype);
 
 	/// loads generic data from Json structure and passes it towards type-specific constructors
 	void init(const JsonNode & input, boost::optional<std::string> name = boost::optional<std::string>());
@@ -149,12 +153,16 @@ public:
 	/// This should set remaining properties, including randomized or depending on map
 	virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0;
 
-	/// Returns object configuration, if available. Othervice returns NULL
+	/// Returns object configuration, if available. Otherwise returns NULL
 	virtual std::unique_ptr<IObjectInfo> getObjectInfo(ObjectTemplate tmpl) const = 0;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & type & subtype & templates & rmgInfo & objectName;
+		if(version >= 759)
+		{
+			h & typeName & subTypeName;
+		}
 	}
 };
 
@@ -167,16 +175,21 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 	struct ObjectContainter
 	{
 		si32 id;
-
+		std::string identifier;
 		std::string name; // human-readable name
-		std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map
+		std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map
 
 		JsonNode base;
-		std::map<si32, TObjectTypeHandler> objects;
+		std::map<si32, TObjectTypeHandler> subObjects;
+		std::map<std::string, si32> subIds;//full id from core scope -> subtype
 
 		template <typename Handler> void serialize(Handler &h, const int version)
 		{
-			h & name & handlerName & base & objects;
+			h & name & handlerName & base & subObjects;
+			if(version >= 759)
+			{
+				h & identifier & subIds;
+			}
 		}
 	};
 
@@ -194,8 +207,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase
 	/// format: customNames[primaryID][secondaryID] -> name
 	std::map<si32, std::vector<std::string>> customNames;
 
-	void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj);
-	ObjectContainter * loadFromJson(const JsonNode & json);
+	void loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj);
+	ObjectContainter * loadFromJson(const JsonNode & json, const std::string & name);
 public:
 	CObjectClassesHandler();
 
@@ -204,7 +217,7 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
 
-	void loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional<si32> subID = boost::optional<si32>());
+	void loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional<si32> subID = boost::optional<si32>());
 	void removeSubObject(si32 ID, si32 subID);
 
 	void beforeValidate(JsonNode & object) override;
@@ -218,10 +231,11 @@ public:
 
 	/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
 	TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
+	TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const;
 
 	std::string getObjectName(si32 type) const;
 	std::string getObjectName(si32 type, si32 subtype) const;
-	
+
 	/// Returns handler string describing the handler (for use in client)
 	std::string getObjectHandlerName(si32 type) const;
 

+ 63 - 0
lib/mapObjects/CObjectHandler.cpp

@@ -18,11 +18,14 @@
 #include "../filesystem/ResourceID.h"
 #include "../IGameCallback.h"
 #include "../CGameState.h"
+#include "../StringConstants.h"
 #include "../mapping/CMap.h"
 
 #include "CObjectClassesHandler.h"
 #include "CGTownInstance.h"
 
+#include "../serializer/JsonSerializeFormat.h"
+
 IGameCallback * IObjectInterface::cb = nullptr;
 
 ///helpers
@@ -317,6 +320,66 @@ bool CGObjectInstance::passableFor(PlayerColor color) const
 	return false;
 }
 
+void CGObjectInstance::serializeJson(JsonSerializeFormat & handler)
+{
+	//only save here, loading is handled by map loader
+	if(handler.saving)
+	{
+		handler.serializeString("type", typeName);
+		handler.serializeString("subtype", subTypeName);
+
+		handler.serializeNumeric("x", pos.x);
+		handler.serializeNumeric("y", pos.y);
+		handler.serializeNumeric("l", pos.z);
+		appearance.writeJson(handler.getCurrent()["template"], false);
+	}
+
+	{
+		auto options = handler.enterStruct("options");
+		serializeJsonOptions(handler);
+	}
+
+	if(handler.saving && handler.getCurrent()["options"].Struct().empty())
+	{
+		handler.getCurrent().Struct().erase("options");
+	}
+}
+
+void CGObjectInstance::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+
+}
+
+void CGObjectInstance::serializeJsonOwner(JsonSerializeFormat & handler)
+{
+	std::string temp;
+
+	//todo: use enum serialize
+	if(handler.saving)
+	{
+		if(tempOwner.isValidPlayer())
+		{
+			temp = GameConstants::PLAYER_COLOR_NAMES[tempOwner.getNum()];
+			handler.serializeString("owner", temp);
+		}
+	}
+	else
+	{
+		tempOwner = PlayerColor::NEUTRAL;//this method assumes that object is ownable
+
+		handler.serializeString("owner", temp);
+
+		if(temp != "")
+		{
+			auto rawOwner = vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, temp);
+			if(rawOwner >=0)
+				tempOwner = PlayerColor(rawOwner);
+			else
+				logGlobal->errorStream() << "Invalid owner :" << temp;
+		}
+	}
+}
+
 CGObjectInstanceBySubIdFinder::CGObjectInstanceBySubIdFinder(CGObjectInstance * obj) : obj(obj)
 {
 

+ 22 - 2
lib/mapObjects/CObjectHandler.h

@@ -1,4 +1,4 @@
-#pragma once
+#pragma once
 
 #include "ObjectTemplate.h"
 
@@ -21,6 +21,7 @@ class IGameCallback;
 class CGObjectInstance;
 struct MetaString;
 struct BattleResult;
+class JsonSerializeFormat;
 
 // This one teleport-specific, but has to be available everywhere in callbacks and netpacks
 // For now it's will be there till teleports code refactored and moved into own file
@@ -39,7 +40,7 @@ public:
 	virtual void newTurn() const;
 	virtual void initObj(); //synchr
 	virtual void setProperty(ui8 what, ui32 val);//synchr
-	
+
 	//Called when queries created DURING HERO VISIT are resolved
 	//First parameter is always hero that visited object and triggered the query
 	virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const;
@@ -117,6 +118,10 @@ public:
 	/// If true hero can visit this object only from neighbouring tiles and can't stand on this object
 	bool blockVisit;
 
+	std::string instanceName;
+	std::string typeName;
+	std::string subTypeName;
+
 	CGObjectInstance();
 	~CGObjectInstance();
 
@@ -168,17 +173,32 @@ public:
 
 	//friend class CGameHandler;
 
+	///Entry point of binary (de-)serialization
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
+		if(version >= 759)
+		{
+			h & instanceName & typeName & subTypeName;
+		}
+
 		h & pos & ID & subID & id & tempOwner & blockVisit & appearance;
 		//definfo is handled by map serializer
 	}
+
+	///Entry point of Json (de-)serialization
+	void serializeJson(JsonSerializeFormat & handler);
+
 protected:
 	/// virtual method that allows synchronously update object state on server and all clients
 	virtual void setPropertyDer(ui8 what, ui32 val);
 
 	/// Gives dummy bonus from this object to hero. Can be used to track visited state
 	void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const;
+
+	///Serialize object-type specific options
+	virtual void serializeJsonOptions(JsonSerializeFormat & handler);
+
+	void serializeJsonOwner(JsonSerializeFormat & handler);
 };
 
 /// function object which can be used to find an object with an specific sub ID

+ 1 - 0
lib/mapObjects/CRewardableConstructor.cpp

@@ -188,6 +188,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config)
 CGObjectInstance * CRewardableConstructor::create(ObjectTemplate tmpl) const
 {
 	auto ret = new CRewardableObject();
+	preInitObject(ret);
 	ret->appearance = tmpl;
 	return ret;
 }

+ 1 - 2
lib/mapObjects/CommonConstructors.h

@@ -32,8 +32,7 @@ protected:
 	ObjectType * createTyped(ObjectTemplate tmpl) const
 	{
 		auto obj = new ObjectType();
-		obj->ID = tmpl.id;
-		obj->subID = tmpl.subid;
+		preInitObject(obj);
 		obj->appearance = tmpl;
 		return obj;
 	}

+ 252 - 8
lib/mapObjects/MiscObjects.cpp

@@ -11,17 +11,19 @@
 #include "StdInc.h"
 #include "MiscObjects.h"
 
+#include "../StringConstants.h"
 #include "../NetPacks.h"
 #include "../CGeneralTextHandler.h"
 #include "../CSoundBase.h"
 #include "../CModHandler.h"
-
+#include "../CHeroHandler.h"
 #include "CObjectClassesHandler.h"
 #include "../spells/CSpellHandler.h"
 #include "../IGameCallback.h"
 #include "../CGameState.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
+#include "../serializer/JsonSerializeFormat.h"
 
 std::map <si32, std::vector<ObjectInstanceID> > CGMagi::eyelist;
 ui8 CGObelisk::obeliskCount = 0; //how many obelisks are on map
@@ -592,6 +594,63 @@ void CGCreature::giveReward(const CGHeroInstance * h) const
 	}
 }
 
+static const std::vector<std::string> CHARACTER_JSON  =
+{
+	"compliant", "friendly", "aggressive", "hostile", "savage"
+};
+
+void CGCreature::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	handler.serializeNumericEnum("character", CHARACTER_JSON, (si8)0, character);
+
+	if(handler.saving)
+	{
+		if(hasStackAtSlot(SlotID(0)))
+		{
+			si32 amount = getStack(SlotID(0)).count;
+			handler.serializeNumeric("amount", amount);
+		}
+
+		if(resources.nonZero())
+		{
+			for(size_t idx = 0; idx < resources.size(); idx++)
+				handler.getCurrent()["rewardResources"][GameConstants::RESOURCE_NAMES[idx]].Float() = resources[idx];
+		}
+
+		auto tmp = (gainedArtifact == ArtifactID(ArtifactID::NONE) ? "" : gainedArtifact.toArtifact()->identifier);
+		handler.serializeString("rewardArtifact", tmp);
+	}
+	else
+	{
+		si32 amount = 0;
+		handler.serializeNumeric("amount", amount);
+		auto  hlp = new CStackInstance();
+		hlp->count = amount;
+		//type will be set during initialization
+		putStack(SlotID(0), hlp);
+		{
+			TResources tmp(handler.getCurrent()["rewardResources"]);
+			std::swap(tmp,resources);
+		}
+		{
+			gainedArtifact = ArtifactID(ArtifactID::NONE);
+			std::string tmp;
+			handler.serializeString("rewardArtifact", tmp);
+
+			if(tmp != "")
+			{
+				auto artid = VLC->modh->identifiers.getIdentifier("core", "artifact", tmp);
+				if(artid)
+					gainedArtifact = ArtifactID(artid.get());
+			}
+		}
+	}
+	handler.serializeBool("noGrowing", notGrowingTeam);
+	handler.serializeBool("neverFlees", neverFlees);
+	handler.serializeString("rewardMessage", message);
+}
+
+//CGMine
 void CGMine::onHeroVisit( const CGHeroInstance * h ) const
 {
 	int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
@@ -630,7 +689,7 @@ void CGMine::newTurn() const
 
 void CGMine::initObj()
 {
-	if(subID >= 7) //Abandoned Mine
+	if(isAbandoned())
 	{
 		//set guardians
 		int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199);
@@ -657,6 +716,11 @@ void CGMine::initObj()
 	producedQuantity = defaultResProduction();
 }
 
+bool CGMine::isAbandoned() const
+{
+	return (subID >= 7);
+}
+
 std::string CGMine::getObjectName() const
 {
 	return VLC->generaltexth->mines.at(subID).first;
@@ -714,7 +778,7 @@ void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &resu
 {
 	if(result.winner == 0) //attacker won
 	{
-		if(subID == 7)
+		if(isAbandoned())
 		{
 			showInfoDialog(hero->tempOwner, 85, 0);
 		}
@@ -728,6 +792,63 @@ void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) con
 		cb->startBattleI(hero, this);
 }
 
+void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	CCreatureSet::serializeJson(handler, "army");
+
+	if(isAbandoned())
+	{
+		auto possibleResources = handler.enterStruct("possibleResources");
+
+		JsonNode & node = handler.getCurrent();
+
+		if(handler.saving)
+		{
+			for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
+				if(tempOwner.getNum() & 1<<i)
+				{
+					JsonNode one(JsonNode::DATA_STRING);
+					one.String() = GameConstants::RESOURCE_NAMES[i];
+					node.Vector().push_back(one);
+				}
+		}
+		else
+		{
+			std::set<int> possibleResources;
+
+			if(node.Vector().size() == 0)
+			{
+				//assume all allowed
+				for(int i = (int)Res::WOOD; i < (int) Res::GOLD; i++)
+					possibleResources.insert(i);
+			}
+			else
+			{
+				auto names = node.convertTo<std::vector<std::string>>();
+
+				for(const std::string & s : names)
+				{
+					int raw_res = vstd::find_pos(GameConstants::RESOURCE_NAMES, s);
+					if(raw_res < 0)
+						logGlobal->errorStream() << "Invalid resource name: "+s;
+					else
+						possibleResources.insert(raw_res);
+				}
+
+				int tmp = 0;
+
+				for(int r : possibleResources)
+					tmp |=  (1<<r);
+				tempOwner = PlayerColor(tmp);
+			}
+		}
+	}
+	else
+	{
+		serializeJsonOwner(handler);
+	}
+}
+
 std::string CGResource::getHoverText(PlayerColor player) const
 {
 	return VLC->generaltexth->restypes[subID];
@@ -812,6 +933,13 @@ void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 		cb->startBattleI(hero, this);
 }
 
+void CGResource::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	CCreatureSet::serializeJson(handler, "guards");
+	handler.serializeNumeric("amount", amount);
+	handler.serializeString("guardMessage", message);
+}
+
 CGTeleport::CGTeleport() :
 	type(UNKNOWN), channel(TeleportChannelID())
 {
@@ -1298,6 +1426,21 @@ void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 		cb->startBattleI(hero, this);
 }
 
+void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	handler.serializeString("guardMessage", message);
+	CCreatureSet::serializeJson(handler, "guards");
+
+	if(handler.saving && ID == Obj::SPELL_SCROLL)
+	{
+		const Bonus * b = storedArtifact->getBonusLocalFirst(Selector::type(Bonus::SPELL));
+		SpellID spellId(b->subtype);
+
+		std::string spell = SpellID(b->subtype).toSpell()->identifier;
+		handler.serializeString("spell", spell);
+	}
+}
+
 void CGWitchHut::initObj()
 {
 	if (allowedAbilities.empty()) //this can happen for RMG. regular maps load abilities from map file
@@ -1355,6 +1498,33 @@ std::string CGWitchHut::getHoverText(const CGHeroInstance * hero) const
 	return hoverName;
 }
 
+void CGWitchHut::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	//TODO: unify allowed abilities with others - make them std::vector<bool>
+
+	std::vector<bool> temp;
+	temp.resize(GameConstants::SKILL_QUANTITY, false);
+
+	auto standard = VLC->heroh->getDefaultAllowedAbilities(); //todo: for WitchHut default is all except Leadership and Necromancy
+
+    if(handler.saving)
+	{
+		for(si32 i = 0; i < GameConstants::SKILL_QUANTITY; ++i)
+			if(vstd::contains(allowedAbilities, i))
+				temp[i] = true;
+	}
+
+	handler.serializeLIC("allowedSkills", &CHeroHandler::decodeSkill, &CHeroHandler::encodeSkill, standard, temp);
+
+	if(!handler.saving)
+	{
+		allowedAbilities.clear();
+		for (si32 i=0; i<temp.size(); i++)
+			if(temp[i])
+				allowedAbilities.push_back(i);
+	}
+}
+
 void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const
 {
 	int message;
@@ -1494,6 +1664,11 @@ std::string CGShrine::getHoverText(const CGHeroInstance * hero) const
 	return hoverName;
 }
 
+void CGShrine::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	handler.serializeId("spell", &CSpellHandler::decodeSpell, &CSpellHandler::encodeSpell, SpellID(SpellID::NONE), spell);
+}
+
 void CGSignBottle::initObj()
 {
 	//if no text is set than we pick random from the predefined ones
@@ -1520,11 +1695,10 @@ void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const
 		cb->removeObject(this);
 }
 
-//TODO: remove
-//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const
-//{
-//
-//}
+void CGSignBottle::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	handler.serializeString("text", message);
+}
 
 void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
 {
@@ -1599,6 +1773,59 @@ void CGScholar::initObj()
 	}
 }
 
+void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler)
+{
+	JsonNode& json = handler.getCurrent();
+	if(handler.saving)
+	{
+		switch(bonusType)
+		{
+		case PRIM_SKILL:
+			json["rewardPrimSkill"].String() = PrimarySkill::names[bonusID];
+			break;
+		case SECONDARY_SKILL:
+			json["rewardSkill"].String() = NSecondarySkill::names[bonusID];
+			break;
+		case SPELL:
+			json["rewardSpell"].String() = VLC->spellh->objects.at(bonusID)->identifier;
+			break;
+		case RANDOM:
+			break;
+		}
+	}
+	else
+	{
+		bonusType = RANDOM;
+		if(json["rewardPrimSkill"].String() != "")
+		{
+			auto raw = VLC->modh->identifiers.getIdentifier("core", "primSkill", json["rewardPrimSkill"].String());
+			if(raw)
+			{
+				bonusType = PRIM_SKILL;
+				bonusID = raw.get();
+			}
+		}
+		else if(json["rewardSkill"].String() != "")
+		{
+			auto raw = VLC->modh->identifiers.getIdentifier("core", "skill", json["rewardSkill"].String());
+			if(raw)
+			{
+				bonusType = SECONDARY_SKILL;
+				bonusID = raw.get();
+			}
+		}
+		else if(json["rewardSpell"].String() != "")
+		{
+			auto raw = VLC->modh->identifiers.getIdentifier("core", "spell", json["rewardSpell"].String());
+			if(raw)
+			{
+				bonusType = SPELL;
+				bonusID = raw.get();
+			}
+		}
+	}
+}
+
 void CGGarrison::onHeroVisit (const CGHeroInstance *h) const
 {
 	int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
@@ -1635,6 +1862,13 @@ void CGGarrison::battleFinished(const CGHeroInstance *hero, const BattleResult &
 		onHeroVisit(hero);
 }
 
+void CGGarrison::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	handler.serializeBool("removableUnits", removableUnits);
+	serializeJsonOwner(handler);
+	CCreatureSet::serializeJson(handler, "army");
+}
+
 void CGMagi::initObj()
 {
 	if (ID == Obj::EYE_OF_MAGI)
@@ -1772,6 +2006,11 @@ void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const
 	}
 }
 
+void CGShipyard::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	serializeJsonOwner(handler);
+}
+
 void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
 {
 	//if player has not bought map of this subtype yet and underground exist for stalagmite cartographer
@@ -1953,3 +2192,8 @@ void CGLighthouse::giveBonusTo( PlayerColor player ) const
 	gb.bonus.sid = id.getNum();
 	cb->sendAndApply(&gb);
 }
+
+void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler)
+{
+	serializeJsonOwner(handler);
+}

+ 25 - 5
lib/mapObjects/MiscObjects.h

@@ -14,6 +14,8 @@
  *
  */
 
+class CMap;
+
 class DLL_LINKAGE CPlayersVisited: public CGObjectInstance
 {
 public:
@@ -84,8 +86,9 @@ public:
 	}
 protected:
 	void setPropertyDer(ui8 what, ui32 val) override;
-private:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
+private:
 	void fight(const CGHeroInstance *h) const;
 	void flee( const CGHeroInstance * h ) const;
 	void fleeDecision(const CGHeroInstance *h, ui32 pursue) const;
@@ -96,7 +99,6 @@ private:
 
 };
 
-
 class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles
 {
 public:
@@ -110,6 +112,8 @@ public:
 		h & static_cast<CGObjectInstance&>(*this);
 		h & message;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGWitchHut : public CPlayersVisited
@@ -127,6 +131,8 @@ public:
 		h & static_cast<CPlayersVisited&>(*this);
 		h & allowedAbilities & ability;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGScholar : public CGObjectInstance
@@ -136,7 +142,6 @@ public:
 	EBonusType bonusType;
 	ui16 bonusID; //ID of skill/spell
 
-//	void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove
 	CGScholar() : bonusType(EBonusType::RANDOM){};
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void initObj() override;
@@ -145,6 +150,8 @@ public:
 		h & static_cast<CGObjectInstance&>(*this);
 		h & bonusType & bonusID;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGGarrison : public CArmedInstance
@@ -161,6 +168,8 @@ public:
 		h & static_cast<CArmedInstance&>(*this);
 		h & removableUnits;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGArtifact : public CArmedInstance
@@ -185,6 +194,8 @@ public:
 		h & static_cast<CArmedInstance&>(*this);
 		h & message & storedArtifact;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGResource : public CArmedInstance
@@ -207,6 +218,8 @@ public:
 		h & static_cast<CArmedInstance&>(*this);
 		h & amount & message;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGShrine : public CPlayersVisited
@@ -223,6 +236,8 @@ public:
 		h & static_cast<CPlayersVisited&>(*this);;
 		h & spell;
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGMine : public CArmedInstance
@@ -243,6 +258,7 @@ private:
 	std::string getObjectName() const override;
 	std::string getHoverText(PlayerColor player) const override;
 
+	bool isAbandoned() const;
 public:
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -250,6 +266,8 @@ public:
 		h & producedResource & producedQuantity;
 	}
 	ui32 defaultResProduction();
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 struct DLL_LINKAGE TeleportChannel
@@ -414,6 +432,8 @@ public:
 		h & static_cast<CGObjectInstance&>(*this);
 		h & static_cast<IShipyard&>(*this);
 	}
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };
 
 class DLL_LINKAGE CGMagi : public CGObjectInstance
@@ -430,8 +450,6 @@ public:
 	}
 };
 
-
-
 class DLL_LINKAGE CCartographer : public CPlayersVisited
 {
 ///behaviour varies depending on surface and  floor
@@ -482,4 +500,6 @@ public:
 		h & static_cast<CGObjectInstance&>(*this);
 	}
 	void giveBonusTo( PlayerColor player ) const;
+protected:
+	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 };

+ 97 - 8
lib/mapObjects/ObjectTemplate.cpp

@@ -183,7 +183,7 @@ void ObjectTemplate::readMap(CBinaryReader & reader)
 	}
 }
 
-void ObjectTemplate::readJson(const JsonNode &node)
+void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain)
 {
 	animationFile = node["animation"].String();
 
@@ -202,7 +202,7 @@ void ObjectTemplate::readJson(const JsonNode &node)
 	else
 		visitDir = 0x00;
 
-	if (!node["allowedTerrains"].isNull())
+	if(withTerrain && !node["allowedTerrains"].isNull())
 	{
 		for (auto & entry : node["allowedTerrains"].Vector())
 			allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String())));
@@ -211,11 +211,14 @@ void ObjectTemplate::readJson(const JsonNode &node)
 	{
 		for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++)
 			allowedTerrains.insert(ETerrainType(i));
+
+		allowedTerrains.erase(ETerrainType::ROCK);
 	}
 
-	if (allowedTerrains.empty())
+	if(withTerrain && allowedTerrains.empty())
 		logGlobal->warnStream() << "Loaded template without allowed terrains!";
 
+
 	auto charToTile = [&](const char & ch) -> ui8
 	{
 		switch (ch)
@@ -249,11 +252,97 @@ void ObjectTemplate::readJson(const JsonNode &node)
 			usedTiles[mask.size() - 1 - i][line.size() - 1 - j] = charToTile(line[j]);
 	}
 
-	const JsonNode zindex = node["zIndex"];
-	if (!zindex.isNull())
-		printPriority = node["zIndex"].Float();
-	else
-		printPriority = 0; //default value
+	printPriority = node["zIndex"].Float();
+}
+
+void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const
+{
+	node["animation"].String() = animationFile;
+
+	if(visitDir != 0x0 && isVisitable())
+	{
+		JsonVector & visitDirs = node["visitableFrom"].Vector();
+		visitDirs.resize(3);
+
+		visitDirs[0].String().resize(3);
+		visitDirs[1].String().resize(3);
+		visitDirs[2].String().resize(3);
+
+		visitDirs[0].String()[0] = (visitDir & 1)   ? '+' : '-';
+		visitDirs[0].String()[1] = (visitDir & 2)   ? '+' : '-';
+		visitDirs[0].String()[2] = (visitDir & 4)   ? '+' : '-';
+		visitDirs[1].String()[2] = (visitDir & 8)   ? '+' : '-';
+		visitDirs[2].String()[2] = (visitDir & 16)  ? '+' : '-';
+		visitDirs[2].String()[1] = (visitDir & 32)  ? '+' : '-';
+		visitDirs[2].String()[0] = (visitDir & 64)  ? '+' : '-';
+		visitDirs[1].String()[0] = (visitDir & 128) ? '+' : '-';
+
+		visitDirs[1].String()[1] = '-';
+	}
+
+	if(withTerrain)
+	{
+		//assumed that ROCK terrain not included
+		if(allowedTerrains.size() < (GameConstants::TERRAIN_TYPES - 1))
+		{
+			JsonVector & data = node["allowedTerrains"].Vector();
+
+			for(auto type : allowedTerrains)
+			{
+				JsonNode value(JsonNode::DATA_STRING);
+				value.String() = GameConstants::TERRAIN_NAMES[type.num];
+				data.push_back(value);
+			}
+		}
+	}
+
+	auto tileToChar = [&](const ui8 & tile) -> char
+	{
+		if(tile & VISIBLE)
+		{
+			if(tile & BLOCKED)
+			{
+				if(tile & VISITABLE)
+					return 'A';
+				else
+					return 'B';
+			}
+			else
+				return 'V';
+		}
+		else
+		{
+			if(tile & BLOCKED)
+			{
+				if(tile & VISITABLE)
+					return 'T';
+				else
+					return 'H';
+			}
+			else
+				return '0';
+		}
+	};
+
+	size_t height = getHeight();
+	size_t width  = getWidth();
+
+	JsonVector & mask = node["mask"].Vector();
+
+
+	for(size_t i=0; i < height; i++)
+	{
+		JsonNode lineNode(JsonNode::DATA_STRING);
+
+		std::string & line = lineNode.String();
+		line.resize(width);
+		for(size_t j=0; j < width; j++)
+			line[j] = tileToChar(usedTiles[height - 1 - i][width - 1 - j]);
+		mask.push_back(lineNode);
+	}
+
+	if(printPriority != 0)
+		node["zIndex"].Float() = printPriority;
 }
 
 ui32 ObjectTemplate::getWidth() const

+ 2 - 1
lib/mapObjects/ObjectTemplate.h

@@ -73,7 +73,8 @@ public:
 	void readTxt(CLegacyConfigParser & parser);
 	void readMsk();
 	void readMap(CBinaryReader & reader);
-	void readJson(const JsonNode & node);
+	void readJson(const JsonNode & node, const bool withTerrain = true);
+	void writeJson(JsonNode & node, const bool withTerrain = true) const;
 
 	bool operator==(const ObjectTemplate& ot) const { return (id == ot.id && subid == ot.subid); }
 

+ 59 - 7
lib/mapping/CMap.cpp

@@ -19,7 +19,7 @@ SHeroName::SHeroName() : heroId(-1)
 
 PlayerInfo::PlayerInfo(): canHumanPlay(false), canComputerPlay(false),
 	aiTactic(EAiTactic::RANDOM), isFactionRandom(false), mainCustomHeroPortrait(-1), mainCustomHeroId(-1), hasMainTown(false),
-	generateHeroAtMainTown(false), team(255), hasRandomHero(false), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1)
+	generateHeroAtMainTown(false), team(TeamID::NO_TEAM), hasRandomHero(false), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1)
 {
 	allowedFactions = VLC->townh->getAllowedFactions();
 }
@@ -63,6 +63,7 @@ EventCondition::EventCondition(EWinLoseType condition):
 	object(nullptr),
 	value(-1),
 	objectType(-1),
+	objectSubtype(-1),
 	position(-1, -1, -1),
 	condition(condition)
 {
@@ -72,6 +73,7 @@ EventCondition::EventCondition(EWinLoseType condition, si32 value, si32 objectTy
 	object(nullptr),
 	value(value),
 	objectType(objectType),
+	objectSubtype(-1),
 	position(position),
 	condition(condition)
 {}
@@ -141,11 +143,6 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
 	return visitableObjects.back();
 }
 
-bool TerrainTile::isCoastal() const
-{
-	return extTileFlags & 64;
-}
-
 EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const
 {
 	if(terType == ETerrainType::WATER || terType == ETerrainType::ROCK)
@@ -322,6 +319,35 @@ CGHeroInstance * CMap::getHero(int heroID)
 	return nullptr;
 }
 
+bool CMap::isCoastalTile(const int3 & pos) const
+{
+	//todo: refactoring: extract neighbor tile iterator and use it in GameState
+	static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0),
+					int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
+
+	if(!isInTheMap(pos))
+	{
+		logGlobal->errorStream() << "Coastal check outside of map :"<<pos;
+		return false;
+	}
+
+	if(isWaterTile(pos))
+		return false;
+
+	for (auto & dir : dirs)
+	{
+		const int3 hlp = pos + dir;
+
+		if(!isInTheMap(hlp))
+			continue;
+		const TerrainTile &hlpt = getTile(hlp);
+		if(hlpt.isWater())
+			return true;
+	}
+
+	return false;
+}
+
 bool CMap::isInTheMap(const int3 & pos) const
 {
 	if(pos.x < 0 || pos.y < 0 || pos.z < 0 || pos.x >= width || pos.y >= height
@@ -349,7 +375,7 @@ const TerrainTile & CMap::getTile(const int3 & tile) const
 
 bool CMap::isWaterTile(const int3 &pos) const
 {
-	return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER;
+	return isInTheMap(pos) && getTile(pos).isWater();
 }
 
 bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const
@@ -534,6 +560,32 @@ void CMap::addQuest(CGObjectInstance * quest)
 	quests.push_back(q->quest);
 }
 
+void CMap::addNewObject(CGObjectInstance * obj)
+{
+    if(obj->id != ObjectInstanceID(objects.size()))
+		throw std::runtime_error("Invalid object instance id");
+
+	if(obj->instanceName == "")
+		throw std::runtime_error("Object instance name missing");
+
+	auto it = instanceNames.find(obj->instanceName);
+	if(it != instanceNames.end())
+		throw std::runtime_error("Object instance name duplicated:"+obj->instanceName);
+
+    objects.push_back(obj);
+    instanceNames[obj->instanceName] = obj;
+    addBlockVisTiles(obj);
+
+	if(obj->ID == Obj::TOWN)
+	{
+		towns.push_back(static_cast<CGTownInstance *>(obj));
+	}
+	if(obj->ID == Obj::HERO)
+	{
+		heroesOnMap.push_back(static_cast<CGHeroInstance*>(obj));
+	}
+}
+
 void CMap::initTerrain()
 {
 	int level = twoLevel ? 2 : 1;

+ 41 - 8
lib/mapping/CMap.h

@@ -76,7 +76,7 @@ struct DLL_LINKAGE PlayerInfo
 	bool hasMainTown; /// The default value is false.
 	bool generateHeroAtMainTown; /// The default value is false.
 	int3 posOfMainTown;
-	TeamID team; /// The default value is 255 representing that the player belongs to no team.
+	TeamID team; /// The default value NO_TEAM
 	bool hasRandomHero; /// Player has a random hero
 
 	bool generateHero; /// Unused.
@@ -98,6 +98,7 @@ struct DLL_LINKAGE PlayerInfo
 struct DLL_LINKAGE EventCondition
 {
 	enum EWinLoseType {
+		//internal use, deprecated
 		HAVE_ARTIFACT,     // type - required artifact
 		HAVE_CREATURES,    // type - creatures to collect, value - amount to collect
 		HAVE_RESOURCES,    // type - resource ID, value - amount to collect
@@ -105,26 +106,44 @@ struct DLL_LINKAGE EventCondition
 		CONTROL,           // position - position of object, optional, type - type of object
 		DESTROY,           // position - position of object, optional, type - type of object
 		TRANSPORT,         // position - where artifact should be transported, type - type of artifact
+
+		//map format version pre 1.0
 		DAYS_PASSED,       // value - number of days from start of the game
 		IS_HUMAN,          // value - 0 = player is AI, 1 = player is human
 		DAYS_WITHOUT_TOWN, // value - how long player can live without town, 0=instakill
 		STANDARD_WIN,      // normal defeat all enemies condition
-		CONST_VALUE        // condition that always evaluates to "value" (0 = false, 1 = true)
+		CONST_VALUE,        // condition that always evaluates to "value" (0 = false, 1 = true)
+
+		//map format version 1.0+
+		HAVE_0,
+		HAVE_BUILDING_0,
+		DESTROY_0
 	};
 
 	EventCondition(EWinLoseType condition = STANDARD_WIN);
 	EventCondition(EWinLoseType condition, si32 value, si32 objectType, int3 position = int3(-1, -1, -1));
 
-	const CGObjectInstance * object; // object that was at specified position on start
+	const CGObjectInstance * object; // object that was at specified position or with instance name on start
 	si32 value;
 	si32 objectType;
+	si32 objectSubtype;
+	std::string objectInstanceName;
 	int3 position;
 	EWinLoseType condition;
 
 	template <typename Handler>
 	void serialize(Handler & h, const int version)
 	{
-		h & object & value & objectType & position & condition;
+		h & object;
+		h & value;
+		h & objectType;
+		h & position;
+		h & condition;
+		if(version > 759)
+		{
+			h & objectSubtype;
+			h & objectInstanceName;
+		}
 	}
 };
 
@@ -171,7 +190,10 @@ struct DLL_LINKAGE TriggeredEvent
 	template <typename Handler>
 	void serialize(Handler & h, const int version)
 	{
-		h & identifier & trigger & description & onFulfill & effect;
+		h & identifier;
+		h & trigger;
+		h & description;
+		h & onFulfill & effect;
 	}
 };
 
@@ -207,7 +229,7 @@ struct DLL_LINKAGE DisposedHero
 
 namespace EMapFormat
 {
-enum EMapFormat
+enum EMapFormat: ui8
 {
 	INVALID = 0,
 	//    HEX     DEC
@@ -215,7 +237,8 @@ enum EMapFormat
 	AB  = 0x15, // 21
 	SOD = 0x1c, // 28
 // HOTA = 0x1e ... 0x20 // 28 ... 30
-	WOG = 0x33  // 51
+	WOG = 0x33,  // 51
+	VCMI = 0xF0
 };
 }
 
@@ -251,7 +274,7 @@ public:
 	std::vector<PlayerInfo> players; /// The default size of the vector is PlayerColor::PLAYER_LIMIT.
 	ui8 howManyTeams;
 	std::vector<bool> allowedHeroes;
-	std::vector<ui16> placeholdedHeroes;
+
 	bool areAnyPlayers; /// Unused. True if there are any playable players on the map.
 
 	/// "main quests" of the map that describe victory and loss conditions
@@ -277,8 +300,10 @@ public:
 	CMapEditManager * getEditManager();
 	TerrainTile & getTile(const int3 & tile);
 	const TerrainTile & getTile(const int3 & tile) const;
+	bool isCoastalTile(const int3 & pos) const;
 	bool isInTheMap(const int3 & pos) const;
 	bool isWaterTile(const int3 & pos) const;
+
 	bool checkForVisitableDir( const int3 & src, const TerrainTile *pom, const int3 & dst ) const;
 	int3 guardingCreaturePosition (int3 pos) const;
 
@@ -289,6 +314,7 @@ public:
 	void addNewArtifactInstance(CArtifactInstance * art);
 	void eraseArtifactInstance(CArtifactInstance * art);
 	void addQuest(CGObjectInstance * quest);
+	void addNewObject(CGObjectInstance * obj);
 
 	/// Gets object of specified type on requested position
 	const CGObjectInstance * getObjectiveObjectFrom(int3 pos, Obj::EObj type);
@@ -326,6 +352,8 @@ public:
 
 	int3 ***guardingCreaturePositions;
 
+	std::map<std::string, ConstTransitivePtr<CGObjectInstance> > instanceNames;
+
 private:
 	/// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground
 	TerrainTile*** terrain;
@@ -393,5 +421,10 @@ public:
 		h & CGObelisk::obeliskCount & CGObelisk::visited;
 		h & CGTownInstance::merchantArtifacts;
 		h & CGTownInstance::universitySkills;
+
+		if(formatVersion >= 759)
+		{
+			h & instanceNames;
+		}
 	}
 };

+ 0 - 1
lib/mapping/CMapDefines.h

@@ -70,7 +70,6 @@ struct DLL_LINKAGE TerrainTile
 	Obj topVisitableId(bool excludeTop = false) const;
 	CGObjectInstance * topVisitableObj(bool excludeTop = false) const;
 	bool isWater() const;
-	bool isCoastal() const;
 	EDiggingStatus getDiggingStatus(const bool excludeTop = true) const;
 	bool hasFavorableWinds() const;
 

+ 6 - 11
lib/mapping/CMapEditManager.cpp

@@ -478,7 +478,6 @@ void CDrawTerrainOperation::execute()
 
 	updateTerrainTypes();
 	updateTerrainViews();
-	//TODO add coastal bit to extTileFlags appropriately
 }
 
 void CDrawTerrainOperation::undo()
@@ -1044,16 +1043,12 @@ void CInsertObjectOperation::execute()
 {
 	obj->pos = pos;
 	obj->id = ObjectInstanceID(map->objects.size());
-	map->objects.push_back(obj);
-	if(obj->ID == Obj::TOWN)
-	{
-		map->towns.push_back(static_cast<CGTownInstance *>(obj));
-	}
-	if(obj->ID == Obj::HERO)
-	{
-		map->heroesOnMap.push_back(static_cast<CGHeroInstance*>(obj));
-	}
-	map->addBlockVisTiles(obj);
+
+	boost::format fmt("%s_%d");
+	fmt % obj->typeName % obj->id.getNum();
+	obj->instanceName = fmt.str();
+
+	map->addNewObject(obj);
 }
 
 void CInsertObjectOperation::undo()

+ 26 - 15
lib/mapping/CMapService.cpp

@@ -5,6 +5,7 @@
 #include "../filesystem/CBinaryReader.h"
 #include "../filesystem/CCompressedStream.h"
 #include "../filesystem/CMemoryStream.h"
+
 #include "CMap.h"
 
 #include "MapFormatH3M.h"
@@ -68,21 +69,31 @@ std::unique_ptr<IMapLoader> CMapService::getMapLoader(std::unique_ptr<CInputStre
 	ui32 header = reader.readUInt32();
 	reader.getStream()->seek(0);
 
-	// Check which map format is used
-	// gzip header is 3 bytes only in size
-	switch(header & 0xffffff)
+	//check for ZIP magic. Zip files are VCMI maps
+	switch(header)
 	{
-		// gzip header magic number, reversed for LE
-		case 0x00088B1F:
-			stream = std::unique_ptr<CInputStream>(new CCompressedStream(std::move(stream), true));
-			return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(stream.get()));
-		case EMapFormat::WOG :
-		case EMapFormat::AB  :
-		case EMapFormat::ROE :
-		case EMapFormat::SOD :
-			return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(stream.get()));
-		default :
-			throw std::runtime_error("Unknown map format");
+	case 0x06054b50:
+	case 0x04034b50:
+	case 0x02014b50:
+		return std::unique_ptr<IMapLoader>(new CMapLoaderJson(stream.get()));
+		break;
+	default:
+		// Check which map format is used
+		// gzip header is 3 bytes only in size
+		switch(header & 0xffffff)
+		{
+			// gzip header magic number, reversed for LE
+			case 0x00088B1F:
+				stream = std::unique_ptr<CInputStream>(new CCompressedStream(std::move(stream), true));
+				return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(stream.get()));
+			case EMapFormat::WOG :
+			case EMapFormat::AB  :
+			case EMapFormat::ROE :
+			case EMapFormat::SOD :
+				return std::unique_ptr<IMapLoader>(new CMapLoaderH3M(stream.get()));
+			default :
+				throw std::runtime_error("Unknown map format");
+		}
 	}
 }
 
@@ -103,5 +114,5 @@ std::unique_ptr<IMapPatcher> CMapService::getMapPatcher(std::string scenarioName
 
 	boost::to_lower(scenarioName);
 	logGlobal->debugStream() << "Request to patch map " << scenarioName;
-	return std::unique_ptr<IMapPatcher>(new CMapLoaderJson(node[scenarioName]));
+	return std::unique_ptr<IMapPatcher>(new CMapPatcher(node[scenarioName]));
 }

+ 14 - 1
lib/mapping/CMapService.h

@@ -126,7 +126,7 @@ public:
 	virtual std::unique_ptr<CMapHeader> loadMapHeader() = 0;
 };
 
-class DLL_LINKAGE IMapPatcher : public IMapLoader
+class DLL_LINKAGE IMapPatcher
 {
 public:
 	/**
@@ -135,3 +135,16 @@ public:
 	 */
 	virtual void patchMapHeader(std::unique_ptr<CMapHeader> & header) = 0;
 };
+
+/**
+ * Interface for saving a map.
+ */
+class DLL_LINKAGE IMapSaver
+{
+public:
+	/**
+	 * Saves the VCMI/H3 map file.
+	 *
+	 */
+	 virtual void saveMap(const std::unique_ptr<CMap> & map) = 0;
+};

+ 21 - 55
lib/mapping/MapFormatH3M.cpp

@@ -119,11 +119,6 @@ void CMapLoaderH3M::init()
 	readEvents();
 	times.push_back(MapLoadingTime("events", sw.getDiff()));
 
-	// Calculate blocked / visitable positions
-	for(auto & elem : map->objects)
-	{
-		map->addBlockVisTiles(elem);
-	}
 	times.push_back(MapLoadingTime("blocked/visitable tiles", sw.getDiff()));
 
 	// Print profiling times
@@ -615,10 +610,15 @@ void CMapLoaderH3M::readAllowedHeroes()
 	if(mapHeader->version > EMapFormat::ROE)
 	{
 		int placeholdersQty = reader.readUInt32();
-		for(int p = 0; p < placeholdersQty; ++p)
-		{
-			mapHeader->placeholdedHeroes.push_back(reader.readUInt8());
-		}
+
+		reader.skip(placeholdersQty * 1);
+
+//		std::vector<ui16> placeholdedHeroes;
+//
+//		for(int p = 0; p < placeholdersQty; ++p)
+//		{
+//			placeholdedHeroes.push_back(reader.readUInt8());
+//		}
 	}
 }
 
@@ -828,7 +828,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
 			{
 				// catapult by default
 				assert(!hero->getArt(ArtifactPosition::MACH4));
-				hero->putArtifact(ArtifactPosition::MACH4, createArtifact(ArtifactID::CATAPULT));
+				hero->putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createArtifact(map, ArtifactID::CATAPULT));
 			}
 		}
 
@@ -885,7 +885,7 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
 		}
 
 		// this is needed, because some H3M maps (last scenario of ROE map) contain invalid data like misplaced artifacts
-		auto artifact = createArtifact(aid);
+		auto artifact =  CArtifactInstance::createArtifact(map, aid);
 		auto artifactPos = ArtifactPosition(slot);
 		if (artifact->canBePutAt(ArtifactLocation(hero, artifactPos)))
 		{
@@ -900,40 +900,6 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
 	return isArt;
 }
 
-CArtifactInstance * CMapLoaderH3M::createArtifact(int aid, int spellID /*= -1*/)
-{
-	CArtifactInstance * a = nullptr;
-	if(aid >= 0)
-	{
-		if(spellID < 0)
-		{
-			a = CArtifactInstance::createNewArtifactInstance(aid);
-		}
-		else
-		{
-			a = CArtifactInstance::createScroll(SpellID(spellID).toSpell());
-		}
-	}
-	else //FIXME: create combined artifact instance for random combined artifacts, just in case
-	{
-		a = new CArtifactInstance(); //random, empty
-	}
-
-	map->addNewArtifactInstance(a);
-
-	//TODO make it nicer
-	if(a->artType && (!!a->artType->constituents))
-	{
-		CCombinedArtifactInstance * comb = dynamic_cast<CCombinedArtifactInstance *>(a);
-		for(CCombinedArtifactInstance::ConstituentInfo & ci : comb->constituentsInfo)
-		{
-			map->addNewArtifactInstance(ci.art);
-		}
-	}
-
-	return a;
-}
-
 void CMapLoaderH3M::readTerrain()
 {
 	map->initTerrain();
@@ -1236,7 +1202,7 @@ void CMapLoaderH3M::readObjects()
 					artID = objTempl.subid;
 				}
 
-				art->storedArtifact = createArtifact(artID, spellID);
+				art->storedArtifact = CArtifactInstance::createArtifact(map, artID, spellID);
 				break;
 			}
 		case Obj::RANDOM_RESOURCE:
@@ -1488,16 +1454,16 @@ void CMapLoaderH3M::readObjects()
 		}
 		nobj->appearance = objTempl;
 		assert(idToBeGiven == ObjectInstanceID(map->objects.size()));
-		map->objects.push_back(nobj);
-		if(nobj->ID == Obj::TOWN)
-		{
-			map->towns.push_back(static_cast<CGTownInstance *>(nobj));
-		}
-		if(nobj->ID == Obj::HERO)
+
 		{
-			logGlobal->debugStream() << "Hero: " << VLC->heroh->heroes[nobj->subID]->name << " at " << objPos;
-			map->heroesOnMap.push_back(static_cast<CGHeroInstance*>(nobj));
+			//TODO: define valid typeName and subtypeName fro H3M maps
+			//boost::format fmt("%s_%d");
+			//fmt % nobj->typeName % nobj->id.getNum();
+			boost::format fmt("obj_%d");
+			fmt % nobj->id.getNum();
+			nobj->instanceName = fmt.str();
 		}
+		map->addNewObject(nobj);
 	}
 
 	std::sort(map->heroesOnMap.begin(), map->heroesOnMap.end(), [](const ConstTransitivePtr<CGHeroInstance> & a, const ConstTransitivePtr<CGHeroInstance> & b)
@@ -1998,7 +1964,7 @@ CGTownInstance * CMapLoaderH3M::readTown(int castleID)
 		ui8 c = reader.readUInt8();
 		for(int yy = 0; yy < 8; ++yy)
 		{
-			int spellid = i * 8 + yy; 
+			int spellid = i * 8 + yy;
 			if(spellid < GameConstants::SPELLS_QUANTITY)
 			{
 				if(c != (c | static_cast<ui8>(std::pow(2., yy))) && map->allowedSpell[spellid]) //add random spell only if it's allowed on entire map

+ 0 - 9
lib/mapping/MapFormatH3M.h

@@ -124,15 +124,6 @@ private:
 	 */
 	bool loadArtifactToSlot(CGHeroInstance * hero, int slot);
 
-	/**
-	 * Creates an artifact instance.
-	 *
-	 * @param aid the id of the artifact
-	 * @param spellID optional. the id of a spell if a spell scroll object should be created
-	 * @return the created artifact instance
-	 */
-	CArtifactInstance * createArtifact(int aid, int spellID = -1);
-
 	/**
 	 * Read rumors.
 	 */

+ 937 - 95
lib/mapping/MapFormatJson.cpp

@@ -11,161 +11,1003 @@
 #include "StdInc.h"
 #include "MapFormatJson.h"
 
+#include "../filesystem/CInputStream.h"
+#include "../filesystem/COutputStream.h"
 #include "CMap.h"
 #include "../CModHandler.h"
+#include "../CHeroHandler.h"
+#include "../CTownHandler.h"
 #include "../VCMI_Lib.h"
+#include "../mapObjects/ObjectTemplate.h"
+#include "../mapObjects/CObjectHandler.h"
+#include "../mapObjects/CObjectClassesHandler.h"
+#include "../mapObjects/CGHeroInstance.h"
+#include "../mapObjects/CGTownInstance.h"
+#include "../spells/CSpellHandler.h"
+#include "../StringConstants.h"
+#include "../serializer/JsonDeserializer.h"
+#include "../serializer/JsonSerializer.h"
 
-static const std::string conditionNames[] = {
-"haveArtifact", "haveCreatures",   "haveResources",   "haveBuilding",
-"control",      "destroy",         "transport",       "daysPassed",
-"isHuman",      "daysWithoutTown", "standardWin",     "constValue"
-};
+namespace HeaderDetail
+{
+	static const ui8 difficultyDefault = 1;//normal
 
-static const std::string typeNames[] = { "victory", "defeat" };
+	static const std::vector<std::string> difficultyMap =
+	{
+		"EASY",
+		"NORMAL",
+		"HARD",
+		"EXPERT",
+		"IMPOSSIBLE"
+	};
+}
 
-CMapLoaderJson::CMapLoaderJson(JsonNode stream):
-	input(stream)
+namespace TriggeredEventsDetail
 {
+	static const std::array<std::string, 15> conditionNames =
+	{
+		"haveArtifact", "haveCreatures",   "haveResources",   "haveBuilding",
+		"control",      "destroy",         "transport",       "daysPassed",
+		"isHuman",      "daysWithoutTown", "standardWin",     "constValue",
+
+		"have_0", "haveBuilding_0", "destroy_0"
+	};
+
+
+	static const std::array<std::string, 2> typeNames = { "victory", "defeat" };
+
+	static EventCondition JsonToCondition(const JsonNode & node)
+	{
+		//todo: support of new condition format
+		EventCondition event;
+
+		const auto & conditionName = node.Vector()[0].String();
+
+		auto pos = vstd::find_pos(conditionNames, conditionName);
+
+		event.condition = EventCondition::EWinLoseType(pos);
+		if (node.Vector().size() > 1)
+		{
+			const JsonNode & data = node.Vector()[1];
+			if (data["type"].getType() == JsonNode::DATA_STRING)
+			{
+				auto identifier = VLC->modh->identifiers.getIdentifier(data["type"]);
+				if(identifier)
+					event.objectType = identifier.get();
+				else
+					throw std::runtime_error("Identifier resolution failed in event condition");
+			}
+
+			if (data["type"].getType() == JsonNode::DATA_FLOAT)
+				event.objectType = data["type"].Float();
+
+			if (!data["value"].isNull())
+				event.value = data["value"].Float();
+
+			if (!data["position"].isNull())
+			{
+				auto & position = data["position"].Vector();
+				event.position.x = position.at(0).Float();
+				event.position.y = position.at(1).Float();
+				event.position.z = position.at(2).Float();
+			}
+
+//			if(!data["subtype"].isNull())
+//			{
+//				//todo
+//			}
+//			event.objectInstanceName = data["object"].String();
+		}
+		return event;
+	}
+
+	static JsonNode ConditionToJson(const EventCondition& event)
+	{
+		JsonNode json;
+
+		JsonVector& asVector = json.Vector();
+
+		JsonNode condition;
+		condition.String() = conditionNames.at(event.condition);
+		asVector.push_back(condition);
 
+		JsonNode data;
+
+		//todo: save identifier
+
+		if(event.objectType != -1)
+			data["type"].Float() = event.objectType;
+
+		if(event.value != -1)
+			data["value"].Float() = event.value;
+
+		if(event.position != int3(-1,-1,-1))
+		{
+			auto & position = data["position"].Vector();
+			position.resize(3);
+			position[0].Float() = event.position.x;
+			position[1].Float() = event.position.y;
+			position[2].Float() = event.position.z;
+		}
+
+		if(!data.isNull())
+			asVector.push_back(data);
+
+		return std::move(json);
+	}
+}//namespace TriggeredEventsDetail
+
+namespace TerrainDetail
+{
+	static const std::array<std::string, 10> terrainCodes =
+	{
+		"dt", "sa", "gr", "sn", "sw", "rg", "sb", "lv", "wt", "rc"
+	};
+	static const std::array<std::string, 4> roadCodes =
+	{
+		"", "pd", "pg", "pc"
+	};
+
+	static const std::array<std::string, 5> riverCodes =
+	{
+		"", "rw", "ri", "rm", "rl"
+	};
+
+	static const std::array<char, 10> flipCodes =
+	{
+		'_', '-', '|', '+'
+	};
 }
 
-std::unique_ptr<CMap> CMapLoaderJson::loadMap()
+///CMapFormatJson
+const int CMapFormatJson::VERSION_MAJOR = 1;
+const int CMapFormatJson::VERSION_MINOR = 0;
+
+const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json";
+const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json";
+
+void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std::set<TFaction> & value)
 {
-	map = new CMap();
-	mapHeader.reset(map);
-	readMap();
-	mapHeader.reset();
-	return std::unique_ptr<CMap>(map);
+	//TODO: unify allowed factions with others - make them std::vector<bool>
+
+	std::vector<bool> temp;
+	temp.resize(VLC->townh->factions.size(), false);
+	auto standard = VLC->townh->getDefaultAllowed();
+
+    if(handler.saving)
+	{
+		for(auto faction : VLC->townh->factions)
+			if(faction->town && vstd::contains(value, faction->index))
+				temp[std::size_t(faction->index)] = true;
+	}
+
+	handler.serializeLIC("allowedFactions", &CTownHandler::decodeFaction, &CTownHandler::encodeFaction, standard, temp);
+
+	if(!handler.saving)
+	{
+		value.clear();
+		for (std::size_t i=0; i<temp.size(); i++)
+			if(temp[i])
+				value.insert(i);
+	}
 }
 
-std::unique_ptr<CMapHeader> CMapLoaderJson::loadMapHeader()
+void CMapFormatJson::serializeHeader(JsonSerializeFormat & handler)
 {
-	mapHeader.reset(new CMapHeader);
-	readHeader();
-	return std::move(mapHeader);
+	handler.serializeString("name", mapHeader->name);
+	handler.serializeString("description", mapHeader->description);
+	handler.serializeNumeric("heroLevelLimit", mapHeader->levelLimit);
+
+	//todo: support arbitrary percentage
+	handler.serializeNumericEnum("difficulty", HeaderDetail::difficultyMap, HeaderDetail::difficultyDefault, mapHeader->difficulty);
+
+	serializePlayerInfo(handler);
+
+	handler.serializeLIC("allowedHeroes", &CHeroHandler::decodeHero, &CHeroHandler::encodeHero, VLC->heroh->getDefaultAllowed(), mapHeader->allowedHeroes);
 }
 
-/*
-	//This code can be used to write map header to console or file in its Json representation
+void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler)
+{
+	auto playersData = handler.enterStruct("players");
 
-	JsonNode out;
-	JsonNode data;
-	data["victoryString"].String() = mapHeader->victoryMessage;
-	data["defeatString"].String() = mapHeader->defeatMessage;
+	for(int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++)
+	{
+		PlayerInfo & info = mapHeader->players[player];
+
+		if(handler.saving)
+		{
+			if(!info.canAnyonePlay())
+				continue;
+		}
+
+		auto playerData = playersData.enterStruct(GameConstants::PLAYER_COLOR_NAMES[player]);
 
-	data["victoryIconIndex"].Float() = mapHeader->victoryIconIndex;
-	data["defeatIconIndex"].Float() = mapHeader->defeatIconIndex;
+		if(!handler.saving)
+		{
+			if(playerData.get().isNull())
+			{
+				info.canComputerPlay = false;
+				info.canHumanPlay = false;
+				continue;
+			}
+			info.canComputerPlay = true;
+		}
+
+		serializeAllowedFactions(handler, info.allowedFactions);
 
-	for (const TriggeredEvent & entry : mapHeader->triggeredEvents)
+		handler.serializeEnum("canPlay", "PlayerOrAI", "AIOnly", info.canHumanPlay);
+
+		//saving whole structure only if position is valid
+		if(!handler.saving || info.posOfMainTown.valid())
+		{
+			auto mainTown = handler.enterStruct("mainTown");
+			handler.serializeBool("generateHero", info.generateHeroAtMainTown);
+			handler.serializeNumeric("x", info.posOfMainTown.x);
+			handler.serializeNumeric("y", info.posOfMainTown.y);
+			handler.serializeNumeric("l", info.posOfMainTown.z);
+		}
+		if(!handler.saving)
+		{
+			info.hasMainTown = info.posOfMainTown.valid();
+		}
+
+		//mainHero
+
+		//mainHeroPortrait
+
+		//mainCustomHeroName
+
+		//heroes
+		{
+			//auto heroes = playerData.enterStruct("heroes");
+
+		}
+
+		if(!handler.saving)
+		{
+			//isFactionRandom indicates that player may select faction, depends on towns & heroes
+			// true if main town is random and generateHeroAtMainTown==true
+			// or main hero is random
+			//TODO: recheck mechanics
+			info.isFactionRandom = info.allowedFactions.size() > 1;
+		}
+	}
+}
+
+void CMapFormatJson::readTeams(JsonDeserializer & handler)
+{
+	auto teams = handler.enterStruct("teams");
+	const JsonNode & src = teams.get();
+
+    if(src.getType() != JsonNode::DATA_VECTOR)
 	{
-		JsonNode event;
-		event["message"].String() = entry.onFulfill;
-		event["effect"]["messageToSend"].String() = entry.effect.toOtherMessage;
-		event["effect"]["type"].String() = typeNames[entry.effect.type];
-		event["condition"] = entry.trigger.toJson(eventToJson);
-		data["triggeredEvents"][entry.identifier] = event;
+		// No alliances
+		if(src.getType() != JsonNode::DATA_NULL)
+			logGlobal->errorStream() << "Invalid teams field type";
+
+		mapHeader->howManyTeams = 0;
+		for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
+		{
+			if(mapHeader->players[i].canComputerPlay || mapHeader->players[i].canHumanPlay)
+			{
+				mapHeader->players[i].team = TeamID(mapHeader->howManyTeams++);
+			}
+		}
 	}
+	else
+	{
+		const JsonVector & srcVector = src.Vector();
+		mapHeader->howManyTeams = srcVector.size();
 
-	out[mapHeader->name] = data;
-	logGlobal->errorStream() << out;
+		for(int team = 0; team < mapHeader->howManyTeams; team++)
+		{
+			for(const JsonNode & playerData : srcVector[team].Vector())
+			{
+				PlayerColor player = PlayerColor(vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, playerData.String()));
+				if(player.isValidPlayer())
+				{
+					if(mapHeader->players[player.getNum()].canAnyonePlay())
+					{
+						mapHeader->players[player.getNum()].team = TeamID(team);
+					}
+				}
+			}
+		}
 
-JsonNode eventToJson(const EventCondition & cond)
+		for(PlayerInfo & player : mapHeader->players)
+		{
+			if(player.canAnyonePlay() && player.team == TeamID::NO_TEAM)
+				player.team = TeamID(mapHeader->howManyTeams++);
+		}
+
+	}
+}
+
+
+void CMapFormatJson::writeTeams(JsonSerializer & handler)
 {
-	JsonNode ret;
-	ret.Vector().resize(2);
-	ret.Vector()[0].String() = conditionNames[size_t(cond.condition)];
-	JsonNode & data = ret.Vector()[1];
-	data["type"].Float() = cond.objectType;
-	data["value"].Float() = cond.value;
-	data["position"].Vector().resize(3);
-	data["position"].Vector()[0].Float() = cond.position.x;
-	data["position"].Vector()[1].Float() = cond.position.y;
-	data["position"].Vector()[2].Float() = cond.position.z;
+	auto teams = handler.enterStruct("teams");
+	JsonNode & dest = teams.get();
+	std::vector<std::set<PlayerColor>> teamsData;
+
+	teamsData.resize(mapHeader->howManyTeams);
+
+	//get raw data
+	for(int idx = 0; idx < mapHeader->players.size(); idx++)
+	{
+		const PlayerInfo & player = mapHeader->players.at(idx);
+		int team = player.team.getNum();
+		if(vstd::iswithin(team, 0, mapHeader->howManyTeams-1) && player.canAnyonePlay())
+			teamsData.at(team).insert(PlayerColor(idx));
+	}
+
+	//remove single-member teams
+	vstd::erase_if(teamsData, [](std::set<PlayerColor> & elem) -> bool
+	{
+		return elem.size() <= 1;
+	});
+
+	//construct output
+	dest.setType(JsonNode::DATA_VECTOR);
 
-	return ret;
+	for(const std::set<PlayerColor> & teamData : teamsData)
+	{
+		JsonNode team(JsonNode::DATA_VECTOR);
+		for(const PlayerColor & player : teamData)
+		{
+			JsonNode member(JsonNode::DATA_STRING);
+			member.String() = GameConstants::PLAYER_COLOR_NAMES[player.getNum()];
+			team.Vector().push_back(std::move(member));
+		}
+		dest.Vector().push_back(std::move(team));
+	}
 }
-*/
-void CMapLoaderJson::patchMapHeader(std::unique_ptr<CMapHeader> & header)
+
+void CMapFormatJson::serializeTriggeredEvents(JsonSerializeFormat & handler)
 {
-	header.swap(mapHeader);
+	handler.serializeString("victoryString", mapHeader->victoryMessage);
+	handler.serializeNumeric("victoryIconIndex", mapHeader->victoryIconIndex);
+
+	handler.serializeString("defeatString", mapHeader->defeatMessage);
+	handler.serializeNumeric("defeatIconIndex", mapHeader->defeatIconIndex);
+}
+
+
+void CMapFormatJson::readTriggeredEvents(JsonDeserializer & handler)
+{
+	const JsonNode & input = handler.getCurrent();
+
+	serializeTriggeredEvents(handler);
+
+	mapHeader->triggeredEvents.clear();
+
+	for (auto & entry : input["triggeredEvents"].Struct())
+	{
+		TriggeredEvent event;
+		event.identifier = entry.first;
+		readTriggeredEvent(event, entry.second);
+		mapHeader->triggeredEvents.push_back(event);
+	}
+}
+
+void CMapFormatJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source)
+{
+	using namespace TriggeredEventsDetail;
+
+	event.onFulfill = source["message"].String();
+	event.description = source["description"].String();
+	event.effect.type = vstd::find_pos(typeNames, source["effect"]["type"].String());
+	event.effect.toOtherMessage = source["effect"]["messageToSend"].String();
+	event.trigger = EventExpression(source["condition"], JsonToCondition); // logical expression
+}
+
+void CMapFormatJson::writeTriggeredEvents(JsonSerializer & handler)
+{
+	JsonNode & output = handler.getCurrent();
+
+	serializeTriggeredEvents(handler);
+
+	JsonMap & triggeredEvents = output["triggeredEvents"].Struct();
+
+	for(auto event : mapHeader->triggeredEvents)
+		writeTriggeredEvent(event, triggeredEvents[event.identifier]);
+}
+
+void CMapFormatJson::writeTriggeredEvent(const TriggeredEvent& event, JsonNode& dest)
+{
+	using namespace TriggeredEventsDetail;
+
+	dest["message"].String() = event.onFulfill;
+	dest["description"].String() = event.description;
+
+	dest["effect"]["type"].String() = typeNames.at(size_t(event.effect.type));
+	dest["effect"]["messageToSend"].String() = event.effect.toOtherMessage;
+
+	dest["condition"] = event.trigger.toJson(ConditionToJson);
+}
+
+void CMapFormatJson::serializeOptions(JsonSerializeFormat & handler)
+{
+	//rumors
+
+	//disposedHeroes
+
+	//predefinedHeroes
+
+	handler.serializeLIC("allowedAbilities", &CHeroHandler::decodeSkill, &CHeroHandler::encodeSkill, VLC->heroh->getDefaultAllowedAbilities(), map->allowedAbilities);
+
+	handler.serializeLIC("allowedArtifacts", &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact, VLC->arth->getDefaultAllowed(), map->allowedArtifact);
+
+	handler.serializeLIC("allowedSpells", &CSpellHandler::decodeSpell, &CSpellHandler::encodeSpell, VLC->spellh->getDefaultAllowed(), map->allowedSpell);
+
+	//events
+}
+
+void CMapFormatJson::readOptions(JsonDeserializer & handler)
+{
+	serializeOptions(handler);
+}
+
+void CMapFormatJson::writeOptions(JsonSerializer & handler)
+{
+	serializeOptions(handler);
+}
+
+
+///CMapPatcher
+CMapPatcher::CMapPatcher(JsonNode stream):
+	input(stream)
+{
+	//todo: update map patches and change this
+	fileVersionMajor = 0;
+	fileVersionMinor = 0;
+}
+
+void CMapPatcher::patchMapHeader(std::unique_ptr<CMapHeader> & header)
+{
+	map = nullptr;
+	mapHeader = header.get();
 	if (!input.isNull())
 		readPatchData();
-	header.swap(mapHeader);
+}
+
+void CMapPatcher::readPatchData()
+{
+	JsonDeserializer handler(input);
+	readTriggeredEvents(handler);
+}
+
+
+///CMapLoaderJson
+CMapLoaderJson::CMapLoaderJson(CInputStream * stream):
+	buffer(stream),
+	ioApi(new CProxyROIOApi(buffer)),
+	loader("", "_", ioApi)
+{
+
+}
+
+si32 CMapLoaderJson::getIdentifier(const std::string& type, const std::string& name)
+{
+	boost::optional<si32> res = VLC->modh->identifiers.getIdentifier("core", type, name, false);
+
+	if(!res)
+	{
+		throw new std::runtime_error("Map load failed. Identifier not resolved.");
+	}
+	return res.get();
+}
+
+std::unique_ptr<CMap> CMapLoaderJson::loadMap()
+{
+	LOG_TRACE(logGlobal);
+	std::unique_ptr<CMap> result = std::unique_ptr<CMap>(new CMap());
+	map = result.get();
+	mapHeader = map;
+	readMap();
+	return std::move(result);
+}
+
+std::unique_ptr<CMapHeader> CMapLoaderJson::loadMapHeader()
+{
+	LOG_TRACE(logGlobal);
+	map = nullptr;
+	std::unique_ptr<CMapHeader> result = std::unique_ptr<CMapHeader>(new CMapHeader());
+	mapHeader = result.get();
+	readHeader(false);
+	return std::move(result);
+}
+
+JsonNode CMapLoaderJson::getFromArchive(const std::string & archiveFilename)
+{
+	ResourceID resource(archiveFilename, EResType::TEXT);
+
+	if(!loader.existsResource(resource))
+		throw new std::runtime_error(archiveFilename+" not found");
+
+	auto data = loader.load(resource)->readAll();
+
+	JsonNode res(reinterpret_cast<char*>(data.first.get()), data.second);
+
+	return std::move(res);
 }
 
 void CMapLoaderJson::readMap()
 {
-	readHeader();
-	assert(0); // Not implemented, vcmi does not have its own map format right now
+	LOG_TRACE(logGlobal);
+	readHeader(true);
+	map->initTerrain();
+	readTerrain();
+	readObjects();
+
+	map->calculateGuardingGreaturePositions();
 }
 
-void CMapLoaderJson::readHeader()
+void CMapLoaderJson::readHeader(const bool complete)
 {
-	//TODO: read such data like map name & size
-	readPatchData();
-	readPlayerInfo();
-	assert(0); // Not implemented
+	//do not use map field here, use only mapHeader
+	JsonNode header = getFromArchive(HEADER_FILE_NAME);
+
+	fileVersionMajor = header["versionMajor"].Float();
+
+	if(fileVersionMajor != VERSION_MAJOR)
+	{
+		logGlobal->errorStream() << "Unsupported map format version: " << fileVersionMajor;
+		throw std::runtime_error("Unsupported map format version");
+	}
+
+	fileVersionMinor = header["versionMinor"].Float();
+
+	if(fileVersionMinor > VERSION_MINOR)
+	{
+		logGlobal->traceStream() << "Too new map format revision: " << fileVersionMinor << ". This map should work but some of map features may be ignored.";
+	}
+
+	JsonDeserializer handler(header);
+
+	mapHeader->version = EMapFormat::VCMI;//todo: new version field
+
+	//todo: multilevel map load support
+	{
+		auto levels = handler.enterStruct("mapLevels");
+
+		{
+			auto surface = levels.enterStruct("surface");
+			mapHeader->height = surface.get()["height"].Float();
+			mapHeader->width = surface.get()["width"].Float();
+		}
+		{
+			auto underground = levels.enterStruct("underground");
+			mapHeader->twoLevel = !underground.get().isNull();
+		}
+	}
+
+	serializeHeader(handler);
+
+	readTriggeredEvents(handler);
+
+
+	readTeams(handler);
+	//TODO: readHeader
+
+	if(complete)
+		readOptions(handler);
 }
 
-void CMapLoaderJson::readPatchData()
+
+void CMapLoaderJson::readTerrainTile(const std::string& src, TerrainTile& tile)
 {
-	mapHeader->victoryMessage = input["victoryString"].String();
-	mapHeader->victoryIconIndex = input["victoryIconIndex"].Float();
+	using namespace TerrainDetail;
+	{//terrain type
+		const std::string typeCode = src.substr(0,2);
 
-	mapHeader->defeatMessage = input["defeatString"].String();
-	mapHeader->defeatIconIndex = input["defeatIconIndex"].Float();
+		int rawType = vstd::find_pos(terrainCodes, typeCode);
 
-	readTriggeredEvents();
+		if(rawType < 0)
+			throw new std::runtime_error("Invalid terrain type code in "+src);
+
+		tile.terType = ETerrainType(rawType);
+	}
+	int startPos = 2; //0+typeCode fixed length
+	{//terrain view
+		int pos = startPos;
+		while(isdigit(src.at(pos)))
+			pos++;
+		int len = pos - startPos;
+		if(len<=0)
+			throw new std::runtime_error("Invalid terrain view in "+src);
+		const std::string rawCode = src.substr(startPos,len);
+		tile.terView = atoi(rawCode.c_str());
+		startPos+=len;
+	}
+	{//terrain flip
+		int terrainFlip = vstd::find_pos(flipCodes, src.at(startPos++));
+		if(terrainFlip < 0)
+			throw new std::runtime_error("Invalid terrain flip in "+src);
+		else
+			tile.extTileFlags = terrainFlip;
+	}
+	if(startPos >= src.size())
+		return;
+	bool hasRoad = true;
+	{//road type
+		const std::string typeCode = src.substr(startPos,2);
+		startPos+=2;
+		int rawType = vstd::find_pos(roadCodes, typeCode);
+		if(rawType < 0)
+		{
+			rawType = vstd::find_pos(riverCodes, typeCode);
+			if(rawType < 0)
+				throw new std::runtime_error("Invalid river type in "+src);
+			else
+			{
+				tile.riverType = ERiverType::ERiverType(rawType);
+				hasRoad = false;
+			}
+		}
+		else
+			tile.roadType = ERoadType::ERoadType(rawType);
+	}
+	if(hasRoad)
+	{//road dir
+		int pos = startPos;
+		while(isdigit(src.at(pos)))
+			pos++;
+		int len = pos - startPos;
+		if(len<=0)
+			throw new std::runtime_error("Invalid road dir in "+src);
+		const std::string rawCode = src.substr(startPos,len);
+		tile.roadDir = atoi(rawCode.c_str());
+		startPos+=len;
+	}
+	if(hasRoad)
+	{//road flip
+		int flip = vstd::find_pos(flipCodes, src.at(startPos++));
+		if(flip < 0)
+			throw new std::runtime_error("Invalid road flip in "+src);
+		else
+			tile.extTileFlags |= (flip<<4);
+	}
+	if(startPos >= src.size())
+		return;
+	if(hasRoad)
+	{//river type
+		const std::string typeCode = src.substr(startPos,2);
+		startPos+=2;
+		int rawType = vstd::find_pos(riverCodes, typeCode);
+		if(rawType < 0)
+			throw new std::runtime_error("Invalid river type in "+src);
+		tile.riverType = ERiverType::ERiverType(rawType);
+	}
+	{//river dir
+		int pos = startPos;
+		while(isdigit(src.at(pos)))
+			pos++;
+		int len = pos - startPos;
+		if(len<=0)
+			throw new std::runtime_error("Invalid river dir in "+src);
+		const std::string rawCode = src.substr(startPos,len);
+		tile.riverDir = atoi(rawCode.c_str());
+		startPos+=len;
+	}
+	{//river flip
+		int flip = vstd::find_pos(flipCodes, src.at(startPos++));
+		if(flip < 0)
+			throw new std::runtime_error("Invalid road flip in "+src);
+		else
+			tile.extTileFlags |= (flip<<2);
+	}
 }
 
-void CMapLoaderJson::readTriggeredEvents()
+void CMapLoaderJson::readTerrainLevel(const JsonNode& src, const int index)
 {
-	mapHeader->triggeredEvents.clear();
+	int3 pos(0,0,index);
 
-	for (auto & entry : input["triggeredEvents"].Struct())
+	const JsonVector & rows = src.Vector();
+
+	if(rows.size() != map->height)
+		throw new std::runtime_error("Invalid terrain data");
+
+	for(pos.y = 0; pos.y < map->height; pos.y++)
 	{
-		TriggeredEvent event;
-		event.identifier = entry.first;
-		readTriggeredEvent(event, entry.second);
-		mapHeader->triggeredEvents.push_back(event);
+		const JsonVector & tiles = rows[pos.y].Vector();
+
+		if(tiles.size() != map->width)
+			throw new std::runtime_error("Invalid terrain data");
+
+		for(pos.x = 0; pos.x < map->width; pos.x++)
+			readTerrainTile(tiles[pos.x].String(), map->getTile(pos));
+	}
+}
+
+void CMapLoaderJson::readTerrain()
+{
+	{
+		const JsonNode surface = getFromArchive("surface_terrain.json");
+		readTerrainLevel(surface, 0);
+	}
+	if(map->twoLevel)
+	{
+		const JsonNode underground = getFromArchive("underground_terrain.json");
+		readTerrainLevel(underground, 1);
+	}
+
+}
+
+CMapLoaderJson::MapObjectLoader::MapObjectLoader(CMapLoaderJson * _owner, JsonMap::value_type& json):
+	owner(_owner), instance(nullptr),id(-1), jsonKey(json.first), configuration(json.second)
+{
+
+}
+
+void CMapLoaderJson::MapObjectLoader::construct()
+{
+	logGlobal->debugStream() <<"Loading: " <<jsonKey;
+
+	//TODO:consider move to ObjectTypeHandler
+	//find type handler
+	std::string typeName = configuration["type"].String(), subtypeName = configuration["subtype"].String();
+	if(typeName.empty())
+	{
+		logGlobal->errorStream() << "Object type missing";
+		logGlobal->debugStream() << configuration;
+		return;
 	}
+
+	int3 pos;
+	pos.x = configuration["x"].Float();
+	pos.y = configuration["y"].Float();
+	pos.z = configuration["l"].Float();
+
+	//special case for grail
+    if(typeName == "grail")
+	{
+		owner->map->grailPos = pos;
+
+		owner->map->grailRadius = configuration["options"]["grailRadius"].Float();
+		return;
+	}
+	else if(subtypeName.empty())
+	{
+		logGlobal->errorStream() << "Object subtype missing";
+		logGlobal->debugStream() << configuration;
+		return;
+	}
+
+	auto handler = VLC->objtypeh->getHandlerFor(typeName, subtypeName);
+
+	ObjectTemplate appearance;
+
+	appearance.readJson(configuration["template"], false);
+	appearance.id = Obj(handler->type);
+	appearance.subid = handler->subtype;
+
+	instance = handler->create(appearance);
+
+	instance->id = ObjectInstanceID(owner->map->objects.size());
+	instance->instanceName = jsonKey;
+	instance->pos = pos;
+	owner->map->addNewObject(instance);
 }
 
-static EventCondition JsonToCondition(const JsonNode & node)
+void CMapLoaderJson::MapObjectLoader::configure()
 {
-	EventCondition event;
-	event.condition = EventCondition::EWinLoseType(vstd::find_pos(conditionNames, node.Vector()[0].String()));
-	if (node.Vector().size() > 1)
+	if(nullptr == instance)
+		return;
+
+	JsonDeserializer handler(configuration);
+
+	instance->serializeJson(handler);
+
+	if(auto art = dynamic_cast<CGArtifact *>(instance))
 	{
-		const JsonNode & data = node.Vector()[1];
-		if (data["type"].getType() == JsonNode::DATA_STRING)
-			event.objectType = VLC->modh->identifiers.getIdentifier(data["type"]).get();
-		if (data["type"].getType() == JsonNode::DATA_FLOAT)
-			event.objectType = data["type"].Float();
+		//todo: find better place for this code
 
-		if (!data["value"].isNull())
-			event.value = data["value"].Float();
+		int artID = ArtifactID::NONE;
+		int spellID = -1;
 
-		if (!data["position"].isNull())
+		if(art->ID == Obj::SPELL_SCROLL)
+		{
+			auto spellIdentifier = configuration["options"]["spell"].String();
+			auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", spellIdentifier);
+			if(rawId)
+				spellID = rawId.get();
+			else
+				spellID = 0;
+			artID = ArtifactID::SPELL_SCROLL;
+		}
+		else if(art->ID  == Obj::ARTIFACT)
 		{
-			event.position.x = data["position"].Vector()[0].Float();
-			event.position.y = data["position"].Vector()[1].Float();
-			event.position.z = data["position"].Vector()[2].Float();
+			//specific artifact
+			artID = art->subID;
 		}
+
+		art->storedArtifact = CArtifactInstance::createArtifact(owner->map, artID, spellID);
 	}
-	return event;
 }
 
-void CMapLoaderJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source)
+void CMapLoaderJson::readObjects()
 {
-	event.onFulfill = source["message"].String();
-	event.description = source["description"].String();
-	event.effect.type = vstd::find_pos(typeNames, source["effect"]["type"].String());
-	event.effect.toOtherMessage = source["effect"]["messageToSend"].String();
-	event.trigger = EventExpression(source["condition"], JsonToCondition); // logical expression
+	LOG_TRACE(logGlobal);
+
+	std::vector<std::unique_ptr<MapObjectLoader>> loaders;//todo: optimize MapObjectLoader memory layout
+
+	JsonNode data = getFromArchive(OBJECTS_FILE_NAME);
+
+	//get raw data
+	for(auto & p : data.Struct())
+		loaders.push_back(vstd::make_unique<MapObjectLoader>(this, p));
+
+	for(auto & ptr : loaders)
+		ptr->construct();
+
+	//configure objects after all objects are constructed so we may resolve internal IDs even to actual pointers OTF
+	for(auto & ptr : loaders)
+		ptr->configure();
+
+	std::sort(map->heroesOnMap.begin(), map->heroesOnMap.end(), [](const ConstTransitivePtr<CGHeroInstance> & a, const ConstTransitivePtr<CGHeroInstance> & b)
+	{
+		return a->subID < b->subID;
+	});
+}
+
+///CMapSaverJson
+CMapSaverJson::CMapSaverJson(CInputOutputStream * stream):
+	buffer(stream),
+	ioApi(new CProxyIOApi(buffer)),
+	saver(ioApi, "_")
+{
+	fileVersionMajor = VERSION_MAJOR;
+	fileVersionMinor = VERSION_MINOR;
 }
 
-void CMapLoaderJson::readPlayerInfo()
+CMapSaverJson::~CMapSaverJson()
 {
-	assert(0); // Not implemented
+
 }
+
+void CMapSaverJson::addToArchive(const JsonNode& data, const std::string& filename)
+{
+	std::ostringstream out;
+	out << data;
+	out.flush();
+
+	{
+		auto s = out.str();
+		std::unique_ptr<COutputStream> stream = saver.addFile(filename);
+
+		if (stream->write((const ui8*)s.c_str(), s.size()) != s.size())
+			throw new std::runtime_error("CMapSaverJson::saveHeader() zip compression failed.");
+	}
+}
+
+void CMapSaverJson::saveMap(const std::unique_ptr<CMap>& map)
+{
+	this->map = map.get();
+	this->mapHeader = this->map;
+	writeHeader();
+	writeTerrain();
+	writeObjects();
+}
+
+void CMapSaverJson::writeHeader()
+{
+	JsonNode header;
+	JsonSerializer handler(header);
+
+	header["versionMajor"].Float() = VERSION_MAJOR;
+	header["versionMinor"].Float() = VERSION_MINOR;
+
+	//todo: multilevel map save support
+	JsonNode & levels = header["mapLevels"];
+	levels["surface"]["height"].Float() = mapHeader->height;
+	levels["surface"]["width"].Float() = mapHeader->width;
+	levels["surface"]["index"].Float() = 0;
+
+	if(mapHeader->twoLevel)
+	{
+		levels["underground"]["height"].Float() = mapHeader->height;
+		levels["underground"]["width"].Float() = mapHeader->width;
+		levels["underground"]["index"].Float() = 1;
+	}
+
+	serializeHeader(handler);
+
+	writeTriggeredEvents(handler);
+
+	writeTeams(handler);
+
+	writeOptions(handler);
+
+	addToArchive(header, HEADER_FILE_NAME);
+}
+
+const std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile)
+{
+	using namespace TerrainDetail;
+
+	std::ostringstream out;
+	out.setf(std::ios::dec, std::ios::basefield);
+	out.unsetf(std::ios::showbase);
+
+	out << terrainCodes.at(int(tile.terType)) << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
+
+	if(tile.roadType != ERoadType::NO_ROAD)
+	{
+		out << roadCodes.at(int(tile.roadType)) << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
+	}
+
+	if(tile.riverType != ERiverType::NO_RIVER)
+	{
+		out << riverCodes.at(int(tile.riverType)) << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
+	}
+
+	return out.str();
+}
+
+JsonNode CMapSaverJson::writeTerrainLevel(const int index)
+{
+	JsonNode data;
+	int3 pos(0,0,index);
+
+	data.Vector().resize(map->height);
+
+	for(pos.y = 0; pos.y < map->height; pos.y++)
+	{
+		JsonNode & row = data.Vector()[pos.y];
+
+		row.Vector().resize(map->width);
+
+		for(pos.x = 0; pos.x < map->width; pos.x++)
+			row.Vector()[pos.x].String() = writeTerrainTile(map->getTile(pos));
+	}
+
+	return std::move(data);
+}
+
+void CMapSaverJson::writeTerrain()
+{
+	//todo: multilevel map save support
+
+	JsonNode surface = writeTerrainLevel(0);
+	addToArchive(surface, "surface_terrain.json");
+
+	if(map->twoLevel)
+	{
+		JsonNode underground = writeTerrainLevel(1);
+		addToArchive(underground, "underground_terrain.json");
+	}
+}
+
+void CMapSaverJson::writeObjects()
+{
+	JsonNode data(JsonNode::DATA_STRUCT);
+
+	JsonSerializer handler(data);
+
+	for(CGObjectInstance * obj : map->objects)
+	{
+		auto temp = handler.enterStruct(obj->instanceName);
+
+		obj->serializeJson(handler);
+	}
+
+	if(map->grailPos.valid())
+	{
+		JsonNode grail(JsonNode::DATA_STRUCT);
+		grail["type"].String() = "grail";
+
+		grail["x"].Float() = map->grailPos.x;
+		grail["y"].Float() = map->grailPos.y;
+		grail["l"].Float() = map->grailPos.z;
+
+		grail["options"]["radius"].Float() = map->grailRadius;
+
+		std::string grailId = boost::str(boost::format("grail_%d") % map->objects.size());
+
+		data[grailId] = grail;
+	}
+
+	addToArchive(data, OBJECTS_FILE_NAME);
+}
+

+ 197 - 24
lib/mapping/MapFormatJson.h

@@ -1,6 +1,5 @@
-
 /*
- * MapFormatH3M.h, part of VCMI engine
+ * MapFormatJSON.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -14,17 +13,136 @@
 #include "CMapService.h"
 #include "../JsonNode.h"
 
+#include "../filesystem/CZipSaver.h"
+#include "../filesystem/CZipLoader.h"
+#include "../GameConstants.h"
+
 class TriggeredEvent;
+struct TerrainTile;
+struct PlayerInfo;
+class CGObjectInstance;
+class AObjectTypeHandler;
+
+class JsonSerializeFormat;
+class JsonDeserializer;
+class JsonSerializer;
+
+class DLL_LINKAGE CMapFormatJson
+{
+public:
+	static const int VERSION_MAJOR;
+	static const int VERSION_MINOR;
+
+	static const std::string HEADER_FILE_NAME;
+	static const std::string OBJECTS_FILE_NAME;
+
+	int fileVersionMajor;
+	int fileVersionMinor;
+protected:
+
+	/** ptr to the map object which gets filled by data from the buffer or written to buffer */
+	CMap * map;
+
+	/**
+	 * ptr to the map header object which gets filled by data from the buffer.
+	 * (when loading map and mapHeader point to the same object)
+	 */
+	CMapHeader * mapHeader;
+
+	void serializeAllowedFactions(JsonSerializeFormat & handler, std::set<TFaction> & value);
+
+	///common part of header saving/loading
+	void serializeHeader(JsonSerializeFormat & handler);
+
+	///player information saving/loading
+	void serializePlayerInfo(JsonSerializeFormat & handler);
+
+	/**
+	 * Reads team settings to header
+	 */
+	void readTeams(JsonDeserializer & handler);
+
+	/**
+	 * Saves team settings to header
+	 */
+	void writeTeams(JsonSerializer & handler);
+
+
+	///common part triggered events of saving/loading
+	void serializeTriggeredEvents(JsonSerializeFormat & handler);
+
+	/**
+	 * Reads triggered events, including victory/loss conditions
+	 */
+	void readTriggeredEvents(JsonDeserializer & handler);
+
+	/**
+	 * Writes triggered events, including victory/loss conditions
+	 */
+	void writeTriggeredEvents(JsonSerializer & handler);
+
+	/**
+	 * Reads one of triggered events
+	 */
+	void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source);
+
+	/**
+	 * Writes one of triggered events
+	 */
+	void writeTriggeredEvent(const TriggeredEvent & event, JsonNode & dest);
+
 
-class DLL_LINKAGE CMapLoaderJson : public IMapPatcher
+
+	///common part of map attributes saving/loading
+	void serializeOptions(JsonSerializeFormat & handler);
+
+	/**
+	 * Loads map attributes except header ones
+	 */
+	void readOptions(JsonDeserializer & handler);
+
+	/**
+	 * Saves map attributes except header ones
+	 */
+	void writeOptions(JsonSerializer & handler);
+};
+
+class DLL_LINKAGE CMapPatcher : public CMapFormatJson, public IMapPatcher
 {
 public:
 	/**
 	 * Default constructor.
 	 *
+	 * @param stream. A stream containing the map data.
+	 */
+	CMapPatcher(JsonNode stream);
+
+public: //IMapPatcher
+	/**
+	 * Modifies supplied map header using Json data
+	 *
+	 */
+	void patchMapHeader(std::unique_ptr<CMapHeader> & header) override;
+
+private:
+	/**
+	 * Reads subset of header that can be replaced by patching.
+	 */
+	void readPatchData();
+
+
+	JsonNode input;
+};
+
+class DLL_LINKAGE CMapLoaderJson : public CMapFormatJson, public IMapLoader
+{
+public:
+	/**
+	 * Constructor.
+	 *
 	 * @param stream a stream containing the map data
 	 */
-	CMapLoaderJson(JsonNode stream);
+	CMapLoaderJson(CInputStream * stream);
 
 	/**
 	 * Loads the VCMI/Json map file.
@@ -40,52 +158,107 @@ public:
 	 */
 	std::unique_ptr<CMapHeader> loadMapHeader() override;
 
+private:
+
+	struct MapObjectLoader
+	{
+		MapObjectLoader(CMapLoaderJson * _owner, JsonMap::value_type & json);
+		CMapLoaderJson * owner;
+		CGObjectInstance * instance;
+		ObjectInstanceID id;
+		std::string jsonKey;//full id defined by map creator
+		JsonNode & configuration;
+
+		///constructs object (without configuration)
+		void construct();
+
+		///configures object
+		void configure();
+
+	};
+
+	si32 getIdentifier(const std::string & type, const std::string & name);
+
 	/**
-	 * Modifies supplied map header using Json data
-	 *
+	 * Reads the map header.
 	 */
-	void patchMapHeader(std::unique_ptr<CMapHeader> & header) override;
+	void readHeader(const bool complete);
 
-private:
 	/**
 	 * Reads complete map.
 	 */
 	void readMap();
 
+	void readTerrainTile(const std::string & src, TerrainTile & tile);
+
+	void readTerrainLevel(const JsonNode & src, const int index);
+
+	void readTerrain();
+
 	/**
-	 * Reads the map header.
+	 * Loads all map objects from zip archive
 	 */
-	void readHeader();
+	void readObjects();
+
+	JsonNode getFromArchive(const std::string & archiveFilename);
+
+	CInputStream * buffer;
+	std::shared_ptr<CIOApi> ioApi;
+
+	CZipLoader loader;///< object to handle zip archive operations
+};
 
+class DLL_LINKAGE CMapSaverJson : public CMapFormatJson, public IMapSaver
+{
+public:
 	/**
-	 * Reads subset of header that can be replaced by patching.
+	 * Constructor.
+	 *
+	 * @param stream a stream to save the map to, will contain zip archive
 	 */
-	void readPatchData();
+	CMapSaverJson(CInputOutputStream * stream);
+
+	~CMapSaverJson();
 
 	/**
-	 * Reads player information.
+	 * Actually saves the VCMI/Json map into stream.
 	 */
-	void readPlayerInfo();
+	void saveMap(const std::unique_ptr<CMap> & map) override;
+private:
 
 	/**
-	 * Reads triggered events, including victory/loss conditions
+	 * Saves @data as json file with specified @filename
 	 */
-	void readTriggeredEvents();
+	void addToArchive(const JsonNode & data, const std::string & filename);
 
 	/**
-	 * Reads one of triggered events
+	 * Saves header to zip archive
 	 */
-	void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source);
+	void writeHeader();
 
+	/**
+	 * Encodes one tile into string
+	 * @param tile tile to serialize
+	 */
+	const std::string writeTerrainTile(const TerrainTile & tile);
 
-	/** ptr to the map object which gets filled by data from the buffer */
-	CMap * map;
+	/**
+	 * Saves map level into json
+	 * @param index z coordinate
+	 */
+	JsonNode writeTerrainLevel(const int index);
 
 	/**
-	 * ptr to the map header object which gets filled by data from the buffer.
-	 * (when loading map and mapHeader point to the same object)
+	 * Saves all terrain into zip archive
+	 */
+	void writeTerrain();
+
+	/**
+	 * Saves all map objects into zip archive
 	 */
-	std::unique_ptr<CMapHeader> mapHeader;
+	void writeObjects();
 
-	const JsonNode input;
+	CInputOutputStream * buffer;
+	std::shared_ptr<CIOApi> ioApi;
+	CZipSaver saver;///< object to handle zip archive operations
 };

+ 4 - 5
lib/rmg/CMapGenerator.cpp

@@ -565,11 +565,10 @@ void CMapGenerator::createConnections()
 		}
 		if (!guardPos.valid())
 		{
-			auto teleport1 = new CGMonolith;
-			teleport1->ID = Obj::MONOLITH_TWO_WAY;
-			teleport1->subID = getNextMonlithIndex();
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, getNextMonlithIndex());
+			auto teleport1 = factory->create(ObjectTemplate());
 
-			auto teleport2 = new CGMonolith(*teleport1);
+			auto teleport2 = factory->create(ObjectTemplate());
 
 			zoneA->addRequiredObject (teleport1, connection.getGuardStrength());
 			zoneB->addRequiredObject (teleport2, connection.getGuardStrength());
@@ -579,7 +578,7 @@ void CMapGenerator::createConnections()
 
 void CMapGenerator::addHeaderInfo()
 {
-	map->version = EMapFormat::SOD;
+	map->version = EMapFormat::VCMI;
 	map->width = mapGenOptions->getWidth();
 	map->height = mapGenOptions->getHeight();
 	map->twoLevel = mapGenOptions->getHasTwoLevels();

+ 64 - 70
lib/rmg/CRmgTemplateZone.cpp

@@ -1036,10 +1036,9 @@ bool CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength,
 		amount = strength / VLC->creh->creatures[creId]->AIValue;
 	}
 
+	auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId);
 
-	auto guard = new CGCreature();
-	guard->ID = Obj::MONSTER;
-	guard->subID = creId;
+	auto guard = (CGCreature *) guardFactory->create(ObjectTemplate());
 	guard->character = CGCreature::HOSTILE;
 	auto  hlp = new CStackInstance(creId, amount);
 	//will be set during initialization
@@ -1318,19 +1317,23 @@ void CRmgTemplateZone::initTownType (CMapGenerator* gen)
 	{
 		for (int i = 0; i < count; i++)
 		{
-			auto town = new CGTownInstance();
-			town->ID = Obj::TOWN;
+			si32 subType = townType;
 
-			if (this->townsAreSameType)
-				town->subID = townType;
-			else
+			if(totalTowns>0)
 			{
-				if (townTypes.size())
-					town->subID = *RandomGeneratorUtil::nextItem(townTypes, gen->rand);
-				else
-					town->subID = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed
+				if(!this->townsAreSameType)
+				{
+					if (townTypes.size())
+						subType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand);
+					else
+						subType = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed
+				}
 			}
 
+			auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, subType);
+			auto town = (CGTownInstance *) townFactory->create(ObjectTemplate());
+			town->ID = Obj::TOWN;
+
 			town->tempOwner = player;
 			if (hasFort)
 				town->builtBuildings.insert(BuildingID::FORT);
@@ -1342,10 +1345,8 @@ void CRmgTemplateZone::initTownType (CMapGenerator* gen)
 					town->possibleSpells.push_back(spell->id);
 			}
 
-			if (!totalTowns)
+			if (totalTowns <= 0)
 			{
-				//first town in zone sets the facton of entire zone
-				town->subID = townType;
 				//register MAIN town of zone
 				gen->registerZone(town->subID);
 				//first town in zone goes in the middle
@@ -1381,10 +1382,9 @@ void CRmgTemplateZone::initTownType (CMapGenerator* gen)
 			randomizeTownType(gen);
 		}
 
-		auto  town = new CGTownInstance();
-		town->ID = Obj::TOWN;
+		auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, townType);
 
-		town->subID = townType;
+		CGTownInstance * town = (CGTownInstance *) townFactory->create(ObjectTemplate());
 		town->tempOwner = player;
 		town->builtBuildings.insert(BuildingID::FORT);
 		town->builtBuildings.insert(BuildingID::DEFAULT);
@@ -1495,21 +1495,27 @@ void CRmgTemplateZone::paintZoneTerrain (CMapGenerator* gen, ETerrainType terrai
 
 bool CRmgTemplateZone::placeMines (CMapGenerator* gen)
 {
-	std::vector<Res::ERes> required_mines;
-	required_mines.push_back(Res::ERes::WOOD);
-	required_mines.push_back(Res::ERes::ORE);
-
 	static const Res::ERes woodOre[] = {Res::ERes::WOOD, Res::ERes::ORE};
 	static const Res::ERes preciousResources[] = {Res::ERes::GEMS, Res::ERes::CRYSTAL, Res::ERes::MERCURY, Res::ERes::SULFUR};
 
+	std::array<TObjectTypeHandler, 7> factory =
+	{
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 0),
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 1),
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 2),
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 3),
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 4),
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 5),
+		VLC->objtypeh->getHandlerFor(Obj::MINE, 6)
+	};
+
 	for (const auto & res : woodOre)
 	{
 		for (int i = 0; i < mines[res]; i++)
 		{
-			auto mine = new CGMine();
-			mine->ID = Obj::MINE;
-			mine->subID = static_cast<si32>(res);
+			auto mine = (CGMine *) factory.at(static_cast<si32>(res))->create(ObjectTemplate());
 			mine->producedResource = res;
+			mine->tempOwner = PlayerColor::NEUTRAL;
 			mine->producedQuantity = mine->defaultResProduction();
 			if (!i)
 				addCloseObject(mine, 1500); //only firts one is close
@@ -1521,20 +1527,18 @@ bool CRmgTemplateZone::placeMines (CMapGenerator* gen)
 	{
 		for (int i = 0; i < mines[res]; i++)
 		{
-			auto mine = new CGMine();
-			mine->ID = Obj::MINE;
-			mine->subID = static_cast<si32>(res);
+			auto mine = (CGMine *) factory.at(static_cast<si32>(res))->create(ObjectTemplate());
 			mine->producedResource = res;
+			mine->tempOwner = PlayerColor::NEUTRAL;
 			mine->producedQuantity = mine->defaultResProduction();
 			addRequiredObject(mine, 3500);
 		}
 	}
 	for (int i = 0; i < mines[Res::GOLD]; i++)
 	{
-		auto mine = new CGMine();
-		mine->ID = Obj::MINE;
-		mine->subID = static_cast<si32>(Res::GOLD);
+		auto mine = (CGMine *) factory.at(Res::GOLD)->create(ObjectTemplate());
 		mine->producedResource = Res::GOLD;
+		mine->tempOwner = PlayerColor::NEUTRAL;
 		mine->producedQuantity = mine->defaultResProduction();
 		addRequiredObject(mine, 7000);
 	}
@@ -2105,9 +2109,8 @@ void CRmgTemplateZone::placeAndGuardObject(CMapGenerator* gen, CGObjectInstance*
 
 void CRmgTemplateZone::placeSubterraneanGate(CMapGenerator* gen, int3 pos, si32 guardStrength)
 {
-	auto gate = new CGSubterraneanGate;
-	gate->ID = Obj::SUBTERRANEAN_GATE;
-	gate->subID = 0;
+	auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0);
+	auto gate = factory->create(ObjectTemplate());
 	placeObject (gen, gate, pos, true);
 	addToConnectLater (getAccessibleOffset (gen, gate->appearance, pos)); //guard will be placed on accessibleOffset
 	guardObject (gen, gate, guardStrength, true);
@@ -2301,9 +2304,8 @@ ObjectInfo CRmgTemplateZone::getRandomObject(CMapGenerator* gen, CTreasurePileIn
 		{
 			oi.generateObject = [minValue]() -> CGObjectInstance *
 			{
-				auto obj = new CGPandoraBox();
-				obj->ID = Obj::PANDORAS_BOX;
-				obj->subID = 0;
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+				auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 				obj->resources[Res::GOLD] = minValue;
 				return obj;
 			};
@@ -2391,9 +2393,6 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	{
 		oi.generateObject = [i, gen, this]() -> CGObjectInstance *
 		{
-			auto obj = new CGHeroInstance;
-			obj->ID = Obj::PRISON;
-
 			std::vector<ui32> possibleHeroes;
 			for (int j = 0; j < gen->map->allowedHeroes.size(); j++)
 			{
@@ -2402,6 +2401,10 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 			}
 
 			auto hid = *RandomGeneratorUtil::nextItem(possibleHeroes, gen->rand);
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
+			auto obj = (CGHeroInstance *) factory->create(ObjectTemplate());
+
+
 			obj->subID = hid; //will be initialized later
 			obj->exp = prisonExp[i];
 			obj->setOwner(PlayerColor::NEUTRAL);
@@ -2469,9 +2472,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	{
 		oi.generateObject = [i, gen]() -> CGObjectInstance *
 		{
-			auto obj = new CGArtifact();
-			obj->ID = Obj::SPELL_SCROLL;
-			obj->subID = 0;
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::SPELL_SCROLL, 0);
+			auto obj = (CGArtifact *) factory->create(ObjectTemplate());
 			std::vector<SpellID> out;
 
 			for (auto spell : VLC->spellh->objects) //spellh size appears to be greater (?)
@@ -2497,9 +2499,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	{
 		oi.generateObject = [i]() -> CGObjectInstance *
 		{
-			auto obj = new CGPandoraBox();
-			obj->ID = Obj::PANDORAS_BOX;
-			obj->subID = 0;
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 			obj->resources[Res::GOLD] = i * 5000;
 			return obj;
 		};
@@ -2514,9 +2515,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	{
 		oi.generateObject = [i]() -> CGObjectInstance *
 		{
-			auto obj = new CGPandoraBox();
-			obj->ID = Obj::PANDORAS_BOX;
-			obj->subID = 0;
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 			obj->gainedExp = i * 5000;
 			return obj;
 		};
@@ -2562,9 +2562,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 
 		oi.generateObject = [creature, creaturesAmount]() -> CGObjectInstance *
 		{
-			auto obj = new CGPandoraBox();
-			obj->ID = Obj::PANDORAS_BOX;
-			obj->subID = 0;
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 			auto stack = new CStackInstance(creature, creaturesAmount);
 			obj->creatures.putStack(SlotID(0), stack);
 			return obj;
@@ -2580,9 +2579,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	{
 		oi.generateObject = [i, gen]() -> CGObjectInstance *
 		{
-			auto obj = new CGPandoraBox();
-			obj->ID = Obj::PANDORAS_BOX;
-			obj->subID = 0;
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 
 			std::vector <CSpell *> spells;
 			for (auto spell : VLC->spellh->objects)
@@ -2610,9 +2608,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 	{
 		oi.generateObject = [i,gen]() -> CGObjectInstance *
 		{
-			auto obj = new CGPandoraBox();
-			obj->ID = Obj::PANDORAS_BOX;
-			obj->subID = 0;
+			auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+			auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 
 			std::vector <CSpell *> spells;
 			for (auto spell : VLC->spellh->objects)
@@ -2640,9 +2637,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 
 	oi.generateObject = [gen]() -> CGObjectInstance *
 	{
-		auto obj = new CGPandoraBox();
-		obj->ID = Obj::PANDORAS_BOX;
-		obj->subID = 0;
+		auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
+		auto obj = (CGPandoraBox *) factory->create(ObjectTemplate());
 
 		std::vector <CSpell *> spells;
 		for (auto spell : VLC->spellh->objects)
@@ -2713,9 +2709,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 
 			oi.generateObject = [creature, creaturesAmount, randomAppearance, gen, this, generateArtInfo]() -> CGObjectInstance *
 			{
-				auto obj = new CGSeerHut();
-				obj->ID = Obj::SEER_HUT;
-				obj->subID = randomAppearance;
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
+				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
 				obj->rewardType = CGSeerHut::CREATURE;
 				obj->rID = creature->idNumber;
 				obj->rVal = creaturesAmount;
@@ -2752,9 +2747,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 
 			oi.generateObject = [i, randomAppearance, gen, this, generateArtInfo]() -> CGObjectInstance *
 			{
-				auto obj = new CGSeerHut();
-				obj->ID = Obj::SEER_HUT;
-				obj->subID = randomAppearance;
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
+				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
+
 				obj->rewardType = CGSeerHut::EXPERIENCE;
 				obj->rID = 0; //unitialized?
 				obj->rVal = seerExpGold[i];
@@ -2777,9 +2772,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 
 			oi.generateObject = [i, randomAppearance, gen, this, generateArtInfo]() -> CGObjectInstance *
 			{
-				auto obj = new CGSeerHut();
-				obj->ID = Obj::SEER_HUT;
-				obj->subID = randomAppearance;
+				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
+				auto obj = (CGSeerHut *) factory->create(ObjectTemplate());
 				obj->rewardType = CGSeerHut::RESOURCES;
 				obj->rID = Res::GOLD;
 				obj->rVal = seerExpGold[i];

+ 163 - 0
lib/serializer/JsonDeserializer.cpp

@@ -0,0 +1,163 @@
+/*
+ * JsonDeserializer.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 "JsonDeserializer.h"
+
+#include "../JsonNode.h"
+
+JsonDeserializer::JsonDeserializer(JsonNode & root_):
+	JsonSerializeFormat(root_, false)
+{
+
+}
+
+void JsonDeserializer::serializeBool(const std::string & fieldName, bool & value)
+{
+	value = current->operator[](fieldName).Bool();
+}
+
+void JsonDeserializer::serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value)
+{
+	const JsonNode & tmp = current->operator[](fieldName);
+
+	value = tmp.String() == trueValue;
+}
+
+void JsonDeserializer::serializeFloat(const std::string & fieldName, double & value)
+{
+	value = current->operator[](fieldName).Float();
+}
+
+void JsonDeserializer::serializeIntEnum(const std::string & fieldName, const std::vector<std::string> & enumMap, const si32 defaultValue, si32 & value)
+{
+	const std::string & valueName = current->operator[](fieldName).String();
+
+	si32 rawValue = vstd::find_pos(enumMap, valueName);
+	if(rawValue < 0)
+		value = defaultValue;
+	else
+		value = rawValue;
+}
+
+void JsonDeserializer::serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value)
+{
+	std::string identifier;
+	serializeString(fieldName, identifier);
+
+	if(identifier == "")
+	{
+		value = defaultValue;
+		return;
+	}
+
+	si32 rawId = decoder(identifier);
+	if(rawId >= 0)
+		value = rawId;
+	else
+		value = defaultValue;
+}
+
+void JsonDeserializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector<bool> & standard, std::vector<bool> & value)
+{
+	const JsonNode & field = current->operator[](fieldName);
+	if(field.isNull())
+		return;
+
+	const JsonNode & anyOf = field["anyOf"];
+	const JsonNode & allOf = field["allOf"];
+	const JsonNode & noneOf = field["noneOf"];
+
+	if(anyOf.Vector().empty() && allOf.Vector().empty())
+	{
+		//permissive mode
+		value = standard;
+	}
+	else
+	{
+		//restrictive mode
+		value.clear();
+		value.resize(standard.size(), false);
+
+		readLICPart(anyOf, decoder, true, value);
+		readLICPart(allOf, decoder, true, value);
+	}
+
+	readLICPart(noneOf, decoder, false, value);
+}
+
+void JsonDeserializer::serializeLIC(const std::string & fieldName, LIC & value)
+{
+	const JsonNode & field = current->operator[](fieldName);
+
+	const JsonNode & anyOf = field["anyOf"];
+	const JsonNode & allOf = field["allOf"];
+	const JsonNode & noneOf = field["noneOf"];
+
+	if(anyOf.Vector().empty())
+	{
+		//permissive mode
+		value.any = value.standard;
+	}
+	else
+	{
+		//restrictive mode
+		value.any.clear();
+		value.any.resize(value.standard.size(), false);
+
+		readLICPart(anyOf, value.decoder, true, value.any);
+	}
+
+	readLICPart(allOf, value.decoder, true, value.all);
+	readLICPart(noneOf, value.decoder, true, value.none);
+
+	//remove any banned from allowed and required
+	for(si32 idx = 0; idx < value.none.size(); idx++)
+	{
+		if(value.none[idx])
+		{
+			value.all[idx] = false;
+			value.any[idx] = false;
+		}
+	}
+
+	//add all required to allowed
+	for(si32 idx = 0; idx < value.all.size(); idx++)
+	{
+		if(value.all[idx])
+		{
+			value.any[idx] = true;
+		}
+	}
+}
+
+void JsonDeserializer::serializeString(const std::string & fieldName, std::string & value)
+{
+	value = current->operator[](fieldName).String();
+}
+
+void JsonDeserializer::readLICPart(const JsonNode & part, const TDecoder & decoder, const bool val, std::vector<bool> & value)
+{
+	for(size_t index = 0; index < part.Vector().size(); index++)
+	{
+		const std::string & identifier = part.Vector()[index].String();
+
+		const si32 rawId = decoder(identifier);
+		if(rawId >= 0)
+		{
+			if(rawId < value.size())
+				value[rawId] = val;
+			else
+				logGlobal->errorStream() << "JsonDeserializer::serializeLIC: id out of bounds " << rawId;
+		}
+	}
+}
+

+ 34 - 0
lib/serializer/JsonDeserializer.h

@@ -0,0 +1,34 @@
+/*
+ * JsonDeserializer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include  "JsonSerializeFormat.h"
+
+class JsonNode;
+
+class JsonDeserializer: public JsonSerializeFormat
+{
+public:
+	JsonDeserializer(JsonNode & root_);
+
+	void serializeBool(const std::string & fieldName, bool & value) override;
+	void serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) override;
+	void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector<bool> & standard, std::vector<bool> & value) override;
+	void serializeLIC(const std::string & fieldName, LIC & value) override;
+	void serializeString(const std::string & fieldName, std::string & value) override;
+
+protected:
+	void serializeFloat(const std::string & fieldName, double & value) override;
+	void serializeIntEnum(const std::string & fieldName, const std::vector<std::string> & enumMap, const si32 defaultValue, si32 & value) override;
+	void serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) override;
+private:
+	void readLICPart(const JsonNode & part, const TDecoder & decoder, const bool val, std::vector<bool> & value);
+};

+ 90 - 0
lib/serializer/JsonSerializeFormat.cpp

@@ -0,0 +1,90 @@
+/*
+ * JsonSerializeFormat.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 "JsonSerializeFormat.h"
+
+#include "../JsonNode.h"
+
+
+//JsonStructSerializer
+JsonStructSerializer::JsonStructSerializer(JsonStructSerializer&& other):
+	restoreState(false),
+	owner(other.owner),
+	parentNode(other.parentNode),
+	thisNode(other.thisNode)
+{
+
+}
+
+JsonStructSerializer::~JsonStructSerializer()
+{
+	if(restoreState)
+		owner.current = parentNode;
+}
+
+JsonStructSerializer::JsonStructSerializer(JsonSerializeFormat& owner_, const std::string& fieldName):
+	restoreState(true),
+	owner(owner_),
+	parentNode(owner.current),
+	thisNode(&(parentNode->operator[](fieldName)))
+{
+	owner.current = thisNode;
+}
+
+JsonStructSerializer::JsonStructSerializer(JsonStructSerializer & parent, const std::string & fieldName):
+	restoreState(true),
+	owner(parent.owner),
+	parentNode(parent.thisNode),
+	thisNode(&(parentNode->operator[](fieldName)))
+{
+	owner.current = thisNode;
+}
+
+JsonStructSerializer JsonStructSerializer::enterStruct(const std::string & fieldName)
+{
+	return JsonStructSerializer(*this, fieldName);
+}
+
+JsonNode& JsonStructSerializer::get()
+{
+	return *thisNode;
+}
+
+JsonSerializeFormat * JsonStructSerializer::operator->()
+{
+	return &owner;
+}
+
+JsonSerializeFormat::LIC::LIC(const std::vector<bool> & Standard, const TDecoder & Decoder, const TEncoder & Encoder):
+	standard(Standard), decoder(Decoder), encoder(Encoder)
+{
+	any.resize(standard.size(), false);
+	all.resize(standard.size(), false);
+	none.resize(standard.size(), false);
+}
+
+
+//JsonSerializeFormat
+JsonSerializeFormat::JsonSerializeFormat(JsonNode & root_, const bool saving_):
+	saving(saving_),
+	root(&root_),
+	current(root)
+{
+
+}
+
+JsonStructSerializer JsonSerializeFormat::enterStruct(const std::string & fieldName)
+{
+	JsonStructSerializer res(*this, fieldName);
+
+	return res;
+}

+ 148 - 0
lib/serializer/JsonSerializeFormat.h

@@ -0,0 +1,148 @@
+/*
+ * JsonSerializeFormat.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
+
+class JsonNode;
+
+class JsonSerializeFormat;
+
+class JsonStructSerializer: public boost::noncopyable
+{
+public:
+	JsonStructSerializer(JsonStructSerializer && other);
+	virtual ~JsonStructSerializer();
+
+	JsonStructSerializer enterStruct(const std::string & fieldName);
+
+	JsonNode & get();
+
+	JsonSerializeFormat * operator->();
+private:
+	JsonStructSerializer(JsonSerializeFormat & owner_, const std::string & fieldName);
+	JsonStructSerializer(JsonStructSerializer & parent, const std::string & fieldName);
+
+	bool restoreState;
+	JsonSerializeFormat & owner;
+	JsonNode * parentNode;
+	JsonNode * thisNode;
+	friend class JsonSerializeFormat;
+};
+
+class JsonSerializeFormat: public boost::noncopyable
+{
+public:
+	///user-provided callback to resolve string identifier
+	///returns resolved identifier or -1 on error
+	typedef std::function<si32(const std::string &)> TDecoder;
+
+	///user-provided callback to get string identifier
+	///may assume that object index is valid
+	typedef std::function<std::string(si32)> TEncoder;
+
+	struct LIC
+	{
+		LIC(const std::vector<bool> & Standard, const TDecoder & Decoder, const TEncoder & Encoder);
+
+		const std::vector<bool> & standard;
+		const TDecoder & decoder;
+		const TEncoder & encoder;
+		std::vector<bool> all, any, none;
+	};
+
+	const bool saving;
+
+	JsonSerializeFormat() = delete;
+	virtual ~JsonSerializeFormat() = default;
+
+	JsonNode & getRoot()
+	{
+		return * root;
+	};
+
+	JsonNode & getCurrent()
+	{
+		return * current;
+	};
+
+	JsonStructSerializer enterStruct(const std::string & fieldName);
+
+	template <typename T>
+	void serializeBool(const std::string & fieldName, const T trueValue, const T falseValue, T & value)
+	{
+		bool temp = (value == trueValue);
+		serializeBool(fieldName, temp);
+		if(!saving)
+			value = temp ? trueValue : falseValue;
+	}
+
+	virtual void serializeBool(const std::string & fieldName, bool & value) = 0;
+
+	virtual void serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) = 0;
+
+	/** @brief Restrictive ("anyOf") simple serialization of Logical identifier condition, simple deserialization (allOf=anyOf)
+	 *
+	 * @param fieldName
+	 * @param decoder resolve callback, should report errors itself and do not throw
+	 * @param encoder encode callback, should report errors itself and do not throw
+	 * @param value target value, must be resized properly
+	 *
+	 */
+	virtual void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector<bool> & standard, std::vector<bool> & value) = 0;
+
+	/** @brief Complete serialization of Logical identifier condition
+	 */
+	virtual void serializeLIC(const std::string & fieldName, LIC & value) = 0;
+
+	template <typename T>
+	void serializeNumericEnum(const std::string & fieldName, const std::vector<std::string> & enumMap, const T defaultValue, T & value)
+	{
+		si32 temp = value;
+		serializeIntEnum(fieldName, enumMap, defaultValue, temp);
+		if(!saving)
+			value = temp;
+	};
+
+	template <typename T>
+	void serializeNumeric(const std::string & fieldName, T & value)
+	{
+		double temp = value;
+		serializeFloat(fieldName, temp);
+		if(!saving)
+			value = temp;
+	};
+
+	virtual void serializeString(const std::string & fieldName, std::string & value) = 0;
+
+	template <typename T>
+	void serializeId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const T & defaultValue, T & value)
+	{
+		const si32 tempDefault = defaultValue.num;
+		si32 tempValue = value.num;
+		serializeIntId(fieldName, decoder, encoder, tempDefault, tempValue);
+		if(!saving)
+			value = T(tempValue);
+	}
+
+protected:
+	JsonNode * root;
+	JsonNode * current;
+
+	JsonSerializeFormat(JsonNode & root_, const bool saving_);
+
+	virtual void serializeFloat(const std::string & fieldName, double & value) = 0;
+
+	virtual void serializeIntEnum(const std::string & fieldName, const std::vector<std::string> & enumMap, const si32 defaultValue, si32 & value) = 0;
+
+	virtual void serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) = 0;
+private:
+	friend class JsonStructSerializer;
+};
+

+ 94 - 0
lib/serializer/JsonSerializer.cpp

@@ -0,0 +1,94 @@
+/*
+ * JsonSerializer.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 "JsonSerializer.h"
+
+#include "../JsonNode.h"
+
+JsonSerializer::JsonSerializer(JsonNode & root_):
+	JsonSerializeFormat(root_, true)
+{
+
+}
+
+void JsonSerializer::serializeBool(const std::string & fieldName, bool & value)
+{
+	if(value)
+		current->operator[](fieldName).Bool() = true;
+}
+
+void JsonSerializer::serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value)
+{
+	current->operator[](fieldName).String() = value ? trueValue : falseValue;
+}
+
+void JsonSerializer::serializeFloat(const std::string & fieldName, double & value)
+{
+	if(value != 0)
+		current->operator[](fieldName).Float() = value;
+}
+
+void JsonSerializer::serializeIntEnum(const std::string & fieldName, const std::vector<std::string> & enumMap, const si32 defaultValue, si32 & value)
+{
+	if(defaultValue != value)
+		current->operator[](fieldName).String() = enumMap.at(value);
+}
+
+void JsonSerializer::serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value)
+{
+	if(defaultValue != value)
+	{
+		std::string identifier = encoder(value);
+		serializeString(fieldName, identifier);
+	}
+}
+
+void JsonSerializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector<bool> & standard, std::vector<bool> & value)
+{
+	assert(standard.size() == value.size());
+	if(standard == value)
+		return;
+
+	writeLICPart(fieldName, "anyOf", encoder, value);
+}
+
+void JsonSerializer::serializeLIC(const std::string & fieldName, LIC & value)
+{
+	if(value.any != value.standard)
+	{
+		writeLICPart(fieldName, "anyOf", value.encoder, value.any);
+	}
+
+	writeLICPart(fieldName, "allOf", value.encoder, value.all);
+	writeLICPart(fieldName, "noneOf", value.encoder, value.none);
+}
+
+void JsonSerializer::serializeString(const std::string & fieldName, std::string & value)
+{
+	if(value != "")
+		current->operator[](fieldName).String() = value;
+}
+
+void JsonSerializer::writeLICPart(const std::string& fieldName, const std::string& partName, const TEncoder& encoder, const std::vector<bool> & data)
+{
+	auto & target = current->operator[](fieldName)[partName].Vector();
+	for(si32 idx = 0; idx < data.size(); idx ++)
+	{
+		if(data[idx])
+		{
+			JsonNode val(JsonNode::DATA_STRING);
+			val.String() = encoder(idx);
+			target.push_back(std::move(val));
+		}
+	}
+}
+

+ 35 - 0
lib/serializer/JsonSerializer.h

@@ -0,0 +1,35 @@
+/*
+ * JsonSerializer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include  "JsonSerializeFormat.h"
+
+class JsonNode;
+
+class JsonSerializer: public JsonSerializeFormat
+{
+public:
+	JsonSerializer(JsonNode & root_);
+
+	void serializeBool(const std::string & fieldName, bool & value) override;
+	void serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) override;
+	void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector<bool> & standard, std::vector<bool> & value) override;
+	void serializeLIC(const std::string & fieldName, LIC & value) override;
+	void serializeString(const std::string & fieldName, std::string & value) override;
+
+protected:
+	void serializeFloat(const std::string & fieldName, double & value) override;
+	void serializeIntEnum(const std::string & fieldName, const std::vector<std::string> & enumMap, const si32 defaultValue, si32 & value) override;
+	void serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) override;
+
+private:
+	void writeLICPart(const std::string & fieldName, const std::string & partName, const TEncoder & encoder, const std::vector<bool> & data);
+};

+ 24 - 2
lib/spells/CSpellHandler.cpp

@@ -799,11 +799,12 @@ const std::string CSpellHandler::getTypeName() const
 	return "spell";
 }
 
-CSpell * CSpellHandler::loadFromJson(const JsonNode & json)
+CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string & identifier)
 {
 	using namespace SpellConfig;
 
 	CSpell * spell = new CSpell();
+	spell->identifier = identifier;
 
 	const auto type = json["type"].String();
 
@@ -1077,6 +1078,27 @@ CSpellHandler::~CSpellHandler()
 std::vector<bool> CSpellHandler::getDefaultAllowed() const
 {
 	std::vector<bool> allowedSpells;
-	allowedSpells.resize(GameConstants::SPELLS_QUANTITY, true);
+	allowedSpells.reserve(objects.size());
+
+	for(const CSpell * s : objects)
+	{
+		allowedSpells.push_back( !(s->isSpecialSpell() || s->isCreatureAbility()));
+	}
+
 	return allowedSpells;
 }
+
+si32 CSpellHandler::decodeSpell(const std::string& identifier)
+{
+	auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier);
+	if(rawId)
+		return rawId.get();
+	else
+		return -1;
+}
+
+std::string CSpellHandler::encodeSpell(const si32 index)
+{
+	return VLC->spellh->objects[index]->identifier;
+}
+

+ 8 - 2
lib/spells/CSpellHandler.h

@@ -172,7 +172,7 @@ public:
 	};
 
 	SpellID id;
-	std::string identifier; //???
+	std::string identifier;
 	std::string name;
 
 	si32 level;
@@ -360,11 +360,17 @@ public:
 
 	const std::string getTypeName() const override;
 
+	///json serialization helper
+	static si32 decodeSpell(const std::string & identifier);
+
+	///json serialization helper
+	static std::string encodeSpell(const si32 index);
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & objects ;
 	}
 
 protected:
-	CSpell * loadFromJson(const JsonNode & json) override;
+	CSpell * loadFromJson(const JsonNode & json, const std::string & identifier) override;
 };

+ 2 - 0
test/CMakeLists.txt

@@ -8,6 +8,8 @@ set(test_SRCS
 		StdInc.cpp
 		CVcmiTestConfig.cpp
 		CMapEditManagerTest.cpp
+                MapComparer.cpp
+                CMapFormatTest.cpp
 )
 
 add_executable(vcmitest ${test_SRCS})

+ 18 - 18
test/CMapEditManagerTest.cpp

@@ -12,11 +12,9 @@
 #include "StdInc.h"
 #include <boost/test/unit_test.hpp>
 
+#include "../lib/filesystem/ResourceID.h"
 #include "../lib/mapping/CMapService.h"
 #include "../lib/mapping/CMap.h"
-#include "../lib/filesystem/Filesystem.h"
-#include "../lib/filesystem/CFilesystemLoader.h"
-#include "../lib/filesystem/AdapterLoaders.h"
 #include "../lib/JsonNode.h"
 #include "../lib/mapping/CMapEditManager.h"
 #include "../lib/int3.h"
@@ -25,11 +23,12 @@
 
 BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
 {
+	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_Type start";
 	try
 	{
 		auto map = make_unique<CMap>();
-		map->width = 100;
-		map->height = 100;
+		map->width = 52;
+		map->height = 52;
 		map->initTerrain();
 		auto editManager = map->getEditManager();
 		editManager->clearTerrain();
@@ -81,6 +80,9 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
 		editManager->drawTerrain(ETerrainType::ROCK);
 		BOOST_CHECK(map->getTile(int3(5, 6, 1)).terType == ETerrainType::ROCK || map->getTile(int3(7, 8, 1)).terType == ETerrainType::ROCK);
 
+		//todo: add checks here and enable, also use smaller size
+		#if 0
+
 		// next check
 		auto map2 = make_unique<CMap>();
 		map2->width = 128;
@@ -96,29 +98,23 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
 									int3(98, 46, 1), int3(99, 46, 1)});
 		editManager2->getTerrainSelection().setSelection(selection);
 		editManager2->drawTerrain(ETerrainType::ROCK);
+		#endif // 0
 
 	}
 	catch(const std::exception & e)
 	{
-		logGlobal-> errorStream() << e.what();
+		logGlobal->errorStream() << "CMapEditManager_DrawTerrain_Type crash" << "\n" << e.what();
+		throw;
 	}
+	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_Type finish";
 }
 
 BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 {
+	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View start";
 	try
 	{
 		// Load maps and json config
-
-		#if defined(__MINGW32__)
-		const std::string TEST_DATA_DIR = "test/";
-		#else
-		const std::string TEST_DATA_DIR = ".";
-		#endif // defined
-
-
-		auto loader = new CFilesystemLoader("test/", TEST_DATA_DIR);
-		dynamic_cast<CFilesystemList*>(CResourceHandler::get())->addLoader(loader, false);
 		const auto originalMap = CMapService::loadMap("test/TerrainViewTest");
 		auto map = CMapService::loadMap("test/TerrainViewTest");
 		logGlobal->infoStream() << "Loaded test map successfully.";
@@ -149,8 +145,10 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 				const auto & posVector = posNode.Vector();
 				if(posVector.size() != 3) throw std::runtime_error("A position should consist of three values x,y,z. Continue with next position.");
 				int3 pos(posVector[0].Float(), posVector[1].Float(), posVector[2].Float());
-				logGlobal->infoStream() << boost::format("Test pattern '%s' on position x '%d', y '%d', z '%d'.") % patternStr % pos.x % pos.y % pos.z;
+#if 0
+				logGlobal->traceStream() << boost::format("Test pattern '%s' on position x '%d', y '%d', z '%d'.") % patternStr % pos.x % pos.y % pos.z;
 				CTerrainViewPatternUtils::printDebuggingInfoAboutTile(map.get(), pos);
+#endif // 0
 				const auto & originalTile = originalMap->getTile(pos);
 				editManager->getTerrainSelection().selectRange(MapRect(pos, 1, 1));
 				editManager->drawTerrain(originalTile.terType, &gen);
@@ -171,6 +169,8 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 	}
 	catch(const std::exception & e)
 	{
-		logGlobal-> errorStream() << e.what();
+		logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View crash"<< "\n" << e.what();
+		throw;
 	}
+	logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View finish";
 }

+ 97 - 0
test/CMapFormatTest.cpp

@@ -0,0 +1,97 @@
+
+/*
+ * CMapFormatTest.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 <boost/test/unit_test.hpp>
+
+#include "../lib/filesystem/CMemoryBuffer.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"
+
+#include "MapComparer.h"
+
+
+static const int TEST_RANDOM_SEED = 1337;
+
+static std::unique_ptr<CMap> initialMap;
+
+class CMapTestFixture
+{
+public:
+	CMapTestFixture()
+	{
+		CMapGenOptions opt;
+
+		opt.setHeight(CMapHeader::MAP_SIZE_MIDDLE);
+		opt.setWidth(CMapHeader::MAP_SIZE_MIDDLE);
+		opt.setHasTwoLevels(true);
+		opt.setPlayerCount(4);
+
+		opt.setPlayerTypeForStandardPlayer(PlayerColor(0), EPlayerType::HUMAN);
+		opt.setPlayerTypeForStandardPlayer(PlayerColor(1), EPlayerType::AI);
+		opt.setPlayerTypeForStandardPlayer(PlayerColor(2), EPlayerType::AI);
+		opt.setPlayerTypeForStandardPlayer(PlayerColor(3), EPlayerType::AI);
+
+		CMapGenerator gen;
+
+		initialMap = gen.generate(&opt, TEST_RANDOM_SEED);
+		initialMap->name = "Test";
+	};
+	~CMapTestFixture()
+	{
+		initialMap.reset();
+	};
+};
+
+BOOST_GLOBAL_FIXTURE(CMapTestFixture);
+
+BOOST_AUTO_TEST_CASE(CMapFormatVCMI_Simple)
+{
+	logGlobal->info("CMapFormatVCMI_Simple start");
+	BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple start");
+	CMemoryBuffer serializeBuffer;
+	{
+		CMapSaverJson saver(&serializeBuffer);
+		saver.saveMap(initialMap);
+	}
+	BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple serialized");
+	#if 1
+	{
+		auto path = VCMIDirs::get().userDataPath()/"test.vmap";
+		boost::filesystem::remove(path);
+		boost::filesystem::ofstream tmp(path, boost::filesystem::ofstream::binary);
+
+		tmp.write((const char *)serializeBuffer.getBuffer().data(),serializeBuffer.getSize());
+		tmp.flush();
+		tmp.close();
+
+		logGlobal->infoStream() << "Test map has been saved to " << path;
+	}
+	BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple saved");
+
+	#endif // 1
+
+	serializeBuffer.seek(0);
+	{
+		CMapLoaderJson loader(&serializeBuffer);
+		std::unique_ptr<CMap> serialized = loader.loadMap();
+
+		MapComparer c;
+		c(serialized, initialMap);
+	}
+
+	logGlobal->info("CMapFormatVCMI_Simple finish");
+}

+ 52 - 0
test/CMemoryBufferTest.cpp

@@ -0,0 +1,52 @@
+
+/*
+ * CMemoryBufferTest.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 <boost/test/unit_test.hpp>
+
+#include "../lib/filesystem/CMemoryBuffer.h"
+
+
+struct CMemoryBufferFixture
+{
+	CMemoryBuffer subject;
+};
+
+BOOST_FIXTURE_TEST_CASE(CMemoryBuffer_Empty, CMemoryBufferFixture)
+{
+	BOOST_REQUIRE_EQUAL(0, subject.tell());
+	BOOST_REQUIRE_EQUAL(0, subject.getSize());
+	
+	si32 dummy = 1337;
+	
+	auto ret = subject.read((ui8 *)&dummy, sizeof(si32));
+	
+	BOOST_CHECK_EQUAL(0, ret);	
+	BOOST_CHECK_EQUAL(1337, dummy);
+	BOOST_CHECK_EQUAL(0, subject.tell());
+}
+
+BOOST_FIXTURE_TEST_CASE(CMemoryBuffer_Write, CMemoryBufferFixture)
+{
+	const si32 initial = 1337;
+	
+	subject.write((const ui8 *)&initial, sizeof(si32));
+	
+	BOOST_CHECK_EQUAL(4, subject.tell());
+	subject.seek(0);
+	BOOST_CHECK_EQUAL(0, subject.tell());
+	
+	si32 current = 0;
+	auto ret = subject.read((ui8 *)&current, sizeof(si32));
+	BOOST_CHECK_EQUAL(sizeof(si32), ret);	
+	BOOST_CHECK_EQUAL(initial, current);
+	BOOST_CHECK_EQUAL(4, subject.tell());	
+}

+ 8 - 0
test/CVcmiTestConfig.cpp

@@ -18,6 +18,9 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/logging/CLogger.h"
 #include "../lib/CConfigHandler.h"
+#include "../lib/filesystem/Filesystem.h"
+#include "../lib/filesystem/CFilesystemLoader.h"
+#include "../lib/filesystem/AdapterLoaders.h"
 
 CVcmiTestConfig::CVcmiTestConfig()
 {
@@ -29,6 +32,11 @@ CVcmiTestConfig::CVcmiTestConfig()
 	logConfig.configure();
 	loadDLLClasses();
 	logGlobal->infoStream() << "Initialized global test setup.";
+
+	const std::string TEST_DATA_DIR = "test/";
+
+	auto loader = new CFilesystemLoader("test/", TEST_DATA_DIR);
+	dynamic_cast<CFilesystemList*>(CResourceHandler::get())->addLoader(loader, false);
 }
 
 CVcmiTestConfig::~CVcmiTestConfig()

+ 274 - 0
test/MapComparer.cpp

@@ -0,0 +1,274 @@
+/*
+ * MapComparer.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 <boost/test/unit_test.hpp>
+
+#include "MapComparer.h"
+
+#define VCMI_CHECK_FIELD_EQUAL_P(field) BOOST_CHECK_EQUAL(actual->field, expected->field)
+
+#define VCMI_CHECK_FIELD_EQUAL(field) BOOST_CHECK_EQUAL(actual.field, expected.field)
+
+#define VCMI_REQUIRE_FIELD_EQUAL_P(field) BOOST_REQUIRE_EQUAL(actual->field, expected->field)
+#define VCMI_REQUIRE_FIELD_EQUAL(field) BOOST_REQUIRE_EQUAL(actual.field, expected.field)
+
+template <class T>
+void checkEqual(const T & actual, const T & expected)
+{
+	BOOST_CHECK_EQUAL(actual, expected)	;
+}
+
+void checkEqual(const std::vector<bool> & actual, const std::vector<bool> & expected)
+{
+	BOOST_CHECK_EQUAL(actual.size(), expected.size());
+
+	for(auto actualIt = actual.begin(), expectedIt = expected.begin(); actualIt != actual.end() && expectedIt != expected.end(); actualIt++, expectedIt++)
+	{
+		checkEqual(*actualIt, *expectedIt);
+	}
+}
+
+template <class Element>
+void checkEqual(const std::vector<Element> & actual, const std::vector<Element> & expected)
+{
+	BOOST_CHECK_EQUAL(actual.size(), expected.size());
+
+	for(auto actualIt = actual.begin(), expectedIt = expected.begin(); actualIt != actual.end() && expectedIt != expected.end(); actualIt++, expectedIt++)
+	{
+		checkEqual(*actualIt, *expectedIt);
+	}
+}
+
+template <class Element>
+void checkEqual(const std::set<Element> & actual, const std::set<Element> & expected)
+{
+	BOOST_CHECK_EQUAL(actual.size(), expected.size());
+
+	for(auto elem : expected)
+	{
+		if(!vstd::contains(actual, elem))
+			BOOST_ERROR("Required element not found "+boost::to_string(elem));
+	}
+}
+
+void checkEqual(const SHeroName & actual, const SHeroName & expected)
+{
+	VCMI_CHECK_FIELD_EQUAL(heroId);
+	VCMI_CHECK_FIELD_EQUAL(heroName);
+}
+
+void checkEqual(const PlayerInfo & actual, const PlayerInfo & expected)
+{
+	VCMI_CHECK_FIELD_EQUAL(canHumanPlay);
+	VCMI_CHECK_FIELD_EQUAL(canComputerPlay);
+	VCMI_CHECK_FIELD_EQUAL(aiTactic);
+
+	checkEqual(actual.allowedFactions, expected.allowedFactions);
+
+	VCMI_CHECK_FIELD_EQUAL(isFactionRandom);
+	VCMI_CHECK_FIELD_EQUAL(mainCustomHeroPortrait);
+	VCMI_CHECK_FIELD_EQUAL(mainCustomHeroName);
+
+	VCMI_CHECK_FIELD_EQUAL(mainCustomHeroId);
+
+	checkEqual(actual.heroesNames, expected.heroesNames);
+
+	VCMI_CHECK_FIELD_EQUAL(hasMainTown);
+	VCMI_CHECK_FIELD_EQUAL(generateHeroAtMainTown);
+	VCMI_CHECK_FIELD_EQUAL(posOfMainTown);
+	VCMI_CHECK_FIELD_EQUAL(team);
+	VCMI_CHECK_FIELD_EQUAL(hasRandomHero);
+}
+
+void checkEqual(const EventExpression & actual,  const EventExpression & expected)
+{
+	//todo: checkEventExpression
+}
+
+void checkEqual(const TriggeredEvent & actual,  const TriggeredEvent & expected)
+{
+	VCMI_CHECK_FIELD_EQUAL(identifier);
+	VCMI_CHECK_FIELD_EQUAL(description);
+	VCMI_CHECK_FIELD_EQUAL(onFulfill);
+	VCMI_CHECK_FIELD_EQUAL(effect.type);
+	VCMI_CHECK_FIELD_EQUAL(effect.toOtherMessage);
+
+	checkEqual(actual.trigger, expected.trigger);
+}
+
+void checkEqual(const Rumor & actual, const Rumor & expected)
+{
+	VCMI_CHECK_FIELD_EQUAL(name);
+	VCMI_CHECK_FIELD_EQUAL(text);
+}
+
+void checkEqual(const DisposedHero & actual, const DisposedHero & expected)
+{
+	VCMI_CHECK_FIELD_EQUAL(heroId);
+	VCMI_CHECK_FIELD_EQUAL(portrait);
+	VCMI_CHECK_FIELD_EQUAL(name);
+	VCMI_CHECK_FIELD_EQUAL(players);
+
+}
+
+void checkEqual(const ObjectTemplate & actual, const ObjectTemplate & expected)
+{
+	VCMI_CHECK_FIELD_EQUAL(id);
+	VCMI_CHECK_FIELD_EQUAL(subid);
+	VCMI_CHECK_FIELD_EQUAL(printPriority);
+	VCMI_CHECK_FIELD_EQUAL(animationFile);
+	//VCMI_CHECK_FIELD_EQUAL(stringID);
+}
+
+void checkEqual(const TerrainTile & actual, const TerrainTile & expected)
+{
+	//fatal fail here on any error
+	VCMI_REQUIRE_FIELD_EQUAL(terType);
+	VCMI_REQUIRE_FIELD_EQUAL(terView);
+	VCMI_REQUIRE_FIELD_EQUAL(riverType);
+	VCMI_REQUIRE_FIELD_EQUAL(riverDir);
+	VCMI_REQUIRE_FIELD_EQUAL(roadType);
+	VCMI_REQUIRE_FIELD_EQUAL(roadDir);
+	VCMI_REQUIRE_FIELD_EQUAL(extTileFlags);
+
+	BOOST_REQUIRE_EQUAL(actual.blockingObjects.size(), expected.blockingObjects.size());
+	BOOST_REQUIRE_EQUAL(actual.visitableObjects.size(), expected.visitableObjects.size());
+
+	VCMI_REQUIRE_FIELD_EQUAL(visitable);
+	VCMI_REQUIRE_FIELD_EQUAL(blocked);
+
+}
+
+void MapComparer::compareHeader()
+{
+	//map size parameters are vital for further checks
+	VCMI_REQUIRE_FIELD_EQUAL_P(height);
+	VCMI_REQUIRE_FIELD_EQUAL_P(width);
+	VCMI_REQUIRE_FIELD_EQUAL_P(twoLevel);
+
+	VCMI_CHECK_FIELD_EQUAL_P(name);
+	VCMI_CHECK_FIELD_EQUAL_P(description);
+	VCMI_CHECK_FIELD_EQUAL_P(difficulty);
+	VCMI_CHECK_FIELD_EQUAL_P(levelLimit);
+
+	VCMI_CHECK_FIELD_EQUAL_P(victoryMessage);
+	VCMI_CHECK_FIELD_EQUAL_P(defeatMessage);
+	VCMI_CHECK_FIELD_EQUAL_P(victoryIconIndex);
+	VCMI_CHECK_FIELD_EQUAL_P(defeatIconIndex);
+
+	VCMI_CHECK_FIELD_EQUAL_P(howManyTeams);
+
+	checkEqual(actual->players, expected->players);
+
+	checkEqual(actual->allowedHeroes, expected->allowedHeroes);
+
+	std::vector<TriggeredEvent> actualEvents = actual->triggeredEvents;
+	std::vector<TriggeredEvent> expectedEvents = expected->triggeredEvents;
+
+	auto sortByIdentifier = [](const TriggeredEvent & lhs, const TriggeredEvent & rhs) -> bool
+	{
+		return lhs.identifier  < rhs.identifier;
+	};
+	boost::sort (actualEvents, sortByIdentifier);
+	boost::sort (expectedEvents, sortByIdentifier);
+
+	checkEqual(actualEvents, expectedEvents);
+}
+
+void MapComparer::compareOptions()
+{
+	checkEqual(actual->rumors, expected->rumors);
+	checkEqual(actual->disposedHeroes, expected->disposedHeroes);
+	//todo: compareOptions predefinedHeroes
+
+	checkEqual(actual->allowedAbilities, expected->allowedAbilities);
+	checkEqual(actual->allowedArtifact, expected->allowedArtifact);
+	checkEqual(actual->allowedSpell, expected->allowedSpell);
+	//checkEqual(actual->allowedAbilities, expected->allowedAbilities);
+
+	//todo: compareOptions  events
+}
+
+void MapComparer::compareObject(const CGObjectInstance * actual, const CGObjectInstance * expected)
+{
+	BOOST_CHECK_EQUAL(actual->instanceName, expected->instanceName);
+	BOOST_CHECK_EQUAL(typeid(actual).name(), typeid(expected).name());//todo: remove and use just comparison
+
+	std::string actualFullID = boost::to_string(boost::format("%s(%d)|%s(%d) %d") % actual->typeName % actual->ID % actual->subTypeName % actual->subID % actual->tempOwner);
+	std::string expectedFullID = boost::to_string(boost::format("%s(%d)|%s(%d) %d") % expected->typeName % expected->ID % expected->subTypeName % expected->subID % expected->tempOwner);
+
+	BOOST_CHECK_EQUAL(actualFullID, expectedFullID);
+
+	VCMI_CHECK_FIELD_EQUAL_P(pos);
+	checkEqual(actual->appearance, expected->appearance);
+}
+
+void MapComparer::compareObjects()
+{
+	BOOST_CHECK_EQUAL(actual->objects.size(), expected->objects.size());
+
+	for(size_t idx = 0; idx < expected->objects.size(); idx++)
+	{
+		auto expectedObject = expected->objects[idx];
+
+		BOOST_REQUIRE_EQUAL(idx, expectedObject->id.getNum());
+
+		{
+			auto it = expected->instanceNames.find(expectedObject->instanceName);
+
+			BOOST_REQUIRE(it != expected->instanceNames.end());
+		}
+
+		{
+			auto it = actual->instanceNames.find(expectedObject->instanceName);
+
+			BOOST_REQUIRE(it != expected->instanceNames.end());
+
+			auto actualObject = it->second;
+
+			compareObject(actualObject, expectedObject);
+		}
+	}
+}
+
+void MapComparer::compareTerrain()
+{
+	//assume map dimensions check passed
+	//todo: separate check for underground
+
+	for(int x = 0; x < expected->width; x++)
+		for(int y = 0; y < expected->height; y++)
+		{
+			int3 coord(x,y,0);
+			BOOST_TEST_CHECKPOINT(coord);
+
+			checkEqual(actual->getTile(coord), expected->getTile(coord));
+		}
+}
+
+void MapComparer::compare()
+{
+	BOOST_REQUIRE_NE((void *) actual, (void *) expected); //should not point to the same object
+	BOOST_REQUIRE_MESSAGE(actual != nullptr, "Actual map is not defined");
+	BOOST_REQUIRE_MESSAGE(expected != nullptr, "Expected map is not defined");
+
+	compareHeader();
+	compareOptions();
+	compareObjects();
+	compareTerrain();
+}
+
+void MapComparer::operator() (const std::unique_ptr<CMap>& actual, const std::unique_ptr<CMap>&  expected)
+{
+	this->actual = actual.get();
+	this->expected = expected.get();
+	compare();
+}

+ 31 - 0
test/MapComparer.h

@@ -0,0 +1,31 @@
+/*
+ * MapComparer.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "../lib/mapping/CMap.h"
+
+struct MapComparer
+{
+	const CMap * actual;
+	const CMap * expected;
+
+	void compareHeader();
+	void compareOptions();
+	void compareObject(const CGObjectInstance * actual, const CGObjectInstance * expected);
+	void compareObjects();
+	void compareTerrain();
+
+	void compare();
+
+	void operator() (const std::unique_ptr<CMap>& actual, const std::unique_ptr<CMap>& expected);
+};
+
+

+ 6 - 0
test/Test.cbp

@@ -10,6 +10,7 @@
 				<Option platforms="Windows;" />
 				<Option output="../Test" prefix_auto="1" extension_auto="1" />
 				<Option object_output="obj/Debug/x86/" />
+				<Option working_dir="../." />
 				<Option type="1" />
 				<Option compiler="gcc" />
 				<Compiler>
@@ -46,6 +47,7 @@
 			<Add option="-Wno-unused-parameter" />
 			<Add option="-Wno-overloaded-virtual" />
 			<Add option="-Wno-unused-local-typedefs" />
+			<Add directory="$(#zlib.include)" />
 			<Add directory="$(#boost.include)" />
 		</Compiler>
 		<Linker>
@@ -57,8 +59,12 @@
 			<Add directory="../" />
 		</Linker>
 		<Unit filename="CMapEditManagerTest.cpp" />
+		<Unit filename="CMapFormatTest.cpp" />
+		<Unit filename="CMemoryBufferTest.cpp" />
 		<Unit filename="CVcmiTestConfig.cpp" />
 		<Unit filename="CVcmiTestConfig.h" />
+		<Unit filename="MapComparer.cpp" />
+		<Unit filename="MapComparer.h" />
 		<Unit filename="StdInc.cpp">
 			<Option weight="0" />
 		</Unit>

+ 3 - 4
vcmi.workspace

@@ -27,13 +27,12 @@
 			<Depends filename="lib/VCMI_lib.cbp" />
 		</Project>
 		<Project filename="test/Test.cbp">
-			<Depends filename="AI/EmptyAI/EmptyAI.cbp" />
-			<Depends filename="AI/StupidAI/StupidAI.cbp" />
-			<Depends filename="AI/BattleAI/BattleAI.cbp" />
 			<Depends filename="lib/VCMI_lib.cbp" />
 			<Depends filename="client/VCMI_client.cbp" />
 			<Depends filename="server/VCMI_server.cbp" />
-			<Depends filename="AI/VCAI/VCAI.cbp" />
+			<Depends filename="AI/EmptyAI/EmptyAI.cbp" />
+			<Depends filename="AI/StupidAI/StupidAI.cbp" />
+			<Depends filename="AI/BattleAI/BattleAI.cbp" />
 		</Project>
 		<Project filename="scripting/erm/ERM.cbp">
 			<Depends filename="lib/VCMI_lib.cbp" />