Procházet zdrojové kódy

Merge remote-tracking branch 'origin/develop' into rmg_tweaks

Tomasz Zieliński před 1 rokem
rodič
revize
d078808c9f
100 změnil soubory, kde provedl 2071 přidání a 2203 odebrání
  1. 2 0
      CMakeLists.txt
  2. binární
      Mods/vcmi/Data/settingsWindow/checkBoxEmpty.png
  3. binární
      Mods/vcmi/Data/settingsWindow/lineHorizontal.png
  4. binární
      Mods/vcmi/Data/settingsWindow/lineVertical.png
  5. 1 1
      client/CMT.cpp
  6. 2 0
      client/CMakeLists.txt
  7. 0 3
      client/CServerHandler.cpp
  8. 3 3
      client/ClientCommandManager.cpp
  9. 1 1
      client/adventureMap/TurnTimerWidget.cpp
  10. 1 1
      client/battle/BattleInterfaceClasses.cpp
  11. 1 1
      client/battle/BattleWindow.cpp
  12. 2 2
      client/globalLobby/GlobalLobbyClient.cpp
  13. 1 0
      client/globalLobby/GlobalLobbyLoginWindow.cpp
  14. 1 0
      client/globalLobby/GlobalLobbyWidget.cpp
  15. 41 3
      client/gui/InterfaceObjectConfigurable.cpp
  16. 1 0
      client/gui/InterfaceObjectConfigurable.h
  17. 1 1
      client/lobby/CSelectionBase.cpp
  18. 1 1
      client/lobby/OptionsTab.cpp
  19. 1 1
      client/mainmenu/CHighScoreScreen.cpp
  20. 4 4
      client/render/CAnimation.cpp
  21. 1 1
      client/render/Graphics.cpp
  22. 5 3
      client/widgets/CComponent.cpp
  23. 1 1
      client/widgets/CComponent.h
  24. 85 0
      client/widgets/GraphicalPrimitiveCanvas.cpp
  25. 54 0
      client/widgets/GraphicalPrimitiveCanvas.h
  26. 12 49
      client/widgets/MiscWidgets.cpp
  27. 3 27
      client/widgets/MiscWidgets.h
  28. 11 0
      client/widgets/TextControls.cpp
  29. 3 0
      client/widgets/TextControls.h
  30. 2 2
      client/windows/CHeroBackpackWindow.cpp
  31. 2 2
      client/windows/CHeroOverview.cpp
  32. 143 343
      client/windows/CMessage.cpp
  33. 2 2
      client/windows/CMessage.h
  34. 1 1
      client/windows/CSpellWindow.cpp
  35. 1 1
      client/windows/CWindowObject.cpp
  36. 72 200
      client/windows/InfoWindows.cpp
  37. 15 46
      client/windows/InfoWindows.h
  38. 1 1
      client/windows/settings/SettingsMainWindow.cpp
  39. 1 1
      config/schemas/artifact.json
  40. 3 3
      config/schemas/creature.json
  41. 8 1
      config/schemas/hero.json
  42. 2 2
      config/schemas/heroClass.json
  43. 2 2
      config/schemas/objectTemplate.json
  44. 1 1
      config/schemas/obstacle.json
  45. 1 1
      config/schemas/river.json
  46. 1 1
      config/schemas/road.json
  47. 3 3
      config/schemas/spell.json
  48. 1 1
      config/schemas/terrain.json
  49. 61 0
      config/widgets/commonPrimitives.json
  50. 5 5
      config/widgets/extraOptionsTab.json
  51. 20 36
      config/widgets/mapOverview.json
  52. 5 3
      config/widgets/settings/adventureOptionsTab.json
  53. 6 5
      config/widgets/settings/battleOptionsTab.json
  54. 5 3
      config/widgets/settings/generalOptionsTab.json
  55. 3 2
      config/widgets/settings/library.json
  56. 7 6
      config/widgets/settings/settingsMainContainer.json
  57. 15 38
      config/widgets/turnOptionsTab.json
  58. 6 2
      include/vstd/RNG.h
  59. 5 2
      launcher/eu.vcmi.VCMI.metainfo.xml
  60. 2 2
      launcher/jsonutils.cpp
  61. 1 1
      launcher/updatedialog_moc.cpp
  62. 3 4
      lib/CArtHandler.cpp
  63. 1 1
      lib/CConfigHandler.cpp
  64. 3 3
      lib/CCreatureHandler.cpp
  65. 3 3
      lib/CHeroHandler.cpp
  66. 1 0
      lib/CMakeLists.txt
  67. 10 10
      lib/CRandomGenerator.cpp
  68. 1 1
      lib/CSkillHandler.cpp
  69. 16 21
      lib/CTownHandler.cpp
  70. 0 1
      lib/CTownHandler.h
  71. 6 7
      lib/bonuses/Bonus.cpp
  72. 3 3
      lib/bonuses/BonusEnum.cpp
  73. 1 1
      lib/bonuses/BonusList.cpp
  74. 1 1
      lib/bonuses/BonusParams.cpp
  75. 21 21
      lib/bonuses/Limiters.cpp
  76. 13 13
      lib/bonuses/Updaters.cpp
  77. 1 1
      lib/campaign/CampaignHandler.cpp
  78. 5 0
      lib/constants/EntityIdentifiers.cpp
  79. 2 0
      lib/constants/EntityIdentifiers.h
  80. 2 2
      lib/filesystem/Filesystem.cpp
  81. 49 11
      lib/gameState/CGameState.cpp
  82. 1 0
      lib/gameState/CGameState.h
  83. 107 111
      lib/json/JsonBonus.cpp
  84. 12 7
      lib/json/JsonBonus.h
  85. 20 0
      lib/json/JsonFormatException.h
  86. 158 82
      lib/json/JsonNode.cpp
  87. 108 110
      lib/json/JsonNode.h
  88. 258 127
      lib/json/JsonParser.cpp
  89. 23 45
      lib/json/JsonParser.h
  90. 4 4
      lib/json/JsonRandom.cpp
  91. 3 3
      lib/json/JsonRandom.h
  92. 12 151
      lib/json/JsonUtils.cpp
  93. 0 22
      lib/json/JsonUtils.h
  94. 523 552
      lib/json/JsonValidator.cpp
  95. 13 19
      lib/json/JsonValidator.h
  96. 37 37
      lib/json/JsonWriter.cpp
  97. 1 0
      lib/json/JsonWriter.h
  98. 1 1
      lib/mapObjectConstructors/CBankInstanceConstructor.cpp
  99. 5 5
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  100. 1 1
      lib/mapObjectConstructors/CRewardableConstructor.cpp

+ 2 - 0
CMakeLists.txt

@@ -799,10 +799,12 @@ if(WIN32)
 	set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "
 		CreateShortCut \\\"$DESKTOP\\\\VCMI.lnk\\\" \\\"$INSTDIR\\\\${VCMI_MAIN_EXECUTABLE}.exe\\\"
 		ExecShell '' 'netsh' 'advfirewall firewall add rule name=vcmi_server dir=in action=allow program=\\\"$INSTDIR\\\\vcmi_server.exe\\\" enable=yes profile=public,private' SW_HIDE
+		ExecShell '' 'netsh' 'advfirewall firewall add rule name=vcmi_client dir=in action=allow program=\\\"$INSTDIR\\\\vcmi_client.exe\\\" enable=yes profile=public,private' SW_HIDE
 	")
 	set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "
 		Delete \\\"$DESKTOP\\\\VCMI.lnk\\\"
 		ExecShell '' 'netsh' 'advfirewall firewall delete rule name=vcmi_server' SW_HIDE
+		ExecShell '' 'netsh' 'advfirewall firewall delete rule name=vcmi_client' SW_HIDE
 	")
 
 	# Strip MinGW CPack target if build configuration without debug info

binární
Mods/vcmi/Data/settingsWindow/checkBoxEmpty.png


binární
Mods/vcmi/Data/settingsWindow/lineHorizontal.png


binární
Mods/vcmi/Data/settingsWindow/lineVertical.png


+ 1 - 1
client/CMT.cpp

@@ -243,7 +243,7 @@ int main(int argc, char * argv[])
 
 	// Initialize logging based on settings
 	logConfig->configure();
-	logGlobal->debug("settings = %s", settings.toJsonNode().toJson());
+	logGlobal->debug("settings = %s", settings.toJsonNode().toString());
 
 	// Some basic data validation to produce better error messages in cases of incorrect install
 	auto testFile = [](std::string filename, std::string message)

+ 2 - 0
client/CMakeLists.txt

@@ -108,6 +108,7 @@ set(client_SRCS
 	widgets/CGarrisonInt.cpp
 	widgets/CreatureCostBox.cpp
 	widgets/ComboBox.cpp
+	widgets/GraphicalPrimitiveCanvas.cpp
 	widgets/Images.cpp
 	widgets/MiscWidgets.cpp
 	widgets/ObjectLists.cpp
@@ -291,6 +292,7 @@ set(client_HEADERS
 	widgets/CGarrisonInt.h
 	widgets/CreatureCostBox.h
 	widgets/ComboBox.h
+	widgets/GraphicalPrimitiveCanvas.h
 	widgets/Images.h
 	widgets/MiscWidgets.h
 	widgets/ObjectLists.h

+ 0 - 3
client/CServerHandler.cpp

@@ -832,10 +832,7 @@ void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection>
 	boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
 
 	if(getState() == EClientState::DISCONNECTING)
-	{
-		assert(0); //Should not be possible - socket must be closed at this point
 		return;
-	}
 
 	CPack * pack = logicConnection->retrievePack(message);
 	ServerHandlerCPackVisitor visitor(*this);

+ 3 - 3
client/ClientCommandManager.cpp

@@ -248,13 +248,13 @@ void ClientCommandManager::handleGetConfigCommand()
 			{
 				const JsonNode& object = nameAndObject.second;
 
-				std::string name = ModUtility::makeFullIdentifier(object.meta, contentName, nameAndObject.first);
+				std::string name = ModUtility::makeFullIdentifier(object.getModScope(), contentName, nameAndObject.first);
 
 				boost::algorithm::replace_all(name, ":", "_");
 
 				const boost::filesystem::path filePath = contentOutPath / (name + ".json");
 				std::ofstream file(filePath.c_str());
-				file << object.toJson();
+				file << object.toString();
 			}
 		}
 	}
@@ -358,7 +358,7 @@ void ClientCommandManager::handleBonusesCommand(std::istringstream & singleWordB
 	auto format = [outputFormat](const BonusList & b) -> std::string
 	{
 		if(outputFormat == "json")
-			return b.toJsonNode().toJson(true);
+			return b.toJsonNode().toCompactString();
 
 		std::ostringstream ss;
 		ss << b;

+ 1 - 1
client/adventureMap/TurnTimerWidget.cpp

@@ -18,7 +18,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../render/Graphics.h"
 #include "../widgets/Images.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/TextControls.h"
 
 #include "../../CCallback.h"

+ 1 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -34,7 +34,7 @@
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../windows/CMessage.h"
 #include "../windows/CCreatureWindow.h"
 #include "../windows/CSpellWindow.h"

+ 1 - 1
client/battle/BattleWindow.cpp

@@ -813,7 +813,7 @@ void BattleWindow::showAll(Canvas & to)
 	CIntObject::showAll(to);
 
 	if (GH.screenDimensions().x != 800 || GH.screenDimensions().y !=600)
-		CMessage::drawBorder(owner.curInt->playerID, to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+		CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 
 void BattleWindow::show(Canvas & to)

+ 2 - 2
client/globalLobby/GlobalLobbyClient.cpp

@@ -273,7 +273,7 @@ void GlobalLobbyClient::onDisconnected(const std::shared_ptr<INetworkConnection>
 
 void GlobalLobbyClient::sendMessage(const JsonNode & data)
 {
-	networkConnection->sendPacket(data.toBytes(true));
+	networkConnection->sendPacket(data.toBytes());
 }
 
 void GlobalLobbyClient::sendOpenPublicRoom()
@@ -362,5 +362,5 @@ void GlobalLobbyClient::sendProxyConnectionLogin(const NetworkConnectionPtr & ne
 	toSend["accountCookie"] = settings["lobby"]["accountCookie"];
 	toSend["gameRoomID"] = settings["lobby"]["roomID"];
 
-	netConnection->sendPacket(toSend.toBytes(true));
+	netConnection->sendPacket(toSend.toBytes());
 }

+ 1 - 0
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -20,6 +20,7 @@
 #include "../gui/WindowHandler.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/TextControls.h"
 

+ 1 - 0
client/globalLobby/GlobalLobbyWidget.cpp

@@ -18,6 +18,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../widgets/Buttons.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/TextControls.h"

+ 41 - 3
client/gui/InterfaceObjectConfigurable.cpp

@@ -22,7 +22,7 @@
 #include "../widgets/CComponent.h"
 #include "../widgets/ComboBox.h"
 #include "../widgets/Buttons.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
@@ -57,6 +57,7 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset)
 	REGISTER_BUILDER("layout", &InterfaceObjectConfigurable::buildLayout);
 	REGISTER_BUILDER("comboBox", &InterfaceObjectConfigurable::buildComboBox);
 	REGISTER_BUILDER("textInput", &InterfaceObjectConfigurable::buildTextInput);
+	REGISTER_BUILDER("graphicalPrimitive", &InterfaceObjectConfigurable::buildGraphicalPrimitive);
 	REGISTER_BUILDER("transparentFilledRectangle", &InterfaceObjectConfigurable::buildTransparentFilledRectangle);
 	REGISTER_BUILDER("textBox", &InterfaceObjectConfigurable::buildTextBox);
 }
@@ -113,8 +114,20 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
 	{
 		if (!config["library"].isNull())
 		{
-			const JsonNode library(JsonPath::fromJson(config["library"]));
-			loadCustomBuilders(library);
+			if (config["library"].isString())
+			{
+				const JsonNode library(JsonPath::fromJson(config["library"]));
+				loadCustomBuilders(library);
+			}
+
+			if (config["library"].isVector())
+			{
+				for (auto const & entry : config["library"].Vector())
+				{
+					const JsonNode library(JsonPath::fromJson(entry));
+					loadCustomBuilders(library);
+				}
+			}
 		}
 
 		loadCustomBuilders(config["customTypes"]);
@@ -707,6 +720,31 @@ std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const
 	return anim;
 }
 
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildGraphicalPrimitive(const JsonNode & config) const
+{
+	logGlobal->debug("Building widget GraphicalPrimitiveCanvas");
+
+	auto rect = readRect(config["rect"]);
+	auto widget = std::make_shared<GraphicalPrimitiveCanvas>(rect);
+
+	for (auto const & entry : config["primitives"].Vector())
+	{
+		auto color = readColor(entry["color"]);
+		auto typeString = entry["type"].String();
+		auto pointA = readPosition(entry["a"]);
+		auto pointB = readPosition(entry["b"]);
+
+		if (typeString == "line")
+			widget->addLine(pointA, pointB, color);
+		if (typeString == "filledBox")
+			widget->addBox(pointA, pointB, color);
+		if (typeString == "rectangle")
+			widget->addRectangle(pointA, pointB, color);
+	}
+
+	return widget;
+}
+
 std::shared_ptr<TransparentFilledRectangle> InterfaceObjectConfigurable::buildTransparentFilledRectangle(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget TransparentFilledRectangle");

+ 1 - 0
client/gui/InterfaceObjectConfigurable.h

@@ -108,6 +108,7 @@ protected:
 	std::shared_ptr<ComboBox> buildComboBox(const JsonNode &);
 	std::shared_ptr<CTextInput> buildTextInput(const JsonNode &) const;
 	std::shared_ptr<TransparentFilledRectangle> buildTransparentFilledRectangle(const JsonNode & config) const;
+	std::shared_ptr<CIntObject> buildGraphicalPrimitive(const JsonNode & config) const;
 	std::shared_ptr<CTextBox> buildTextBox(const JsonNode & config) const;
 		
 	//composite widgets

+ 1 - 1
client/lobby/CSelectionBase.cpp

@@ -29,7 +29,7 @@
 #include "../mainmenu/CMainMenu.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/CComponent.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"

+ 1 - 1
client/lobby/OptionsTab.cpp

@@ -374,7 +374,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
 		if(!elem.empty())
 			components.push_back(std::make_shared<CComponent>(ComponentType::CREATURE, elem.front(), std::nullopt, CComponent::tiny));
 	}
-	boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140));
+	boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140), 20, 10, 22, 4);
 }
 
 void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()

+ 1 - 1
client/mainmenu/CHighScoreScreen.cpp

@@ -17,7 +17,7 @@
 #include "../widgets/TextControls.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../windows/InfoWindows.h"
 #include "../render/Canvas.h"
 

+ 4 - 4
client/render/CAnimation.cpp

@@ -102,7 +102,7 @@ void CAnimation::initFromJson(const JsonNode & config)
 	std::string basepath;
 	basepath = config["basepath"].String();
 
-	JsonNode base(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode base;
 	base["margins"] = config["margins"];
 	base["width"] = config["width"];
 	base["height"] = config["height"];
@@ -114,7 +114,7 @@ void CAnimation::initFromJson(const JsonNode & config)
 
 		for(const JsonNode & frame : group["frames"].Vector())
 		{
-			JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+			JsonNode toAdd;
 			JsonUtils::inherit(toAdd, base);
 			toAdd["file"].String() = basepath + frame.String();
 			source[groupID].push_back(toAdd);
@@ -129,7 +129,7 @@ void CAnimation::initFromJson(const JsonNode & config)
 		if (source[group].size() <= frame)
 			source[group].resize(frame+1);
 
-		JsonNode toAdd(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode toAdd;
 		JsonUtils::inherit(toAdd, base);
 		toAdd["file"].String() = basepath + node["file"].String();
 		source[group][frame] = toAdd;
@@ -191,7 +191,7 @@ void CAnimation::init()
 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
 		stream->read(textData.get(), stream->getSize());
 
-		const JsonNode config((char*)textData.get(), stream->getSize());
+		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize());
 
 		initFromJson(config);
 	}

+ 1 - 1
client/render/Graphics.cpp

@@ -107,7 +107,7 @@ void Graphics::initializeBattleGraphics()
 		if(!CResourceHandler::get(mod)->existsResource(ResourcePath("config/battles_graphics.json")))
 			continue;
 			
-		const JsonNode config(mod, JsonPath::builtin("config/battles_graphics.json"));
+		const JsonNode config(JsonPath::builtin("config/battles_graphics.json"), mod);
 
 		//initialization of AC->def name mapping
 		if(!config["ac_mapping"].isNull())

+ 5 - 3
client/widgets/CComponent.cpp

@@ -80,13 +80,14 @@ void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optiona
 
 	pos.h += 4; //distance between text and image
 
-	auto max = 80;
+	// WARNING: too low values will lead to bad line-breaks in CPlayerOptionTooltipBox - check right-click on starting town in pregame
+	int max = 80;
 	if (size < large)
 		max = 72;
 	if (size < medium)
-		max = 40;
+		max = 60;
 	if (size < small)
-		max = 30;
+		max = 55;
 
 	if(Type == ComponentType::RESOURCE && !ValText.empty())
 		max = 80;
@@ -426,6 +427,7 @@ void CComponentBox::placeComponents(bool selectable)
 	for(auto & comp : components)
 	{
 		addChild(comp.get());
+		comp->recActions = defActions; //FIXME: for some reason, received component might have recActions set to 0
 		comp->moveTo(Point(pos.x, pos.y));
 	}
 

+ 1 - 1
client/widgets/CComponent.h

@@ -92,7 +92,7 @@ class CComponentBox : public CIntObject
 	std::shared_ptr<CSelectableComponent> selected;
 	std::function<void(int newID)> onSelect;
 
-	static constexpr int defaultBetweenImagesMin = 20;
+	static constexpr int defaultBetweenImagesMin = 42;
 	static constexpr int defaultBetweenSubtitlesMin = 10;
 	static constexpr int defaultBetweenRows = 22;
 	static constexpr int defaultComponentsInRow = 4;

+ 85 - 0
client/widgets/GraphicalPrimitiveCanvas.cpp

@@ -0,0 +1,85 @@
+/*
+ * GraphicalPrimitiveCanvas.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 "GraphicalPrimitiveCanvas.h"
+
+#include "../render/Canvas.h"
+
+GraphicalPrimitiveCanvas::GraphicalPrimitiveCanvas(Rect dimensions)
+{
+	pos = dimensions + pos.topLeft();
+}
+
+void GraphicalPrimitiveCanvas::showAll(Canvas & to)
+{
+	auto const & translatePoint = [this](const Point & input){
+		int x = input.x < 0 ? pos.w + input.x : input.x;
+		int y = input.y < 0 ? pos.h + input.y : input.y;
+
+		return Point(x,y);
+	};
+
+	for (auto const & entry : primitives)
+	{
+		switch (entry.type)
+		{
+			case PrimitiveType::LINE:
+			{
+				to.drawLine(pos.topLeft() + translatePoint(entry.a), pos.topLeft() + translatePoint(entry.b), entry.color, entry.color);
+				break;
+			}
+			case PrimitiveType::FILLED_BOX:
+			{
+				to.drawColorBlended(Rect(pos.topLeft() + translatePoint(entry.a), translatePoint(entry.b)), entry.color);
+				break;
+			}
+			case PrimitiveType::RECTANGLE:
+			{
+				to.drawBorder(Rect(pos.topLeft() + translatePoint(entry.a), translatePoint(entry.b)), entry.color);
+				break;
+			}
+		}
+	}
+}
+
+void GraphicalPrimitiveCanvas::addLine(const Point & from, const Point & to, const ColorRGBA & color)
+{
+	primitives.push_back({color, from, to, PrimitiveType::LINE});
+}
+
+void GraphicalPrimitiveCanvas::addBox(const Point & topLeft, const Point & size, const ColorRGBA & color)
+{
+	primitives.push_back({color, topLeft, size, PrimitiveType::FILLED_BOX});
+}
+
+void GraphicalPrimitiveCanvas::addRectangle(const Point & topLeft, const Point & size, const ColorRGBA & color)
+{
+	primitives.push_back({color, topLeft, size, PrimitiveType::RECTANGLE});
+}
+
+TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color) :
+	GraphicalPrimitiveCanvas(position)
+{
+	addBox(Point(0,0), Point(-1, -1), color);
+}
+
+TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width) :
+	GraphicalPrimitiveCanvas(position)
+{
+	addBox(Point(0,0), Point(-1, -1), color);
+	for (int i = 0; i < width; ++i)
+		addRectangle(Point(i,i), Point(-1-i*2, -1-i*2), colorLine);
+}
+
+SimpleLine::SimpleLine(Point pos1, Point pos2, ColorRGBA color) :
+	GraphicalPrimitiveCanvas(Rect(pos1, pos2 - pos1))
+{
+	addLine(Point(0,0), Point(-1, -1), color);
+}

+ 54 - 0
client/widgets/GraphicalPrimitiveCanvas.h

@@ -0,0 +1,54 @@
+/*
+ * GraphicalPrimitiveCanvas.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 "../gui/CIntObject.h"
+
+class GraphicalPrimitiveCanvas : public CIntObject
+{
+	enum class PrimitiveType
+	{
+		LINE,
+		RECTANGLE,
+		FILLED_BOX
+	};
+
+	struct PrimitiveEntry
+	{
+		ColorRGBA color;
+		Point a;
+		Point b;
+		PrimitiveType type;
+	};
+
+	std::vector<PrimitiveEntry> primitives;
+
+	void showAll(Canvas & to) override;
+
+public:
+	GraphicalPrimitiveCanvas(Rect position);
+
+	void addLine(const Point & from, const Point & to, const ColorRGBA & color);
+	void addBox(const Point & topLeft, const Point & size, const ColorRGBA & color);
+	void addRectangle(const Point & topLeft, const Point & size, const ColorRGBA & color);
+};
+
+class TransparentFilledRectangle : public GraphicalPrimitiveCanvas
+{
+public:
+	TransparentFilledRectangle(Rect position, ColorRGBA color);
+	TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width = 1);
+};
+
+class SimpleLine : public GraphicalPrimitiveCanvas
+{
+public:
+	SimpleLine(Point pos1, Point pos2, ColorRGBA color);
+};

+ 12 - 49
client/widgets/MiscWidgets.cpp

@@ -21,8 +21,9 @@
 #include "../gui/WindowHandler.h"
 #include "../eventsSDL/InputHandler.h"
 #include "../windows/CTradeWindow.h"
-#include "../widgets/TextControls.h"
 #include "../widgets/CGarrisonInt.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
+#include "../widgets/TextControls.h"
 #include "../windows/CCastleInterface.h"
 #include "../windows/InfoWindows.h"
 #include "../render/Canvas.h"
@@ -663,54 +664,13 @@ void CCreaturePic::setAmount(int newAmount)
 		amount->setText("");
 }
 
-TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color) :
-	color(color), colorLine(ColorRGBA()), drawLine(false), lineWidth(0)
-{
-	pos = position + pos.topLeft();
-}
-
-TransparentFilledRectangle::TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width) :
-	color(color), colorLine(colorLine), drawLine(true), lineWidth(width)
-{
-	pos = position + pos.topLeft();
-}
-
-void TransparentFilledRectangle::setDrawBorder(bool on)
-{
-	drawLine = on;
-}
-
-bool TransparentFilledRectangle::getDrawBorder()
-{
-	return drawLine;
-}
-
-void TransparentFilledRectangle::setBorderWidth(int width)
-{
-	lineWidth = width;
-}
-
-void TransparentFilledRectangle::showAll(Canvas & to) 
-{
-	to.drawColorBlended(pos, color);
-	if(drawLine)
-		to.drawBorder(pos, colorLine, lineWidth);
-}
-
-SimpleLine::SimpleLine(Point pos1, Point pos2, ColorRGBA color) :
-	pos1(pos1), pos2(pos2), color(color)
-{}
-
-void SimpleLine::showAll(Canvas & to) 
-{
-	to.drawLine(pos1 + pos.topLeft(), pos2 + pos.topLeft(), color, color);
-}
-
 SelectableSlot::SelectableSlot(Rect area, Point oversize, const int width)
 	: LRClickableAreaWTextComp(area)
+	, selected(false)
 {
-	selection = std::make_unique<TransparentFilledRectangle>(
-		Rect(area.topLeft() - oversize, area.dimensions() + oversize * 2), Colors::TRANSPARENCY, Colors::YELLOW, width);
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	selection = std::make_shared<TransparentFilledRectangle>( Rect(-oversize, area.dimensions() + oversize * 2), Colors::TRANSPARENCY, Colors::YELLOW, width);
 	selectSlot(false);
 }
 
@@ -726,15 +686,18 @@ SelectableSlot::SelectableSlot(Rect area, const int width)
 
 void SelectableSlot::selectSlot(bool on)
 {
-	selection->setDrawBorder(on);
+	selection->setEnabled(on);
+	selected = on;
 }
 
 bool SelectableSlot::isSelected() const
 {
-	return selection->getDrawBorder();
+	return selected;
 }
 
 void SelectableSlot::setSelectionWidth(int width)
 {
-	selection->setBorderWidth(width);
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	selection = std::make_shared<TransparentFilledRectangle>( selection->pos - pos.topLeft(), Colors::TRANSPARENCY, Colors::YELLOW, width);
+	selectSlot(selected);
 }

+ 3 - 27
client/widgets/MiscWidgets.h

@@ -33,6 +33,7 @@ class CCreatureAnim;
 class CComponent;
 class CAnimImage;
 class LRClickableArea;
+class TransparentFilledRectangle;
 
 /// Shows a text by moving the mouse cursor over the object
 class CHoverableArea: public virtual CIntObject
@@ -248,35 +249,10 @@ public:
 	MoraleLuckBox(bool Morale, const Rect &r, bool Small=false);
 };
 
-class TransparentFilledRectangle : public CIntObject
-{
-	ColorRGBA color;
-	ColorRGBA colorLine;
-	bool drawLine;
-	int lineWidth;
-
-public:
-    TransparentFilledRectangle(Rect position, ColorRGBA color);
-    TransparentFilledRectangle(Rect position, ColorRGBA color, ColorRGBA colorLine, int width = 1);
-	void setDrawBorder(bool on);
-	bool getDrawBorder();
-	void setBorderWidth(int width);
-    void showAll(Canvas & to) override;
-};
-
-class SimpleLine : public CIntObject
-{
-	Point pos1;
-	Point pos2;
-	ColorRGBA color;
-public:
-    SimpleLine(Point pos1, Point pos2, ColorRGBA color);
-    void showAll(Canvas & to) override;
-};
-
 class SelectableSlot : public LRClickableAreaWTextComp
 {
-	std::unique_ptr<TransparentFilledRectangle> selection;
+	std::shared_ptr<TransparentFilledRectangle> selection;
+	bool selected;
 
 public:
 	SelectableSlot(Rect area, Point oversize, const int width);

+ 11 - 0
client/widgets/TextControls.cpp

@@ -369,6 +369,17 @@ void CTextBox::sliderMoved(int to)
 	label->scrollTextTo(to);
 }
 
+void CTextBox::trimToFit()
+{
+	if (slider)
+		return;
+
+	pos.w = label->textSize.x;
+	pos.h = label->textSize.y;
+	label->pos.w = label->textSize.x;
+	label->pos.h = label->textSize.y;
+}
+
 void CTextBox::resize(Point newSize)
 {
 	pos.w = newSize.x;

+ 3 - 0
client/widgets/TextControls.h

@@ -116,6 +116,9 @@ public:
 	CTextBox(std::string Text, const Rect & rect, int SliderStyle, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::TOPLEFT, const ColorRGBA & Color = Colors::WHITE);
 
 	void resize(Point newSize);
+	/// Resizes text box to minimal size needed to fit current text
+	/// No effect if text is too large to fit and requires slider
+	void trimToFit();
 	void setText(const std::string & Txt);
 	void sliderMoved(int to);
 };

+ 2 - 2
client/windows/CHeroBackpackWindow.cpp

@@ -45,7 +45,7 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero)
 void CHeroBackpackWindow::showAll(Canvas & to)
 {
 	CIntObject::showAll(to);
-	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 
 CHeroQuickBackpackWindow::CHeroQuickBackpackWindow(const CGHeroInstance * hero, ArtifactPosition targetSlot)
@@ -87,6 +87,6 @@ void CHeroQuickBackpackWindow::showAll(Canvas & to)
 		close();
 		return;
 	}
-	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to.getInternalSurface(), pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15);
+	CMessage::drawBorder(PlayerColor(LOCPLINT->playerID), to, pos.w + 28, pos.h + 29, pos.x - 14, pos.y - 15);
 	CIntObject::showAll(to);
 }

+ 2 - 2
client/windows/CHeroOverview.cpp

@@ -20,7 +20,7 @@
 #include "../widgets/CComponent.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 
 #include "../../lib/GameSettings.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -234,4 +234,4 @@ void CHeroOverview::genControls()
         labelSpellsNames.push_back(std::make_shared<CLabel>(302 + (292 / 2) + 3 * borderOffset + 32 + borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 3, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->spellh)[spell]->getNameTranslated()));
         i++;
     }
-}
+}

+ 143 - 343
client/windows/CMessage.cpp

@@ -11,79 +11,40 @@
 #include "StdInc.h"
 #include "CMessage.h"
 
-#include "../CGameInfo.h"
-#include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/TextOperations.h"
 
-#include "../windows/InfoWindows.h"
-#include "../widgets/Buttons.h"
-#include "../widgets/CComponent.h"
-#include "../widgets/Slider.h"
-#include "../widgets/TextControls.h"
 #include "../gui/CGuiHandler.h"
 #include "../render/CAnimation.h"
-#include "../render/IImage.h"
-#include "../render/IRenderHandler.h"
 #include "../render/Canvas.h"
 #include "../render/Graphics.h"
 #include "../render/IFont.h"
-#include "../renderSDL/SDL_Extensions.h"
-
-#include <SDL_surface.h>
-
-const int BETWEEN_COMPS_ROWS = 10;
-const int BEFORE_COMPONENTS = 30;
-const int BETWEEN_COMPS = 30;
-const int SIDE_MARGIN = 30;
-
-template <typename T, typename U> std::pair<T,U> max(const std::pair<T,U> &x, const std::pair<T,U> &y)
-{
-	std::pair<T,U> ret;
-	ret.first = std::max(x.first,y.first);
-	ret.second = std::max(x.second,y.second);
-	return ret;
-}
-
-//One image component + subtitles below it
-class ComponentResolved : public CIntObject
-{
-public:
-	std::shared_ptr<CComponent> comp;
-
-	//blit component with image centered at this position
-	void showAll(Canvas & to) override;
-
-	//ComponentResolved();
-	ComponentResolved(std::shared_ptr<CComponent> Comp);
-	~ComponentResolved();
-};
-// Full set of components for blitting on dialog box
-struct ComponentsToBlit
-{
-	std::vector< std::vector<std::shared_ptr<ComponentResolved>>> comps;
-	int w, h;
-
-	void blitCompsOnSur(bool blitOr, int inter, int &curh, SDL_Surface *ret);
-	ComponentsToBlit(std::vector<std::shared_ptr<CComponent>> & SComps, int maxw, bool blitOr);
-	~ComponentsToBlit();
-};
+#include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Images.h"
+#include "../widgets/Slider.h"
+#include "../widgets/TextControls.h"
+#include "../windows/InfoWindows.h"
 
-namespace
-{
-	std::array<std::shared_ptr<CAnimation>, PlayerColor::PLAYER_LIMIT_I> dialogBorders;
-	std::array<std::vector<std::shared_ptr<IImage>>, PlayerColor::PLAYER_LIMIT_I> piecesOfBox;
+constexpr int RIGHT_CLICK_POPUP_MIN_SIZE = 100;
+constexpr int SIDE_MARGIN = 11;
+constexpr int TOP_MARGIN = 20;
+constexpr int BOTTOM_MARGIN = 16;
+constexpr int INTERVAL_BETWEEN_BUTTONS = 18;
+constexpr int INTERVAL_BETWEEN_TEXT_AND_BUTTONS = 24;
 
-	std::shared_ptr<IImage> background;//todo: should be CFilledTexture
-}
+static std::array<std::shared_ptr<CAnimation>, PlayerColor::PLAYER_LIMIT_I> dialogBorders;
+static std::array<std::vector<std::shared_ptr<IImage>>, PlayerColor::PLAYER_LIMIT_I> piecesOfBox;
 
 void CMessage::init()
 {
-	for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
+	for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++)
 	{
 		dialogBorders[i] = GH.renderHandler().loadAnimation(AnimationPath::builtin("DIALGBOX"));
 		dialogBorders[i]->preload();
 
-		for(int j=0; j < dialogBorders[i]->size(0); j++)
+		for(int j = 0; j < dialogBorders[i]->size(0); j++)
 		{
 			auto image = dialogBorders[i]->getImage(j, 0);
 			//assume blue color initially
@@ -92,8 +53,6 @@ void CMessage::init()
 			piecesOfBox[i].push_back(image);
 		}
 	}
-
-	background = GH.renderHandler().loadImage(ImagePath::builtin("DIBOXBCK.BMP"), EImageBlitMode::OPAQUE);
 }
 
 void CMessage::dispose()
@@ -102,61 +61,45 @@ void CMessage::dispose()
 		item.reset();
 }
 
-SDL_Surface * CMessage::drawDialogBox(int w, int h, PlayerColor playerColor)
-{
-	//prepare surface
-	SDL_Surface * ret = CSDL_Ext::newSurface(w,h);
-	for (int i=0; i<w; i+=background->width())//background
-	{
-		for (int j=0; j<h; j+=background->height())
-		{
-			background->draw(ret, i, j);
-		}
-	}
-
-	drawBorder(playerColor, ret, w, h);
-	return ret;
-}
-
-std::vector<std::string> CMessage::breakText( std::string text, size_t maxLineWidth, EFonts font )
+std::vector<std::string> CMessage::breakText(std::string text, size_t maxLineWidth, EFonts font)
 {
 	assert(maxLineWidth != 0);
-	if (maxLineWidth == 0)
-		return { text };
+	if(maxLineWidth == 0)
+		return {text};
 
 	std::vector<std::string> ret;
 
-	boost::algorithm::trim_right_if(text,boost::algorithm::is_any_of(std::string(" ")));
+	boost::algorithm::trim_right_if(text, boost::algorithm::is_any_of(std::string(" ")));
 
 	// each iteration generates one output line
-	while (text.length())
+	while(text.length())
 	{
-		ui32 lineWidth = 0;    //in characters or given char metric
-		ui32 wordBreak = -1;    //last position for line break (last space character)
-		ui32 currPos = 0;       //current position in text
-		bool opened = false;    //set to true when opening brace is found
-		std::string color = "";    //color found
+		ui32 lineWidth = 0; //in characters or given char metric
+		ui32 wordBreak = -1; //last position for line break (last space character)
+		ui32 currPos = 0; //current position in text
+		bool opened = false; //set to true when opening brace is found
+		std::string color; //color found
 
 		size_t symbolSize = 0; // width of character, in bytes
 		size_t glyphWidth = 0; // width of printable glyph, pixels
 
 		// loops till line is full or end of text reached
-		while(currPos < text.length()  &&  text[currPos] != 0x0a  &&  lineWidth < maxLineWidth)
+		while(currPos < text.length() && text[currPos] != 0x0a && lineWidth < maxLineWidth)
 		{
 			symbolSize = TextOperations::getUnicodeCharacterSize(text[currPos]);
 			glyphWidth = graphics->fonts[font]->getGlyphWidth(text.data() + currPos);
 
 			// candidate for line break
-			if (ui8(text[currPos]) <= ui8(' '))
+			if(ui8(text[currPos]) <= ui8(' '))
 				wordBreak = currPos;
 
 			/* We don't count braces in string length. */
-			if (text[currPos] == '{')
+			if(text[currPos] == '{')
 			{
-				opened=true;
+				opened = true;
 
 				std::smatch match;
-   				std::regex expr("^\\{(.*?)\\|");
+				std::regex expr("^\\{(.*?)\\|");
 				std::string tmp = text.substr(currPos);
 				if(std::regex_search(tmp, match, expr))
 				{
@@ -168,23 +111,23 @@ std::vector<std::string> CMessage::breakText( std::string text, size_t maxLineWi
 					}
 				}
 			}
-			else if (text[currPos]=='}')
+			else if(text[currPos] == '}')
 			{
-				opened=false;
+				opened = false;
 				color = "";
 			}
 			else
-				lineWidth += (ui32)glyphWidth;
-			currPos += (ui32)symbolSize;
+				lineWidth += glyphWidth;
+			currPos += symbolSize;
 		}
 
 		// long line, create line break
-		if (currPos < text.length()  &&  (text[currPos] != 0x0a))
+		if(currPos < text.length() && (text[currPos] != 0x0a))
 		{
-			if (wordBreak != ui32(-1))
+			if(wordBreak != ui32(-1))
 				currPos = wordBreak;
 			else
-				currPos -= (ui32)symbolSize;
+				currPos -= symbolSize;
 		}
 
 		//non-blank line
@@ -192,7 +135,7 @@ std::vector<std::string> CMessage::breakText( std::string text, size_t maxLineWi
 		{
 			ret.push_back(text.substr(0, currPos));
 
-			if (opened)
+			if(opened)
 				/* Close the brace for the current line. */
 				ret.back() += '}';
 
@@ -203,7 +146,7 @@ std::vector<std::string> CMessage::breakText( std::string text, size_t maxLineWi
 			ret.push_back(""); //add empty string, no extra actions needed
 		}
 
-		if (text.length() != 0 && text[0] == 0x0a)
+		if(text.length() != 0 && text[0] == 0x0a)
 		{
 			/* Remove LF */
 			text.erase(0, 1);
@@ -212,19 +155,19 @@ std::vector<std::string> CMessage::breakText( std::string text, size_t maxLineWi
 		{
 			// trim only if line does not starts with LF
 			// FIXME: necessary? All lines will be trimmed before returning anyway
-			boost::algorithm::trim_left_if(text,boost::algorithm::is_any_of(std::string(" ")));
+			boost::algorithm::trim_left_if(text, boost::algorithm::is_any_of(std::string(" ")));
 		}
 
-		if (opened)
+		if(opened)
 		{
 			/* Add an opening brace for the next line. */
-			if (text.length() != 0)
+			if(text.length() != 0)
 				text.insert(0, "{" + color);
 		}
 	}
 
 	/* Trim whitespaces of every line. */
-	for (auto & elem : ret)
+	for(auto & elem : ret)
 		boost::algorithm::trim(elem);
 
 	return ret;
@@ -244,118 +187,127 @@ std::string CMessage::guessHeader(const std::string & msg)
 int CMessage::guessHeight(const std::string & txt, int width, EFonts font)
 {
 	const auto f = graphics->fonts[font];
-	auto lines = CMessage::breakText(txt, width, font);
-	int lineHeight = static_cast<int>(f->getLineHeight());
-	return lineHeight * (int)lines.size();
+	const auto lines = CMessage::breakText(txt, width, font);
+	size_t lineHeight = f->getLineHeight();
+	return lineHeight * lines.size();
 }
 
 int CMessage::getEstimatedComponentHeight(int numComps)
 {
-	if (numComps > 8) //Bigger than 8 components - return invalid value
+	if(numComps > 8) //Bigger than 8 components - return invalid value
 		return std::numeric_limits<int>::max();
-	else if (numComps > 2)
+	if(numComps > 2)
 		return 160; // 32px * 1 row + 20 to offset
-	else if (numComps)
+	if(numComps > 0)
 		return 118; // 118 px to offset
 	return 0;
 }
 
 void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor player)
 {
-	bool blitOr = false;
-	if(dynamic_cast<CSelWindow*>(ret)) //it's selection window, so we'll blit "or" between components
-		blitOr = true;
-
-	const int sizes[][2] = {{400, 125}, {500, 150}, {600, 200}, {480, 400}};
+	// possible sizes of text boxes section of window
+	// game should pick smallest one that can fit text without slider
+	// or, if not possible - pick last one and use slider
+	constexpr std::array textAreaSizes = {
+		// FIXME: this size should only be used for single-line texts: Point(206, 72),
+		Point(270, 72),
+		Point(270, 136),
+		Point(270, 200),
+		Point(400, 136),
+		Point(400, 200),
+		Point(590, 200)
+	};
 
 	assert(ret && ret->text);
-	for(int i = 0;
-		i < std::size(sizes)
-			&& sizes[i][0] < GH.screenDimensions().x - 150
-			&& sizes[i][1] < GH.screenDimensions().y - 150
-			&& ret->text->slider;
-		i++)
-	{
-		ret->text->resize(Point(sizes[i][0], sizes[i][1]));
-	}
 
-	if(ret->text->slider)
-	{
-		ret->text->slider->addUsedEvents(CIntObject::WHEEL | CIntObject::KEYBOARD);
-	}
-	else
+	// STEP 1: DETERMINE SIZE OF ALL ELEMENTS
+
+	for(const auto & area : textAreaSizes)
 	{
-		ret->text->resize(ret->text->label->textSize + Point(10, 10));
+		ret->text->resize(area);
+		if(!ret->text->slider)
+			break; // suitable size found, use it
 	}
 
-	std::pair<int,int> winSize(ret->text->pos.w, ret->text->pos.h); //start with text size
+	int textHeight = ret->text->pos.h;
 
-	ComponentsToBlit comps(ret->components,500, blitOr);
-	if (ret->components.size())
-		winSize.second += 10 + comps.h; //space to first component
+	if(ret->text->slider)
+		ret->text->slider->addUsedEvents(CIntObject::WHEEL | CIntObject::KEYBOARD);
 
-	int bw = 0;
-	if (ret->buttons.size())
+	int buttonsWidth = 0;
+	int buttonsHeight = 0;
+	if(!ret->buttons.empty())
 	{
-		int bh = 0;
 		// Compute total width of buttons
-		bw = 20*((int)ret->buttons.size()-1); // space between all buttons
-		for(auto & elem : ret->buttons) //and add buttons width
+		buttonsWidth = INTERVAL_BETWEEN_BUTTONS * (ret->buttons.size() - 1); // space between all buttons
+		for(const auto & elem : ret->buttons) //and add buttons width
 		{
-			bw+=elem->pos.w;
-			vstd::amax(bh, elem->pos.h);
+			buttonsWidth += elem->pos.w;
+			vstd::amax(buttonsHeight, elem->pos.h);
 		}
-		winSize.second += 20 + bh;//before button + button
 	}
 
-	// Clip window size
-	vstd::amax(winSize.second, 50);
-	vstd::amax(winSize.first, 80);
-	vstd::amax(winSize.first, comps.w);
-	vstd::amax(winSize.first, bw);
-
-	vstd::amin(winSize.first, GH.screenDimensions().x - 150);
-
-	ret->bitmap = drawDialogBox (winSize.first + 2*SIDE_MARGIN, winSize.second + 2*SIDE_MARGIN, player);
-	ret->pos.h=ret->bitmap->h;
-	ret->pos.w=ret->bitmap->w;
-	ret->center();
+	// STEP 2: COMPUTE WINDOW SIZE
 
-	int curh = SIDE_MARGIN;
-	int xOffset = (ret->pos.w - ret->text->pos.w)/2;
-
-	if(!ret->buttons.size() && !ret->components.size()) //improvement for very small text only popups -> center text vertically
+	if(ret->buttons.empty() && !ret->components)
 	{
-		if(ret->bitmap->h > ret->text->pos.h + 2*SIDE_MARGIN)
-			curh = (ret->bitmap->h - ret->text->pos.h)/2;
+		// use more compact form for right-click popup with no buttons / components
+
+		ret->pos.w = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.x + 2 * SIDE_MARGIN);
+		ret->pos.h = std::max(RIGHT_CLICK_POPUP_MIN_SIZE, ret->text->label->textSize.y + TOP_MARGIN + BOTTOM_MARGIN);
 	}
+	else
+	{
+		int windowContentWidth = ret->text->pos.w;
+		int windowContentHeight = ret->text->pos.h;
+		if(ret->components)
+		{
+			vstd::amax(windowContentWidth, ret->components->pos.w);
+			windowContentHeight += INTERVAL_BETWEEN_TEXT_AND_BUTTONS + ret->components->pos.h;
+		}
+		if(!ret->buttons.empty())
+		{
+			vstd::amax(windowContentWidth, buttonsWidth);
+			windowContentHeight += INTERVAL_BETWEEN_TEXT_AND_BUTTONS + buttonsHeight;
+		}
 
-	ret->text->moveBy(Point(xOffset, curh));
+		ret->pos.w = windowContentWidth + 2 * SIDE_MARGIN;
+		ret->pos.h = windowContentHeight + TOP_MARGIN + BOTTOM_MARGIN;
+	}
 
-	curh += ret->text->pos.h;
+	// STEP 3: MOVE ALL ELEMENTS IN PLACE
 
-	if (ret->components.size())
+	if(ret->buttons.empty() && !ret->components)
 	{
-		curh += BEFORE_COMPONENTS;
-		comps.blitCompsOnSur (blitOr, BETWEEN_COMPS, curh, ret->bitmap);
+		ret->text->trimToFit();
+		ret->text->center(ret->pos.center());
 	}
-	if(ret->buttons.size())
+	else
 	{
-		// Position the buttons at the bottom of the window
-		bw = (ret->bitmap->w/2) - (bw/2);
-		curh = ret->bitmap->h - SIDE_MARGIN - ret->buttons[0]->pos.h;
+		if(ret->components)
+			ret->components->moveBy(Point((ret->pos.w - ret->components->pos.w) / 2, TOP_MARGIN + ret->text->pos.h + INTERVAL_BETWEEN_TEXT_AND_BUTTONS));
 
-		for(auto & elem : ret->buttons)
+		ret->text->trimToFit();
+		ret->text->moveBy(Point((ret->pos.w - ret->text->pos.w) / 2, TOP_MARGIN + (textHeight - ret->text->pos.h) / 2 ));
+
+		if(!ret->buttons.empty())
 		{
-			elem->moveBy(Point(bw, curh));
-			bw += elem->pos.w + 20;
+			int buttonPosX = ret->pos.w / 2 - buttonsWidth / 2;
+			int buttonPosY = ret->pos.h - BOTTOM_MARGIN - ret->buttons[0]->pos.h;
+
+			for(const auto & elem : ret->buttons)
+			{
+				elem->moveBy(Point(buttonPosX, buttonPosY));
+				buttonPosX += elem->pos.w + INTERVAL_BETWEEN_BUTTONS;
+			}
 		}
 	}
-	for(size_t i=0; i<ret->components.size(); i++)
-		ret->components[i]->moveBy(Point(ret->pos.x, ret->pos.y));
+
+	ret->backgroundTexture->pos = ret->pos;
+	ret->center();
 }
 
-void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x, int y)
+void CMessage::drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, int x, int y)
 {
 	if(playerColor.isSpectator())
 		playerColor = PlayerColor(1);
@@ -366,188 +318,36 @@ void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int
 	// Horizontal borders
 	int start_x = x + box[0]->width();
 	const int stop_x = x + w - box[1]->width();
-	const int bottom_y = y+h-box[7]->height()+1;
-	while (start_x < stop_x) {
-		int cur_w = stop_x - start_x;
-		if (cur_w > box[6]->width())
-			cur_w = box[6]->width();
+	const int bottom_y = y + h - box[7]->height() + 1;
+	while(start_x < stop_x)
+	{
 
 		// Top border
-		Rect srcR(0, 0, cur_w, box[6]->height());
-		Rect dstR(start_x, y, 0, 0);
-		box[6]->draw(ret, &dstR, &srcR);
-
+		to.draw(box[6], Point(start_x, y));
 		// Bottom border
-		dstR.y = bottom_y;
-		box[7]->draw(ret, &dstR, &srcR);
+		to.draw(box[7], Point(start_x, bottom_y));
 
-		start_x += cur_w;
+		start_x += box[6]->width();
 	}
 
 	// Vertical borders
 	int start_y = y + box[0]->height();
-	const int stop_y = y + h - box[2]->height()+1;
-	const int right_x = x+w-box[5]->width();
-	while (start_y < stop_y) {
-		int cur_h = stop_y - start_y;
-		if (cur_h > box[4]->height())
-			cur_h = box[4]->height();
+	const int stop_y = y + h - box[2]->height() + 1;
+	const int right_x = x + w - box[5]->width();
+	while(start_y < stop_y)
+	{
 
 		// Left border
-		Rect srcR(0, 0, box[4]->width(), cur_h);
-		Rect dstR(x, start_y, 0, 0);
-		box[4]->draw(ret, &dstR, &srcR);
-
+		to.draw(box[4], Point(x, start_y));
 		// Right border
-		dstR.x = right_x;
-		box[5]->draw(ret, &dstR, &srcR);
+		to.draw(box[5], Point(right_x, start_y));
 
-		start_y += cur_h;
+		start_y += box[4]->height();
 	}
 
 	//corners
-	Rect dstR(x, y, box[0]->width(), box[0]->height());
-	box[0]->draw(ret, &dstR, nullptr);
-
-	dstR=Rect(x+w-box[1]->width(), y,   box[1]->width(), box[1]->height());
-	box[1]->draw(ret, &dstR, nullptr);
-
-	dstR=Rect(x, y+h-box[2]->height()+1, box[2]->width(), box[2]->height());
-	box[2]->draw(ret, &dstR, nullptr);
-
-	dstR=Rect(x+w-box[3]->width(), y+h-box[3]->height()+1, box[3]->width(), box[3]->height());
-	box[3]->draw(ret, &dstR, nullptr);
-}
-
-ComponentResolved::ComponentResolved(std::shared_ptr<CComponent> Comp):
-	comp(Comp)
-{
-	//Temporary assign ownership on comp
-	if (parent)
-		parent->removeChild(this);
-	if (comp->parent)
-	{
-		comp->parent->addChild(this);
-		comp->parent->removeChild(comp.get());
-	}
-
-	addChild(comp.get());
-	defActions = 255 - DISPOSE;
-	pos.x = pos.y = 0;
-
-	pos.w = comp->pos.w;
-	pos.h = comp->pos.h;
-}
-
-ComponentResolved::~ComponentResolved()
-{
-	if (parent)
-	{
-		removeChild(comp.get());
-		parent->addChild(comp.get());
-	}
-}
-
-void ComponentResolved::showAll(Canvas & to)
-{
-	CIntObject::showAll(to);
-	comp->showAll(to);
-}
-
-ComponentsToBlit::~ComponentsToBlit() = default;
-
-ComponentsToBlit::ComponentsToBlit(std::vector<std::shared_ptr<CComponent>> & SComps, int maxw, bool blitOr)
-{
-	int orWidth = static_cast<int>(graphics->fonts[FONT_MEDIUM]->getStringWidth(CGI->generaltexth->allTexts[4]));
-
-	w = h = 0;
-	if(SComps.empty())
-		return;
-
-	comps.resize(1);
-	int curw = 0;
-	int curr = 0; //current row
-
-	for(auto & SComp : SComps)
-	{
-		auto cur = std::make_shared<ComponentResolved>(SComp);
-
-		int toadd = (cur->pos.w + BETWEEN_COMPS + (blitOr ? orWidth : 0));
-		if (curw + toadd > maxw)
-		{
-			curr++;
-			vstd::amax(w,curw);
-			curw = cur->pos.w;
-			comps.resize(curr+1);
-		}
-		else
-		{
-			curw += toadd;
-			vstd::amax(w,curw);
-		}
-
-		comps[curr].push_back(cur);
-	}
-
-	for(auto & elem : comps)
-	{
-		int maxHeight = 0;
-		for(size_t j=0;j<elem.size();j++)
-			vstd::amax(maxHeight, elem[j]->pos.h);
-
-		h += maxHeight + BETWEEN_COMPS_ROWS;
-	}
-}
-
-void ComponentsToBlit::blitCompsOnSur( bool blitOr, int inter, int &curh, SDL_Surface *ret )
-{
-	int orWidth = static_cast<int>(graphics->fonts[FONT_MEDIUM]->getStringWidth(CGI->generaltexth->allTexts[4]));
-
-	for (auto & elem : comps)//for each row
-	{
-		int totalw=0, maxHeight=0;
-		for(size_t j=0;j<elem.size();j++)//find max height & total width in this row
-		{
-			auto cur = elem[j];
-			totalw += cur->pos.w;
-			vstd::amax(maxHeight, cur->pos.h);
-		}
-
-		//add space between comps in this row
-		if(blitOr)
-			totalw += (inter*2+orWidth) * ((int)elem.size() - 1);
-		else
-			totalw += (inter) * ((int)elem.size() - 1);
-
-		int middleh = curh + maxHeight/2;//axis for image aligment
-		int curw = ret->w/2 - totalw/2;
-
-		for(size_t j=0;j<elem.size();j++)
-		{
-			auto cur = elem[j];
-			cur->moveTo(Point(curw, curh));
-
-			//blit component
-			Canvas canvas = Canvas::createFromSurface(ret);
-
-			cur->showAll(canvas);
-			curw += cur->pos.w;
-
-			//if there is subsequent component blit "or"
-			if(j<(elem.size()-1))
-			{
-				if(blitOr)
-				{
-					curw+=inter;
-
-					graphics->fonts[FONT_MEDIUM]->renderTextLeft(ret, CGI->generaltexth->allTexts[4], Colors::WHITE,
-							Point(curw,middleh-((int)graphics->fonts[FONT_MEDIUM]->getLineHeight()/2)));
-
-					curw+=orWidth;
-				}
-				curw+=inter;
-			}
-		}
-		curh += maxHeight + BETWEEN_COMPS_ROWS;
-	}
+	to.draw(box[0], Point(x, y));
+	to.draw(box[1], Point(x + w - box[1]->width(), y));
+	to.draw(box[2], Point(x, y + h - box[2]->height() + 1));
+	to.draw(box[3], Point(x + w - box[3]->width(), y + h - box[3]->height() + 1));
 }

+ 2 - 2
client/windows/CMessage.h

@@ -15,12 +15,12 @@
 struct SDL_Surface;
 class CInfoWindow;
 class CComponent;
+class Canvas;
 
 VCMI_LIB_NAMESPACE_BEGIN
 class ColorRGBA;
 VCMI_LIB_NAMESPACE_END
 
-
 /// Class which draws formatted text messages and generates chat windows
 class CMessage
 {
@@ -29,7 +29,7 @@ class CMessage
 
 public:
 	/// Draw border on exiting surface
-	static void drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x=0, int y=0);
+	static void drawBorder(PlayerColor playerColor, Canvas & to, int w, int h, int x, int y);
 
 	static void drawIWindow(CInfoWindow * ret, std::string text, PlayerColor player);
 

+ 1 - 1
client/windows/CSpellWindow.cpp

@@ -25,7 +25,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
-#include "../widgets/MiscWidgets.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/TextControls.h"
 #include "../adventureMap/AdventureMapInterface.h"

+ 1 - 1
client/windows/CWindowObject.cpp

@@ -232,7 +232,7 @@ void CWindowObject::showAll(Canvas & to)
 
 	CIntObject::showAll(to);
 	if ((options & BORDERED) && (pos.dimensions() != GH.screenDimensions()))
-		CMessage::drawBorder(color, to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+		CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 
 bool CWindowObject::isPopupWindow() const

+ 72 - 200
client/windows/InfoWindows.cpp

@@ -11,83 +11,51 @@
 #include "InfoWindows.h"
 
 #include "../CGameInfo.h"
-#include "../PlayerLocalState.h"
 #include "../CPlayerInterface.h"
-#include "../CMusicHandler.h"
+#include "../PlayerLocalState.h"
 
+#include "../adventureMap/AdventureMapInterface.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+#include "../gui/Shortcut.h"
+#include "../gui/WindowHandler.h"
+#include "../widgets/Buttons.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/Images.h"
 #include "../widgets/MiscWidgets.h"
-#include "../widgets/Buttons.h"
 #include "../widgets/TextControls.h"
-#include "../gui/CGuiHandler.h"
-#include "../gui/WindowHandler.h"
-#include "../battle/BattleInterface.h"
-#include "../battle/BattleInterfaceClasses.h"
-#include "../adventureMap/AdventureMapInterface.h"
 #include "../windows/CMessage.h"
-#include "../render/Canvas.h"
-#include "../renderSDL/SDL_Extensions.h"
-#include "../gui/CursorHandler.h"
-#include "../gui/Shortcut.h"
 
 #include "../../CCallback.h"
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CondSh.h"
-#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff
+#include "../../lib/gameState/InfoAboutArmy.h"
 #include "../../lib/mapObjects/CGCreature.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/mapObjects/MiscObjects.h"
-#include "../../lib/gameState/InfoAboutArmy.h"
-
-#include <SDL_surface.h>
 
-void CSimpleWindow::show(Canvas & to)
+CSelWindow::CSelWindow( const std::string & Text, PlayerColor player, int charperline, const std::vector<std::shared_ptr<CSelectableComponent>> & comps, const std::vector<std::pair<AnimationPath, CFunctionList<void()>>> & Buttons, QueryID askID)
 {
-	if(bitmap)
-		CSDL_Ext::blitAt(bitmap, pos.x, pos.y, to.getInternalSurface());
-}
-CSimpleWindow::~CSimpleWindow()
-{
-	if (bitmap)
-	{
-		SDL_FreeSurface(bitmap);
-		bitmap=nullptr;
-	}
-}
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 
-void CSelWindow::selectionChange(unsigned to)
-{
-	for (unsigned i=0;i<components.size();i++)
-	{
-		auto pom = std::dynamic_pointer_cast<CSelectableComponent>(components[i]);
-		if (!pom)
-			continue;
-		pom->select(i==to);
-	}
-	redraw();
-}
+	backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DiBoxBck"), pos);
 
-CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperline, const std::vector<std::shared_ptr<CSelectableComponent>> & comps, const std::vector<std::pair<AnimationPath, CFunctionList<void()> > > &Buttons, QueryID askID)
-{
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	ID = askID;
-	for (int i = 0; i < Buttons.size(); i++)
+	for(int i = 0; i < Buttons.size(); i++)
 	{
 		buttons.push_back(std::make_shared<CButton>(Point(0, 0), Buttons[i].first, CButton::tooltip(), Buttons[i].second));
-		if (!i  &&  askID.getNum() >= 0)
+		if(!i && askID.getNum() >= 0)
 			buttons.back()->addCallback(std::bind(&CSelWindow::madeChoice, this));
 		buttons[i]->addCallback(std::bind(&CInfoWindow::close, this)); //each button will close the window apart from call-defined actions
 	}
 
 	text = std::make_shared<CTextBox>(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
 
-	if (buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality
+	if(buttons.size() > 1 && askID.getNum() >= 0) //cancel button functionality
 	{
-		buttons.back()->addCallback([askID]() {
-			LOCPLINT->cb.get()->selectionMade(0, askID);
-		});
+		buttons.back()->addCallback([askID](){LOCPLINT->cb->selectionMade(0, askID);});
 		//buttons.back()->addCallback(std::bind(&CCallback::selectionMade, LOCPLINT->cb.get(), 0, askID));
 	}
 
@@ -100,16 +68,9 @@ CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperl
 		buttons.back()->assignedKey = EShortcut::GLOBAL_CANCEL;
 	}
 
-	for(int i=0;i<comps.size();i++)
-	{
-		comps[i]->recActions = 255-DISPOSE;
-		addChild(comps[i].get());
-		components.push_back(comps[i]);
-		comps[i]->onSelect = std::bind(&CSelWindow::selectionChange,this,i);
-		comps[i]->onChoose = std::bind(&CSelWindow::madeChoiceAndClose,this);
-		if(i<8)
-			comps[i]->assignedKey = vstd::next(EShortcut::SELECT_INDEX_1,i);
-	}
+	if(!comps.empty())
+		components = std::make_shared<CComponentBox>(comps, Rect(0,0,0,0));
+
 	CMessage::drawIWindow(this, Text, player);
 }
 
@@ -118,14 +79,10 @@ void CSelWindow::madeChoice()
 	if(ID.getNum() < 0)
 		return;
 	int ret = -1;
-	for (int i=0;i<components.size();i++)
-	{
-		if(std::dynamic_pointer_cast<CSelectableComponent>(components[i])->selected)
-		{
-			ret = i;
-		}
-	}
-	LOCPLINT->cb->selectionMade(ret+1,ID);
+	if(components)
+		ret = components->selectedIndex();
+
+	LOCPLINT->cb->selectionMade(ret + 1, ID);
 }
 
 void CSelWindow::madeChoiceAndClose()
@@ -134,14 +91,16 @@ void CSelWindow::madeChoiceAndClose()
 	close();
 }
 
-CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo & comps, const TButtonsInfo & Buttons)
+CInfoWindow::CInfoWindow(const std::string & Text, PlayerColor player, const TCompsInfo & comps, const TButtonsInfo & Buttons)
 {
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
+
+	backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DiBoxBck"), pos);
 
 	ID = QueryID(-1);
-	for(auto & Button : Buttons)
+	for(const auto & Button : Buttons)
 	{
-		std::shared_ptr<CButton> button = std::make_shared<CButton>(Point(0,0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close, this));
+		auto button = std::make_shared<CButton>(Point(0, 0), Button.first, CButton::tooltip(), std::bind(&CInfoWindow::close, this));
 		button->setBorderColor(Colors::METALLIC_GOLD);
 		button->addCallback(Button.second); //each button will close the window apart from call-defined actions
 		buttons.push_back(button);
@@ -164,15 +123,10 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo
 		buttons.back()->assignedKey = EShortcut::GLOBAL_CANCEL;
 	}
 
-	for(auto & comp : comps)
-	{
-		comp->recActions = 0xff & ~DISPOSE;
-		addChild(comp.get());
-		comp->recActions &= ~(SHOWALL | UPDATE);
-		components.push_back(comp);
-	}
+	if(!comps.empty())
+		components = std::make_shared<CComponentBox>(comps, Rect(0,0,0,0));
 
-	CMessage::drawIWindow(this,Text,player);
+	CMessage::drawIWindow(this, Text, player);
 }
 
 CInfoWindow::CInfoWindow()
@@ -188,128 +142,45 @@ void CInfoWindow::close()
 		LOCPLINT->showingDialog->setn(false);
 }
 
-void CInfoWindow::show(Canvas & to)
-{
-	CIntObject::show(to);
-}
-
-CInfoWindow::~CInfoWindow() = default;
-
 void CInfoWindow::showAll(Canvas & to)
 {
-	CSimpleWindow::show(to);
 	CIntObject::showAll(to);
+	CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 
-void CInfoWindow::showInfoDialog(const std::string &text, const TCompsInfo & components, PlayerColor player)
+CInfoWindow::~CInfoWindow() = default;
+
+void CInfoWindow::showInfoDialog(const std::string & text, const TCompsInfo & components, PlayerColor player)
 {
 	GH.windows().pushWindow(CInfoWindow::create(text, player, components));
 }
 
-void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList<void( ) > &onYes, const CFunctionList<void()> &onNo, PlayerColor player)
+void CInfoWindow::showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList<void()> & onYes, const CFunctionList<void()> & onNo, PlayerColor player)
 {
 	assert(!LOCPLINT || LOCPLINT->showingDialog->get());
-	std::vector<std::pair<AnimationPath,CFunctionList<void()> > > pom;
-	pom.push_back( { AnimationPath::builtin("IOKAY.DEF"), 0 });
-	pom.push_back( { AnimationPath::builtin("ICANCEL.DEF"), 0 });
-	std::shared_ptr<CInfoWindow> temp =  std::make_shared<CInfoWindow>(text, player, components, pom);
+	std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom;
+	pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr);
+	pom.emplace_back(AnimationPath::builtin("ICANCEL.DEF"), nullptr);
+	auto temp = std::make_shared<CInfoWindow>(text, player, components, pom);
 
-	temp->buttons[0]->addCallback( onYes );
-	temp->buttons[1]->addCallback( onNo );
+	temp->buttons[0]->addCallback(onYes);
+	temp->buttons[1]->addCallback(onNo);
 
 	GH.windows().pushWindow(temp);
 }
 
-std::shared_ptr<CInfoWindow> CInfoWindow::create(const std::string &text, PlayerColor playerID, const TCompsInfo & components)
+std::shared_ptr<CInfoWindow> CInfoWindow::create(const std::string & text, PlayerColor playerID, const TCompsInfo & components)
 {
-	std::vector<std::pair<AnimationPath,CFunctionList<void()> > > pom;
-	pom.push_back({AnimationPath::builtin("IOKAY.DEF"), 0});
+	std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom;
+	pom.emplace_back(AnimationPath::builtin("IOKAY.DEF"), nullptr);
 	return std::make_shared<CInfoWindow>(text, playerID, components, pom);
 }
 
-std::string CInfoWindow::genText(std::string title, std::string description)
+std::string CInfoWindow::genText(const std::string & title, const std::string & description)
 {
 	return std::string("{") + title + "}" + "\n\n" + description;
 }
 
-CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free)
- :free(Free),bitmap(Bitmap)
-{
-	init(x, y);
-}
-
-
-CInfoPopup::CInfoPopup(SDL_Surface * Bitmap, const Point &p, ETextAlignment alignment, bool Free)
- : free(Free),bitmap(Bitmap)
-{
-	switch(alignment)
-	{
-	case ETextAlignment::BOTTOMRIGHT:
-		init(p.x - Bitmap->w, p.y - Bitmap->h);
-		break;
-	case ETextAlignment::CENTER:
-		init(p.x - Bitmap->w/2, p.y - Bitmap->h/2);
-		break;
-	case ETextAlignment::TOPLEFT:
-		init(p.x, p.y);
-		break;
-	case ETextAlignment::TOPCENTER:
-		init(p.x - Bitmap->w/2, p.y);
-		break;
-	default:
-		assert(0); //not implemented
-	}
-}
-
-CInfoPopup::CInfoPopup(SDL_Surface *Bitmap, bool Free)
-{
-	CCS->curh->hide();
-
-	free=Free;
-	bitmap=Bitmap;
-
-	if(bitmap)
-	{
-		pos.x = GH.screenDimensions().x / 2 - bitmap->w / 2;
-		pos.y = GH.screenDimensions().y / 2 - bitmap->h / 2;
-		pos.h = bitmap->h;
-		pos.w = bitmap->w;
-	}
-}
-
-void CInfoPopup::close()
-{
-	if(free)
-		SDL_FreeSurface(bitmap);
-	WindowBase::close();
-}
-
-void CInfoPopup::show(Canvas & to)
-{
-	CSDL_Ext::blitAt(bitmap,pos.x,pos.y,to.getInternalSurface());
-}
-
-CInfoPopup::~CInfoPopup()
-{
-	CCS->curh->show();
-}
-
-void CInfoPopup::init(int x, int y)
-{
-	CCS->curh->hide();
-
-	pos.x = x;
-	pos.y = y;
-	pos.h = bitmap->h;
-	pos.w = bitmap->w;
-
-	// Put the window back on screen if necessary
-	vstd::amax(pos.x, 0);
-	vstd::amax(pos.y, 0);
-	vstd::amin(pos.x, GH.screenDimensions().x - bitmap->w);
-	vstd::amin(pos.y, GH.screenDimensions().y - bitmap->h);
-}
-
 bool CRClickPopup::isPopupWindow() const
 {
 	return true;
@@ -320,10 +191,10 @@ void CRClickPopup::close()
 	WindowBase::close();
 }
 
-void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps)
+void CRClickPopup::createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo & comps)
 {
 	PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue
-	if(settings["session"]["spectate"].Bool())//TODO: there must be better way to implement this
+	if(settings["session"]["spectate"].Bool()) //TODO: there must be better way to implement this
 		player = PlayerColor(1);
 
 	auto temp = std::make_shared<CInfoWindow>(txt, player, comps);
@@ -336,7 +207,7 @@ void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCom
 	GH.windows().createAndPushWindow<CRClickPopupInt>(temp);
 }
 
-void CRClickPopup::createAndPush(const std::string & txt, std::shared_ptr<CComponent> component)
+void CRClickPopup::createAndPush(const std::string & txt, const std::shared_ptr<CComponent> & component)
 {
 	CInfoWindow::TCompsInfo intComps;
 	intComps.push_back(component);
@@ -354,7 +225,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p,
 	else
 	{
 		std::vector<Component> components;
-		if (settings["general"]["enableUiEnhancements"].Bool())
+		if(settings["general"]["enableUiEnhancements"].Bool())
 		{
 			if(LOCPLINT->localState->getCurrentHero())
 				components = obj->getPopupComponents(LOCPLINT->localState->getCurrentHero());
@@ -363,7 +234,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p,
 		}
 
 		std::vector<std::shared_ptr<CComponent>> guiComponents;
-		for (auto & component : components)
+		for(auto & component : components)
 			guiComponents.push_back(std::make_shared<CComponent>(component));
 
 		if(LOCPLINT->localState->getCurrentHero())
@@ -373,7 +244,7 @@ void CRClickPopup::createAndPush(const CGObjectInstance * obj, const Point & p,
 	}
 }
 
-CRClickPopupInt::CRClickPopupInt(std::shared_ptr<CIntObject> our)
+CRClickPopupInt::CRClickPopupInt(const std::shared_ptr<CIntObject> & our)
 {
 	CCS->curh->hide();
 	defActions = SHOWALL | UPDATE;
@@ -403,7 +274,7 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGTownInstance * town)
 	InfoAboutTown iah;
 	LOCPLINT->cb->getTownInfo(town, iah, LOCPLINT->localState->getCurrentTown()); //todo: should this be nearest hero?
 
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 	tooltip = std::make_shared<CTownTooltip>(Point(9, 10), iah);
 }
 
@@ -411,9 +282,9 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGHeroInstance * hero)
 	: CWindowObject(RCLICK_POPUP | PLAYER_COLORED, ImagePath::builtin("HEROQVBK"), toScreen(position))
 {
 	InfoAboutHero iah;
-	LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentHero());//todo: should this be nearest hero?
+	LOCPLINT->cb->getHeroInfo(hero, iah, LOCPLINT->localState->getCurrentHero()); //todo: should this be nearest hero?
 
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 	tooltip = std::make_shared<CHeroTooltip>(Point(9, 10), iah);
 }
 
@@ -423,18 +294,19 @@ CInfoBoxPopup::CInfoBoxPopup(Point position, const CGGarrison * garr)
 	InfoAboutTown iah;
 	LOCPLINT->cb->getTownInfo(garr, iah);
 
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 	tooltip = std::make_shared<CArmyTooltip>(Point(9, 10), iah);
 }
 
 CInfoBoxPopup::CInfoBoxPopup(Point position, const CGCreature * creature)
-		: CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), toScreen(position))
+	: CWindowObject(RCLICK_POPUP | BORDERED, ImagePath::builtin("DIBOXBCK"), toScreen(position))
 {
-	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
 	tooltip = std::make_shared<CreatureTooltip>(Point(9, 10), creature);
 }
 
-std::shared_ptr<WindowBase> CRClickPopup::createCustomInfoWindow(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero
+std::shared_ptr<WindowBase>
+CRClickPopup::createCustomInfoWindow(Point position, const CGObjectInstance * specific) //specific=0 => draws info about selected town/hero
 {
 	if(nullptr == specific)
 		specific = LOCPLINT->localState->getCurrentArmy();
@@ -447,16 +319,16 @@ std::shared_ptr<WindowBase> CRClickPopup::createCustomInfoWindow(Point position,
 
 	switch(specific->ID)
 	{
-	case Obj::HERO:
-		return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGHeroInstance *>(specific));
-	case Obj::TOWN:
-		return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGTownInstance *>(specific));
-	case Obj::MONSTER:
-		return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGCreature *>(specific));
-	case Obj::GARRISON:
-	case Obj::GARRISON2:
-		return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGGarrison *>(specific));
-	default:
-		return std::shared_ptr<WindowBase>();
+		case Obj::HERO:
+			return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGHeroInstance *>(specific));
+		case Obj::TOWN:
+			return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGTownInstance *>(specific));
+		case Obj::MONSTER:
+			return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGCreature *>(specific));
+		case Obj::GARRISON:
+		case Obj::GARRISON2:
+			return std::make_shared<CInfoBoxPopup>(position, dynamic_cast<const CGGarrison *>(specific));
+		default:
+			return std::shared_ptr<WindowBase>();
 	}
 }

+ 15 - 46
client/windows/InfoWindows.h

@@ -20,59 +20,44 @@ class CGTownInstance;
 class CGHeroInstance;
 class CGGarrison;
 class CGCreature;
-class Rect;
 
 VCMI_LIB_NAMESPACE_END
 
-struct SDL_Surface;
-class CAnimImage;
-class CLabel;
-class CAnimation;
 class CComponent;
+class CComponentBox;
 class CSelectableComponent;
 class CTextBox;
 class CButton;
-class CSlider;
-class CArmyTooltip;
-
-// Window GUI class
-class CSimpleWindow : public WindowBase
-{
-public:
-	SDL_Surface * bitmap; //background
-	void show(Canvas & to) override;
-	CSimpleWindow():bitmap(nullptr){};
-	virtual ~CSimpleWindow();
-};
+class CFilledTexture;
 
 /// text + comp. + ok button
-class CInfoWindow : public CSimpleWindow
+class CInfoWindow : public WindowBase
 {
 public:
 	using TButtonsInfo = std::vector<std::pair<AnimationPath, CFunctionList<void()>>>;
 	using TCompsInfo = std::vector<std::shared_ptr<CComponent>>;
 	QueryID ID; //for identification
+	std::shared_ptr<CFilledTexture> backgroundTexture;
 	std::shared_ptr<CTextBox> text;
+	std::shared_ptr<CComponentBox> components;
 	std::vector<std::shared_ptr<CButton>> buttons;
-	TCompsInfo components;
 
 	void close() override;
-
-	void show(Canvas & to) override;
 	void showAll(Canvas & to) override;
+
 	void sliderMoved(int to);
 
-	CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo & comps = TCompsInfo(), const TButtonsInfo & Buttons = TButtonsInfo());
+	CInfoWindow(const std::string & Text, PlayerColor player, const TCompsInfo & comps = TCompsInfo(), const TButtonsInfo & Buttons = TButtonsInfo());
 	CInfoWindow();
 	~CInfoWindow();
 
 	//use only before the game starts! (showYesNoDialog in LOCPLINT must be used then)
-	static void showInfoDialog( const std::string & text, const TCompsInfo & components, PlayerColor player = PlayerColor(1));
-	static void showYesNoDialog( const std::string & text, const TCompsInfo & components, const CFunctionList<void()> & onYes, const CFunctionList<void()> & onNo, PlayerColor player = PlayerColor(1));
+	static void showInfoDialog(const std::string & text, const TCompsInfo & components, PlayerColor player = PlayerColor(1));
+	static void showYesNoDialog(const std::string & text, const TCompsInfo & components, const CFunctionList<void()> & onYes, const CFunctionList<void()> & onNo, PlayerColor player = PlayerColor(1));
 	static std::shared_ptr<CInfoWindow> create(const std::string & text, PlayerColor playerID = PlayerColor(1), const TCompsInfo & components = TCompsInfo());
 
 	/// create text from title and description: {title}\n\n description
-	static std::string genText(std::string title, std::string description);
+	static std::string genText(const std::string & title, const std::string & description);
 };
 
 /// popup displayed on R-click
@@ -83,8 +68,8 @@ public:
 	bool isPopupWindow() const override;
 
 	static std::shared_ptr<WindowBase> createCustomInfoWindow(Point position, const CGObjectInstance * specific);
-	static void createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo &comps = CInfoWindow::TCompsInfo());
-	static void createAndPush(const std::string & txt, std::shared_ptr<CComponent> component);
+	static void createAndPush(const std::string & txt, const CInfoWindow::TCompsInfo & comps = CInfoWindow::TCompsInfo());
+	static void createAndPush(const std::string & txt, const std::shared_ptr<CComponent> & component);
 	static void createAndPush(const CGObjectInstance * obj, const Point & p, ETextAlignment alignment = ETextAlignment::BOTTOMRIGHT);
 };
 
@@ -92,24 +77,10 @@ public:
 class CRClickPopupInt : public CRClickPopup
 {
 	std::shared_ptr<CIntObject> inner;
-public:
-	CRClickPopupInt(std::shared_ptr<CIntObject> our);
-	virtual ~CRClickPopupInt();
-};
 
-class CInfoPopup : public CRClickPopup
-{
 public:
-	bool free; //TODO: comment me
-	SDL_Surface * bitmap; //popup background
-	void close() override;
-	void show(Canvas & to) override;
-	CInfoPopup(SDL_Surface * Bitmap, int x, int y, bool Free=false);
-	CInfoPopup(SDL_Surface * Bitmap, const Point &p, ETextAlignment alignment, bool Free=false);
-	CInfoPopup(SDL_Surface * Bitmap = nullptr, bool Free = false);
-
-	void init(int x, int y);
-	~CInfoPopup();
+	CRClickPopupInt(const std::shared_ptr<CIntObject> & our);
+	~CRClickPopupInt();
 };
 
 /// popup on adventure map for town\hero and other objects with customized popup content
@@ -117,6 +88,7 @@ class CInfoBoxPopup : public CWindowObject
 {
 	std::shared_ptr<CIntObject> tooltip;
 	Point toScreen(Point pos);
+
 public:
 	CInfoBoxPopup(Point position, const CGTownInstance * town);
 	CInfoBoxPopup(Point position, const CGHeroInstance * hero);
@@ -128,10 +100,7 @@ public:
 class CSelWindow : public CInfoWindow
 {
 public:
-	void selectionChange(unsigned to);
 	void madeChoice(); //looks for selected component and calls callback
 	void madeChoiceAndClose();
 	CSelWindow(const std::string & text, PlayerColor player, int charperline, const std::vector<std::shared_ptr<CSelectableComponent>> & comps, const std::vector<std::pair<AnimationPath,CFunctionList<void()> > > &Buttons, QueryID askID);
-
-	//notification - this class inherits important destructor from CInfoWindow
 };

+ 1 - 1
client/windows/settings/SettingsMainWindow.cpp

@@ -185,7 +185,7 @@ void SettingsMainWindow::showAll(Canvas & to)
 		color = PlayerColor(1); // TODO: Spectator shouldn't need special code for UI colors
 
 	CIntObject::showAll(to);
-	CMessage::drawBorder(color, to.getInternalSurface(), pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+	CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
 }
 
 void SettingsMainWindow::onScreenResize()

+ 1 - 1
config/schemas/artifact.json

@@ -93,7 +93,7 @@
 				"map" : {
 					"type" : "string",
 					"description" : ".def file for adventure map",
-					"format" : "defFile"
+					"format" : "animationFile"
 				}
 			}
 		},

+ 3 - 3
config/schemas/creature.json

@@ -133,12 +133,12 @@
 				"animation" : {
 					"type" : "string",
 					"description" : "File with animation of this creature in battles",
-					"format" : "defFile"
+					"format" : "animationFile"
 				},
 				"map" : {
 					"type" : "string",
 					"description" : "File with animation of this creature on adventure map",
-					"format" : "defFile"
+					"format" : "animationFile"
 				},
 				"mapMask" : {
 					"type" : "array",
@@ -184,7 +184,7 @@
 						"projectile" : {
 							"type" : "string",
 							"description" : "Path to projectile animation",
-							"format" : "defFile"
+							"format" : "animationFile"
 						},
 						"ray" : {
 							"type" : "array",

+ 8 - 1
config/schemas/hero.json

@@ -29,7 +29,14 @@
 		"battleImage" : {
 			"type" : "string",
 			"description" : "Custom animation to be used on battle, overrides hero class property",
-			"format" : "defFile"
+			"format" : "animationFile"
+		},
+		"compatibilityIdentifiers" : {
+			"type" : "array",
+			"items" : {
+				"type" : "string",
+			},
+			"description" : "Additional identifiers that may refer to this object, to provide compatibility after object has been renamed"
 		},
 		"images" : {
 			"type" : "object",

+ 2 - 2
config/schemas/heroClass.json

@@ -42,12 +42,12 @@
 						"female" : {
 							"type" : "string",
 							"description" : "Female version",
-							"format" : "defFile"
+							"format" : "animationFile"
 						},
 						"male" : {
 							"type" : "string",
 							"description" : "Male version",
-							"format" : "defFile"
+							"format" : "animationFile"
 						}
 					}
 				}

+ 2 - 2
config/schemas/objectTemplate.json

@@ -9,12 +9,12 @@
 		"animation" : {
 			"type" : "string",
 			"description" : "Path to def file with animation of this object",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"editorAnimation" : {
 			"type" : "string",
 			"description" : "Optional path to def file with animation of this object to use in map editor",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"visitableFrom" : {
 			"type" : "array",

+ 1 - 1
config/schemas/obstacle.json

@@ -45,7 +45,7 @@
 			"type" : "string",
 			"description" : "Image resource",
 			"anyOf" : [
-				{ "format" : "defFile" },
+				{ "format" : "animationFile" },
 				{ "format" : "imageFile" }
 			]
 		},

+ 1 - 1
config/schemas/river.json

@@ -20,7 +20,7 @@
 		{
 			"type" : "string",
 			"description" : "Name of file with river graphics",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"delta" :
 		{

+ 1 - 1
config/schemas/road.json

@@ -20,7 +20,7 @@
 		{
 			"type" : "string",
 			"description" : "Name of file with road graphics",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"moveCost" :
 		{

+ 3 - 3
config/schemas/spell.json

@@ -15,13 +15,13 @@
 					{
 						//assumed verticalPosition: top
 						"type" : "string",
-						"format" : "defFile"
+						"format" : "animationFile"
 					},
 					{
 						"type" : "object",
 						"properties" : {
 							"verticalPosition" : {"type" : "string", "enum" :["top","bottom"]},
-							"defName" : {"type" : "string", "format" : "defFile"},
+							"defName" : {"type" : "string", "format" : "animationFile"},
 							"effectName" : { "type" : "string" }
 						},
 						"additionalProperties" : false
@@ -41,7 +41,7 @@
 					"items" : {
 						"type" : "object",
 						"properties" : {
-							"defName" : {"type" : "string", "format" : "defFile"},
+							"defName" : {"type" : "string", "format" : "animationFile"},
 							"minimumAngle" : {"type" : "number", "minimum" : 0}
 						},
 						"additionalProperties" : false

+ 1 - 1
config/schemas/terrain.json

@@ -35,7 +35,7 @@
 		{
 			"type" : "string",
 			"description" : "Name of file with graphicks",
-			"format" : "defFile"
+			"format" : "animationFile"
 		},
 		"rockTerrain" :
 		{

+ 61 - 0
config/widgets/commonPrimitives.json

@@ -0,0 +1,61 @@
+{
+	"boxWithNoBackground" : {
+		"type": "graphicalPrimitive",
+		"primitives" : [
+			// Top line
+			{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] },
+			{ "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] },
+			
+			// Left line
+			{ "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] },
+			{ "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] },
+			
+			// Right line
+			{ "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] },
+			{ "type" : "line", "a" : { "x" : -1, "y" : 1}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] },
+
+			// Bottom line
+			{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] },
+			{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] },
+		]
+	},
+	
+	"horizontalLine" : {
+		"type": "graphicalPrimitive",
+		"primitives" : [
+			{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 64 ] },
+			{ "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 64 ] }
+		]
+	},
+	
+	"verticalLine" : {
+		"type": "graphicalPrimitive",
+		"primitives" : [
+			{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
+			{ "type" : "line", "a" : { "x" : 1, "y" : 0}, "b" : { "x" : 1, "y" : -1}, "color" : [ 0, 0, 0, 64 ] }
+		]
+	},
+	
+	"boxWithBackground" : {
+		"type": "graphicalPrimitive",
+		"primitives" : [
+			{ "type" : "filledBox", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 75 ] },
+		
+			// Top line
+			{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 32 ] },
+			{ "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : -1, "y" : 1}, "color" : [ 0, 0, 0, 48 ] },
+			
+			// Left line
+			{ "type" : "line", "a" : { "x" : 0, "y" : 1}, "b" : { "x" : 0, "y" : -2}, "color" : [ 255, 255, 255, 32 ] },
+			{ "type" : "line", "a" : { "x" : 1, "y" : 2}, "b" : { "x" : 1, "y" : -2}, "color" : [ 0, 0, 0, 48 ] },
+			
+			// Right line
+			{ "type" : "line", "a" : { "x" : -2, "y" : 2}, "b" : { "x" : -2, "y" : -3}, "color" : [ 255, 255, 255, 24 ] },
+			{ "type" : "line", "a" : { "x" : -1, "y" : 1}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] },
+
+			// Bottom line
+			{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -1, "y" : -2}, "color" : [ 255, 255, 255, 24 ] },
+			{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 255, 255, 255, 48 ] }
+		]
+	},
+}

+ 5 - 5
config/widgets/extraOptionsTab.json

@@ -37,27 +37,27 @@
 		},
 		{
 			"type": "transparentFilledRectangle",
-			"rect": {"x": 54, "y": 127, "w": 335, "h": 1},
+			"rect": {"x": 54, "y": 127, "w": 335, "h": 2},
 			"color": [24, 41, 90, 255]
 		},
 		{
 			"type": "transparentFilledRectangle",
-			"rect": {"x": 158, "y": 90, "w": 2, "h": 37},
+			"rect": {"x": 159, "y": 90, "w": 2, "h": 38},
 			"color": [24, 41, 90, 255]
 		},
 		{
 			"type": "transparentFilledRectangle",
-			"rect": {"x": 234, "y": 90, "w": 2, "h": 37},
+			"rect": {"x": 235, "y": 90, "w": 2, "h": 38},
 			"color": [24, 41, 90, 255]
 		},
 		{
 			"type": "transparentFilledRectangle",
-			"rect": {"x": 310, "y": 90, "w": 2, "h": 37},
+			"rect": {"x": 311, "y": 90, "w": 2, "h": 38},
 			"color": [24, 41, 90, 255]
 		},
 		{
 			"type": "transparentFilledRectangle",
-			"rect": {"x": 55, "y": 556, "w": 334, "h": 18},
+			"rect": {"x": 55, "y": 556, "w": 335, "h": 19},
 			"color": [24, 41, 90, 255]
 		},
 		{

+ 20 - 36
config/widgets/mapOverview.json

@@ -1,4 +1,6 @@
 {
+	"library" : "config/widgets/commonPrimitives.json",
+
 	"items":
 	[		
 		{
@@ -8,10 +10,8 @@
 			"rect": {"w": 428, "h": 379}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 5, "w": 418, "h": 20},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 5, "w": 418, "h": 20}
 		},
 		{
 			"type": "label",
@@ -22,10 +22,8 @@
 			"position": {"x": 214, "y": 15}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 30, "w": 418, "h": 20},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 30, "w": 418, "h": 20}
 		},
 		{
 			"type": "label",
@@ -37,10 +35,8 @@
 			"position": {"x": 214, "y": 40}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 55, "w": 418, "h": 20},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 55, "w": 418, "h": 20}
 		},
 		{
 			"type": "label",
@@ -51,10 +47,8 @@
 			"position": {"x": 214, "y": 65}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 29, "y": 79, "w": 171, "h": 171},
-			"color": [0, 0, 0, 255],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 29, "y": 79, "w": 171, "h": 171}
 		},
 		{
 			"type": "label",
@@ -70,10 +64,8 @@
 			"rect": {"x": 30, "y": 80, "w": 169, "h": 169}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 228, "y": 79, "w": 171, "h": 171},
-			"color": [0, 0, 0, 255],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 228, "y": 79, "w": 171, "h": 171}
 		},
 		{
 			"type": "label",
@@ -90,10 +82,8 @@
 			"rect": {"x": 229, "y": 80, "w": 169, "h": 169}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 254, "w": 418, "h": 20},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 254, "w": 418, "h": 20}
 		},
 		{
 			"type": "label",
@@ -104,10 +94,8 @@
 			"position": {"x": 214, "y": 264}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 279, "w": 418, "h": 20},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 279, "w": 418, "h": 20}
 		},
 		{
 			"type": "label",
@@ -119,10 +107,8 @@
 			"position": {"x": 214, "y": 289}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 304, "w": 418, "h": 20},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 304, "w": 418, "h": 20}
 		},
 		{
 			"type": "label",
@@ -133,10 +119,8 @@
 			"position": {"x": 214, "y": 314}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 5, "y": 329, "w": 418, "h": 45},
-			"color": [0, 0, 0, 75],
-			"colorLine": [128, 100, 75, 255]
+			"type": "boxWithBackground",
+			"rect": {"x": 5, "y": 329, "w": 418, "h": 45}
 		},
 		{
 			"type": "textBox",

+ 5 - 3
config/widgets/settings/adventureOptionsTab.json

@@ -1,12 +1,14 @@
 {
-	"library" : "config/widgets/settings/library.json",
+	"library" : [
+		"config/widgets/settings/library.json",
+		"config/widgets/commonPrimitives.json",
+	],
 
 	"items":
 	[
 		{
 			"name": "lineLabelsEnd",
-			"type": "texture",
-			"image": "settingsWindow/lineHorizontal",
+			"type": "horizontalLine",
 			"rect": { "x" : 5, "y" : 229, "w": 365, "h": 3}
 		},
 /////////////////////////////////////// Left section - Hero Speed and Map Scrolling

+ 6 - 5
config/widgets/settings/battleOptionsTab.json

@@ -1,18 +1,19 @@
 {
-	"library" : "config/widgets/settings/library.json",
+	"library" : [
+		"config/widgets/settings/library.json",
+		"config/widgets/commonPrimitives.json",
+	],
 
 	"items":
 	[
 		{
 			"name": "lineCreatureInfo",
-			"type": "texture",
-			"image": "settingsWindow/lineHorizontal",
+			"type": "horizontalLine",
 			"rect": { "x" : 5, "y" : 289, "w": 365, "h": 3}
 		},
 		{
 			"name": "lineAnimationSpeed",
-			"type": "texture",
-			"image": "settingsWindow/lineHorizontal",
+			"type": "horizontalLine",
 			"rect": { "x" : 5, "y" : 349, "w": 365, "h": 3}
 		},
 		{

+ 5 - 3
config/widgets/settings/generalOptionsTab.json

@@ -1,12 +1,14 @@
 {
-	"library" : "config/widgets/settings/library.json",
+	"library" : [
+		"config/widgets/settings/library.json",
+		"config/widgets/commonPrimitives.json",
+	],
 
 	"items":
 	[
 		{
 			"name": "lineLabelsEnd",
-			"type": "texture",
-			"image": "settingsWindow/lineHorizontal",
+			"type": "horizontalLine",
 			"rect": { "x" : 5, "y" : 349, "w": 365, "h": 3}
 		},
 		{

+ 3 - 2
config/widgets/settings/library.json

@@ -35,8 +35,9 @@
 		]
 	},
 	"checkboxFake" : {
-		"type": "picture",
-		"image": "settingsWindow/checkBoxEmpty"
+		"type": "boxWithBackground",
+		"rect": { "x" : 0, "y" : 0, "w": 32, "h": 24}
+		
 	},
 	"audioSlider" : {
 		"type": "slider",

+ 7 - 6
config/widgets/settings/settingsMainContainer.json

@@ -1,4 +1,8 @@
 {
+	"library" : [
+		"config/widgets/commonPrimitives.json"
+	],
+	
 	"items":
 	[
 		{
@@ -9,14 +13,12 @@
 		},
 		{
 			"name": "lineTabs",
-			"type": "texture",
-			"image": "settingsWindow/lineHorizontal",
+			"type": "horizontalLine",
 			"rect": { "x" : 10, "y" : 45, "w": 580, "h": 3}
 		},
 		{
 			"name": "lineColumns",
-			"type": "texture",
-			"image": "settingsWindow/lineVertical",
+			"type": "verticalLine",
 			"rect": { "x" : 370, "y" : 50, "w": 3, "h": 420}
 		},
 
@@ -91,8 +93,7 @@
 
 		{
 			"name": "lineButtons",
-			"type": "texture",
-			"image": "settingsWindow/lineHorizontal",
+			"type": "horizontalLine",
 			"rect": { "x" : 375, "y" : 289, "w": 220, "h": 3}
 		},
 		{

+ 15 - 38
config/widgets/turnOptionsTab.json

@@ -1,5 +1,8 @@
 {
-	"library" : "config/widgets/turnOptionsDropdownLibrary.json",
+	"library" : [
+		"config/widgets/turnOptionsDropdownLibrary.json",
+		"config/widgets/commonPrimitives.json",
+	],
 
 	"customTypes" : {
 		"verticalLayout66" : {
@@ -30,10 +33,8 @@
 			"offset": {"x": 0, "y": 0}
 		},
 		"timeInputBackground" : {
-			"type": "transparentFilledRectangle",
-			"rect": {"x": 0, "y": 0, "w": 86, "h": 23},
-			"color": [0, 0, 0, 128],
-			"colorLine": [64, 80, 128, 128]
+			"type": "boxWithBackground",
+			"rect": {"x": 0, "y": 0, "w": 86, "h": 23}
 		}
 	},
 	
@@ -66,15 +67,7 @@
 			"rect": {"x": 60, "y": 48, "w": 320, "h": 0},
 			"adoptHeight": true
 		},
-		
-//		{
-//			"type": "label",
-//			"font": "medium",
-//			"alignment": "center",
-//			"color": "yellow",
-//			"text": "vcmi.optionsTab.selectPreset",
-//			"position": {"x": 105, "y": 100}
-//		},
+	
 		{
 			"type" : "dropDownTimers",
 			"name": "timerPresetSelector",
@@ -94,36 +87,20 @@
 			"color" : "blue", 
 			"rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}
 		},
+		
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x" : 64, "y" : 394, "w": 316, "h": 124},
-			"color": [0, 0, 0, 0],
-			"colorLine": [64, 80, 128, 128]
-		},
-		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x" : 65, "y" : 416, "w": 314, "h": 1},
-			"color": [0, 0, 0, 0],
-			"colorLine": [80, 96, 160, 128]
-		},
-		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x" : 65, "y" : 417, "w": 314, "h": 1},
-			"color": [0, 0, 0, 0],
-			"colorLine": [32, 40, 128, 128]
+			"type": "boxWithNoBackground",
+			"rect": {"x" : 64, "y" : 394, "w": 316, "h": 124}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x" : 65, "y" : 466, "w": 314, "h": 1},
-			"color": [0, 0, 0, 0],
-			"colorLine": [80, 96, 160, 128]
+			"type": "horizontalLine",
+			"rect": {"x" : 65, "y" : 416, "w": 314, "h": 2}
 		},
 		{
-			"type": "transparentFilledRectangle",
-			"rect": {"x" : 65, "y" : 467, "w": 314, "h": 1},
-			"color": [0, 0, 0, 0],
-			"colorLine": [32, 40, 128, 128]
+			"type": "horizontalLine",
+			"rect": {"x" : 65, "y" : 466, "w": 314, "h": 2}
 		},
+
 		{
 			"type" : "verticalLayout66",
 			"customType" : "labelTitle",

+ 6 - 2
include/vstd/RNG.h

@@ -36,14 +36,18 @@ namespace RandomGeneratorUtil
 	template<typename Container>
 	auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
 	{
-		assert(!container.empty());
+		if(container.empty())
+			throw std::runtime_error("Unable to select random item from empty container!");
+
 		return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
 	}
 
 	template<typename Container>
 	auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container))
 	{
-		assert(!container.empty());
+		if(container.empty())
+			throw std::runtime_error("Unable to select random item from empty container!");
+
 		return std::next(container.begin(), rand.getInt64Range(0, container.size() - 1)());
 	}
 

+ 5 - 2
launcher/eu.vcmi.VCMI.metainfo.xml

@@ -5,8 +5,10 @@
 	<summary>Open-source game engine for Heroes of Might and Magic III</summary>
 	<summary xml:lang="cs">Herní engine s otevřeným kódem pro Heroes of Might and Magic III</summary>
 	<summary xml:lang="de">Open-Source-Spielengine für Heroes of Might and Magic III</summary>
-	<developer_name>VCMI Team</developer_name>
-	<developer_name xml:lang="cs">Tým VCMI</developer_name>
+	<developer id="eu.vcmi">
+		<name>VCMI Team</name>
+		<name xml:lang="cs">Tým VCMI</name>
+	</developer>
 	<metadata_license>CC-BY-SA-4.0</metadata_license>
 	<project_license>GPL-2.0-or-later</project_license>
 	<description>
@@ -75,6 +77,7 @@
 			<image type="source">https://play-lh.googleusercontent.com/wD6xJK2nuzVyKUFODfQs2SEbUuSyEglojpp_FE6GeAuzdA_R44Dp6gTyN70KCwpRtD9M=w2560-h1440</image>
 		</screenshot>
 	</screenshots>
+	<launchable type="desktop-id">vcmilauncher.desktop</launchable>
 	<releases>
 		<release version="1.5.0" date="2024-03-01" type="development"/>
 		<release version="1.4.5" date="2024-01-23" type="stable"/>

+ 2 - 2
launcher/jsonutils.cpp

@@ -89,7 +89,7 @@ QVariant JsonFromFile(QString filename)
 	}
 
 	const auto data = file.readAll();
-	JsonNode node(data.data(), data.size());
+	JsonNode node(reinterpret_cast<const std::byte*>(data.data()), data.size());
 	return toVariant(node);
 }
 
@@ -116,7 +116,7 @@ JsonNode toJson(QVariant object)
 void JsonToFile(QString filename, QVariant object)
 {
 	std::fstream file(qstringToPath(filename).c_str(), std::ios::out | std::ios_base::binary);
-	file << toJson(object).toJson();
+	file << toJson(object).toString();
 }
 
 }

+ 1 - 1
launcher/updatedialog_moc.cpp

@@ -67,7 +67,7 @@ UpdateDialog::UpdateDialog(bool calledManually, QWidget *parent):
 		}
 		
 		auto byteArray = response->readAll();
-		JsonNode node(byteArray.constData(), byteArray.size());
+		JsonNode node(reinterpret_cast<const std::byte*>(byteArray.constData()), byteArray.size());
 		loadFromJson(node);
 	});
 }

+ 3 - 4
lib/CArtHandler.cpp

@@ -350,8 +350,7 @@ std::vector<JsonNode> CArtHandler::loadLegacyData()
 		{
 			if(parser.readString() == "x")
 			{
-				artData["slot"].Vector().push_back(JsonNode());
-				artData["slot"].Vector().back().String() = artSlot;
+				artData["slot"].Vector().emplace_back(artSlot);
 			}
 		}
 		artData["class"].String() = classes.at(parser.readString()[0]);
@@ -461,7 +460,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode
 	VLC->identifiers()->requestIdentifier(scope, "object", "artifact", [=](si32 index)
 	{
 		JsonNode conf;
-		conf.setMeta(scope);
+		conf.setModScope(scope);
 
 		VLC->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex());
 
@@ -469,7 +468,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode
 		{
 			JsonNode templ;
 			templ["animation"].String() = art->advMapDef;
-			templ.setMeta(scope);
+			templ.setModScope(scope);
 
 			// add new template.
 			// Necessary for objects added via mods that don't have any templates in H3

+ 1 - 1
lib/CConfigHandler.cpp

@@ -90,7 +90,7 @@ void SettingsStorage::invalidateNode(const std::vector<std::string> &changedPath
 		JsonUtils::minimize(savedConf, schema);
 
 	std::fstream file(CResourceHandler::get()->getResourceName(JsonPath::builtin(dataFilename))->c_str(), std::ofstream::out | std::ofstream::trunc);
-	file << savedConf.toJson();
+	file << savedConf.toString();
 }
 
 JsonNode & SettingsStorage::getNode(const std::vector<std::string> & path)

+ 3 - 3
lib/CCreatureHandler.cpp

@@ -418,7 +418,7 @@ void CCreatureHandler::loadCommanders()
 
 	std::string modSource = VLC->modh->findResourceOrigin(configResource);
 	JsonNode data(configResource);
-	data.setMeta(modSource);
+	data.setModScope(modSource);
 
 	const JsonNode & config = data; // switch to const data accessors
 
@@ -640,7 +640,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 	VLC->identifiers()->requestIdentifier(scope, "object", "monster", [=](si32 index)
 	{
 		JsonNode conf;
-		conf.setMeta(scope);
+		conf.setModScope(scope);
 
 		VLC->objtypeh->loadSubObject(cre->identifier, conf, Obj::MONSTER, cre->getId().num);
 		if (!advMapFile.isNull())
@@ -649,7 +649,7 @@ CCreature * CCreatureHandler::loadFromJson(const std::string & scope, const Json
 			templ["animation"] = advMapFile;
 			if (!advMapMask.isNull())
 				templ["mask"] = advMapMask;
-			templ.setMeta(scope);
+			templ.setModScope(scope);
 
 			// if creature has custom advMapFile, reset any potentially imported H3M templates and use provided file instead
 			VLC->objtypeh->getHandlerFor(Obj::MONSTER, cre->getId().num)->clearTemplates();

+ 3 - 3
lib/CHeroHandler.cpp

@@ -293,7 +293,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 	for(auto skillPair : node["secondarySkills"].Struct())
 	{
 		int probability = static_cast<int>(skillPair.second.Integer());
-		VLC->identifiers()->requestIdentifier(skillPair.second.meta, "skill", skillPair.first, [heroClass, probability](si32 skillID)
+		VLC->identifiers()->requestIdentifier(skillPair.second.getModScope(), "skill", skillPair.first, [heroClass, probability](si32 skillID)
 		{
 			heroClass->secSkillProbability[skillID] = probability;
 		});
@@ -310,7 +310,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 	{
 		int value = static_cast<int>(tavern.second.Float());
 
-		VLC->identifiers()->requestIdentifier(tavern.second.meta, "faction", tavern.first,
+		VLC->identifiers()->requestIdentifier(tavern.second.getModScope(), "faction", tavern.first,
 		[=](si32 factionID)
 		{
 			heroClass->selectionProbability[FactionID(factionID)] = value;
@@ -329,7 +329,7 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 		classConf["heroClass"].String() = identifier;
 		if (!node["compatibilityIdentifiers"].isNull())
 			classConf["compatibilityIdentifiers"] = node["compatibilityIdentifiers"];
-		classConf.setMeta(scope);
+		classConf.setModScope(scope);
 		VLC->objtypeh->loadSubObject(identifier, classConf, index, heroClass->getIndex());
 	});
 

+ 1 - 0
lib/CMakeLists.txt

@@ -405,6 +405,7 @@ set(lib_HEADERS
 	filesystem/ResourcePath.h
 
 	json/JsonBonus.h
+	json/JsonFormatException.h
 	json/JsonNode.h
 	json/JsonParser.h
 	json/JsonRandom.h

+ 10 - 10
lib/CRandomGenerator.cpp

@@ -37,25 +37,25 @@ void CRandomGenerator::resetSeed()
 
 TRandI CRandomGenerator::getIntRange(int lower, int upper)
 {
-	assert(lower <= upper);
-	return std::bind(TIntDist(lower, upper), std::ref(rand));
+	if (lower <= upper)
+		return std::bind(TIntDist(lower, upper), std::ref(rand));
+	throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper));
 }
 
 vstd::TRandI64 CRandomGenerator::getInt64Range(int64_t lower, int64_t upper)
 {
-	assert(lower <= upper);
-	return std::bind(TInt64Dist(lower, upper), std::ref(rand));
+	if(lower <= upper)
+		return std::bind(TInt64Dist(lower, upper), std::ref(rand));
+	throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper));
 }
 
 int CRandomGenerator::nextInt(int upper)
 {
-	assert(0 <= upper);
 	return getIntRange(0, upper)();
 }
 
 int CRandomGenerator::nextInt(int lower, int upper)
 {
-	assert(lower <= upper);
 	return getIntRange(lower, upper)();
 }
 
@@ -66,19 +66,19 @@ int CRandomGenerator::nextInt()
 
 vstd::TRand CRandomGenerator::getDoubleRange(double lower, double upper)
 {
-	assert(lower <= upper);
-	return std::bind(TRealDist(lower, upper), std::ref(rand));
+	if(lower <= upper)
+		return std::bind(TRealDist(lower, upper), std::ref(rand));
+	throw std::runtime_error("Invalid range provided: " + std::to_string(lower) + " ... " + std::to_string(upper));
+
 }
 
 double CRandomGenerator::nextDouble(double upper)
 {
-	assert(0 <= upper);
 	return getDoubleRange(0, upper)();
 }
 
 double CRandomGenerator::nextDouble(double lower, double upper)
 {
-	assert(lower <= upper);
 	return getDoubleRange(lower, upper)();
 }
 

+ 1 - 1
lib/CSkillHandler.cpp

@@ -168,7 +168,7 @@ std::vector<JsonNode> CSkillHandler::loadLegacyData()
 	std::vector<JsonNode> legacyData;
 	for(int id = 0; id < GameConstants::SKILL_QUANTITY; id++)
 	{
-		JsonNode skillNode(JsonNode::JsonType::DATA_STRUCT);
+		JsonNode skillNode;
 		skillNode["name"].String() = skillNames[id];
 		for(int level = 1; level < NSecondarySkill::levels.size(); level++)
 		{

+ 16 - 21
lib/CTownHandler.cpp

@@ -243,11 +243,6 @@ CTown::~CTown()
 		str.dellNull();
 }
 
-std::string CTown::getRandomNameTranslated(size_t index) const
-{
-	return VLC->generaltexth->translate(getRandomNameTextID(index));
-}
-
 std::string CTown::getRandomNameTextID(size_t index) const
 {
 	return TextIdentifier("faction", faction->modScope, faction->identifier, "randomName", index).get();
@@ -622,7 +617,7 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList
 void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, const JsonNode & source)
 {
 	assert(stringID.find(':') == std::string::npos);
-	assert(!source.meta.empty());
+	assert(!source.getModScope().empty());
 
 	auto * ret = new CBuilding();
 	ret->bid = getMappedValue<BuildingID, std::string>(stringID, BuildingID::NONE, MappedKeys::BUILDING_NAMES_TO_TYPES, false);
@@ -645,11 +640,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	ret->height = getMappedValue<CBuilding::ETowerHeight>(source["height"], CBuilding::HEIGHT_NO_TOWER, CBuilding::TOWER_TYPES);
 
 	ret->identifier = stringID;
-	ret->modScope = source.meta;
+	ret->modScope = source.getModScope();
 	ret->town = town;
 
-	VLC->generaltexth->registerString(source.meta, ret->getNameTextID(), source["name"].String());
-	VLC->generaltexth->registerString(source.meta, ret->getDescriptionTextID(), source["description"].String());
+	VLC->generaltexth->registerString(source.getModScope(), ret->getNameTextID(), source["name"].String());
+	VLC->generaltexth->registerString(source.getModScope(), ret->getDescriptionTextID(), source["description"].String());
 
 	ret->resources = TResources(source["cost"]);
 	ret->produce =   TResources(source["produce"]);
@@ -734,7 +729,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 
 	ret->town->buildings[ret->bid] = ret;
 
-	registerObject(source.meta, ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
+	registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
 }
 
 void CTownHandler::loadBuildings(CTown * town, const JsonNode & source)
@@ -756,14 +751,14 @@ void CTownHandler::loadStructure(CTown &town, const std::string & stringID, cons
 	ret->building = nullptr;
 	ret->buildable = nullptr;
 
-	VLC->identifiers()->tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
+	VLC->identifiers()->tryRequestIdentifier( source.getModScope(), "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
 	{
 		ret->building = town.buildings[BuildingID(identifier)];
 	});
 
 	if (source["builds"].isNull())
 	{
-		VLC->identifiers()->tryRequestIdentifier( source.meta, "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
+		VLC->identifiers()->tryRequestIdentifier( source.getModScope(), "building." + town.faction->getJsonKey(), stringID, [=, &town](si32 identifier) mutable
 		{
 			ret->building = town.buildings[BuildingID(identifier)];
 		});
@@ -949,7 +944,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	}
 	else
 	{
-		VLC->identifiers()->requestIdentifier( source.meta, "spell", "castleMoat", [=](si32 ability)
+		VLC->identifiers()->requestIdentifier( source.getModScope(), "spell", "castleMoat", [=](si32 ability)
 		{
 			town->moatAbility = SpellID(ability);
 		});
@@ -989,7 +984,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		int chance = static_cast<int>(node.second.Float());
 
-		VLC->identifiers()->requestIdentifier(node.second.meta, "heroClass",node.first, [=](si32 classID)
+		VLC->identifiers()->requestIdentifier(node.second.getModScope(), "heroClass",node.first, [=](si32 classID)
 		{
 			VLC->heroclassesh->objects[classID]->selectionProbability[town->faction->getId()] = chance;
 		});
@@ -999,7 +994,7 @@ void CTownHandler::loadTown(CTown * town, const JsonNode & source)
 	{
 		int chance = static_cast<int>(node.second.Float());
 
-		VLC->identifiers()->requestIdentifier(node.second.meta, "spell", node.first, [=](si32 spellID)
+		VLC->identifiers()->requestIdentifier(node.second.getModScope(), "spell", node.first, [=](si32 spellID)
 		{
 			VLC->spellh->objects.at(spellID)->probabilities[town->faction->getId()] = chance;
 		});
@@ -1125,9 +1120,9 @@ 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() = name;
-			config["faction"].meta = scope;
-			if (config.meta.empty())// MODS COMPATIBILITY FOR 0.96
-				config.meta = scope;
+			config["faction"].setModScope(scope, false);
+			if (config.getModScope().empty())// MODS COMPATIBILITY FOR 0.96
+				config.setModScope(scope, false);
 			VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
 
 			// MODS COMPATIBILITY FOR 0.96
@@ -1168,7 +1163,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() = name;
-			config["faction"].meta = scope;
+			config["faction"].setModScope(scope, false);
 			VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index);
 		});
 	}
@@ -1179,7 +1174,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod
 void CTownHandler::loadRandomFaction()
 {
 	JsonNode randomFactionJson(JsonPath::builtin("config/factions/random.json"));
-	randomFactionJson.setMeta(ModScope::scopeBuiltin(), true);
+	randomFactionJson.setModScope(ModScope::scopeBuiltin(), true);
 	loadBuildings(randomTown, randomFactionJson["random"]["town"]["buildings"]);
 }
 
@@ -1206,7 +1201,7 @@ void CTownHandler::initializeRequirements()
 			{
 				logMod->error("Unexpected length of town buildings requirements: %d", node.Vector().size());
 				logMod->error("Entry contains: ");
-				logMod->error(node.toJson());
+				logMod->error(node.toString());
 			}
 
 			auto index = VLC->identifiers()->getIdentifier(requirement.town->getBuildingScope(), node[0]);

+ 0 - 1
lib/CTownHandler.h

@@ -217,7 +217,6 @@ public:
 	void setGreeting(BuildingSubID::EBuildingSubID subID, const std::string & message) const; //may affect only mutable field
 	BuildingID getBuildingType(BuildingSubID::EBuildingSubID subID) const;
 
-	std::string getRandomNameTranslated(size_t index) const;
 	std::string getRandomNameTextID(size_t index) const;
 	size_t getRandomNamesCount() const;
 

+ 6 - 7
lib/bonuses/Bonus.cpp

@@ -26,7 +26,6 @@
 #include "../TerrainHandler.h"
 #include "../constants/StringConstants.h"
 #include "../battle/BattleInfo.h"
-#include "../json/JsonUtils.h"
 #include "../modding/ModUtility.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -72,20 +71,20 @@ si32 CAddInfo::operator[](size_type pos) const
 
 std::string CAddInfo::toString() const
 {
-	return toJsonNode().toJson(true);
+	return toJsonNode().toCompactString();
 }
 
 JsonNode CAddInfo::toJsonNode() const
 {
 	if(size() < 2)
 	{
-		return JsonUtils::intNode(operator[](0));
+		return JsonNode(operator[](0));
 	}
 	else
 	{
-		JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+		JsonNode node;
 		for(si32 value : *this)
-			node.Vector().push_back(JsonUtils::intNode(value));
+			node.Vector().emplace_back(value);
 		return node;
 	}
 }
@@ -143,7 +142,7 @@ static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
 	switch(type)
 	{
 	case BonusType::SPECIAL_UPGRADE:
-		return JsonUtils::stringNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0])));
+		return JsonNode(ModUtility::makeFullIdentifier("", "creature", CreatureID::encode(addInfo[0])));
 	default:
 		return addInfo.toJsonNode();
 	}
@@ -151,7 +150,7 @@ static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
 
 JsonNode Bonus::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 	// only add values that might reasonably be found in config files
 	root["type"].String() = vstd::findKey(bonusNameMap, type);
 	if(subtype != BonusSubtypeID())

+ 3 - 3
lib/bonuses/BonusEnum.cpp

@@ -67,13 +67,13 @@ namespace BonusDuration
 		}
 		if(durationNames.size() == 1)
 		{
-			return JsonUtils::stringNode(durationNames[0]);
+			return JsonNode(durationNames[0]);
 		}
 		else
 		{
-			JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+			JsonNode node;
 			for(const std::string & dur : durationNames)
-				node.Vector().push_back(JsonUtils::stringNode(dur));
+				node.Vector().emplace_back(dur);
 			return node;
 		}
 	}

+ 1 - 1
lib/bonuses/BonusList.cpp

@@ -213,7 +213,7 @@ int BonusList::valOfBonuses(const CSelector &select) const
 
 JsonNode BonusList::toJsonNode() const
 {
-	JsonNode node(JsonNode::JsonType::DATA_VECTOR);
+	JsonNode node;
 	for(const std::shared_ptr<Bonus> & b : bonuses)
 		node.Vector().push_back(b->toJsonNode());
 	return node;

+ 1 - 1
lib/bonuses/BonusParams.cpp

@@ -353,7 +353,7 @@ const JsonNode & BonusParams::toJson()
 			ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, *targetType);
 		jsonCreated = true;
 	}
-	ret.setMeta(ModScope::scopeGame());
+	ret.setModScope(ModScope::scopeGame());
 	return ret;
 };
 

+ 21 - 21
lib/bonuses/Limiters.cpp

@@ -92,7 +92,7 @@ std::string ILimiter::toString() const
 
 JsonNode ILimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 	root["type"].String() = toString();
 	return root;
 }
@@ -127,11 +127,11 @@ std::string CCreatureTypeLimiter::toString() const
 
 JsonNode CCreatureTypeLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_TYPE_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(creature->getJsonKey()));
-	root["parameters"].Vector().push_back(JsonUtils::boolNode(includeUpgrades));
+	root["parameters"].Vector().emplace_back(creature->getJsonKey());
+	root["parameters"].Vector().emplace_back(includeUpgrades);
 
 	return root;
 }
@@ -199,16 +199,16 @@ std::string HasAnotherBonusLimiter::toString() const
 
 JsonNode HasAnotherBonusLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 	std::string typeName = vstd::findKey(bonusNameMap, type);
 	auto sourceTypeName = vstd::findKey(bonusSourceMap, source);
 
 	root["type"].String() = "HAS_ANOTHER_BONUS_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(typeName));
+	root["parameters"].Vector().emplace_back(typeName);
 	if(isSubtypeRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(subtype.toString()));
+		root["parameters"].Vector().emplace_back(subtype.toString());
 	if(isSourceRelevant)
-		root["parameters"].Vector().push_back(JsonUtils::stringNode(sourceTypeName));
+		root["parameters"].Vector().emplace_back(sourceTypeName);
 
 	return root;
 }
@@ -233,11 +233,11 @@ UnitOnHexLimiter::UnitOnHexLimiter(const std::set<BattleHex> & applicableHexes):
 
 JsonNode UnitOnHexLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "UNIT_ON_HEXES";
 	for(const auto & hex : applicableHexes)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(hex));
+		root["parameters"].Vector().emplace_back(hex);
 
 	return root;
 }
@@ -278,11 +278,11 @@ std::string CreatureTerrainLimiter::toString() const
 
 JsonNode CreatureTerrainLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_TERRAIN_LIMITER";
 	auto terrainName = VLC->terrainTypeHandler->getById(terrainType)->getJsonKey();
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(terrainName));
+	root["parameters"].Vector().emplace_back(terrainName);
 
 	return root;
 }
@@ -324,10 +324,10 @@ std::string FactionLimiter::toString() const
 
 JsonNode FactionLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "FACTION_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getById(faction)->getJsonKey()));
+	root["parameters"].Vector().emplace_back(VLC->factions()->getById(faction)->getJsonKey());
 
 	return root;
 }
@@ -354,11 +354,11 @@ std::string CreatureLevelLimiter::toString() const
 
 JsonNode CreatureLevelLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_LEVEL_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(minLevel));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(maxLevel));
+	root["parameters"].Vector().emplace_back(minLevel);
+	root["parameters"].Vector().emplace_back(maxLevel);
 
 	return root;
 }
@@ -392,10 +392,10 @@ std::string CreatureAlignmentLimiter::toString() const
 
 JsonNode CreatureAlignmentLimiter::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "CREATURE_ALIGNMENT_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]));
+	root["parameters"].Vector().emplace_back(GameConstants::ALIGNMENT_NAMES[vstd::to_underlying(alignment)]);
 
 	return root;
 }
@@ -450,8 +450,8 @@ void AggregateLimiter::add(const TLimiterPtr & limiter)
 
 JsonNode AggregateLimiter::toJsonNode() const
 {
-	JsonNode result(JsonNode::JsonType::DATA_VECTOR);
-	result.Vector().push_back(JsonUtils::stringNode(getAggregator()));
+	JsonNode result;
+	result.Vector().emplace_back(getAggregator());
 	for(const auto & l : limiters)
 		result.Vector().push_back(l->toJsonNode());
 	return result;

+ 13 - 13
lib/bonuses/Updaters.cpp

@@ -13,7 +13,7 @@
 #include "Updaters.h"
 #include "Limiters.h"
 
-#include "../json/JsonUtils.h"
+#include "../json/JsonNode.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../CStack.h"
 
@@ -39,7 +39,7 @@ std::string IUpdater::toString() const
 
 JsonNode IUpdater::toJsonNode() const
 {
-	return JsonNode(JsonNode::JsonType::DATA_NULL);
+	return JsonNode();
 }
 
 GrowsWithLevelUpdater::GrowsWithLevelUpdater(int valPer20, int stepSize) : valPer20(valPer20), stepSize(stepSize)
@@ -69,12 +69,12 @@ std::string GrowsWithLevelUpdater::toString() const
 
 JsonNode GrowsWithLevelUpdater::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "GROWS_WITH_LEVEL";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(valPer20));
+	root["parameters"].Vector().emplace_back(valPer20);
 	if(stepSize > 1)
-		root["parameters"].Vector().push_back(JsonUtils::intNode(stepSize));
+		root["parameters"].Vector().emplace_back(stepSize);
 
 	return root;
 }
@@ -98,7 +98,7 @@ std::string TimesHeroLevelUpdater::toString() const
 
 JsonNode TimesHeroLevelUpdater::toJsonNode() const
 {
-	return JsonUtils::stringNode("TIMES_HERO_LEVEL");
+	return JsonNode("TIMES_HERO_LEVEL");
 }
 
 ArmyMovementUpdater::ArmyMovementUpdater():
@@ -141,13 +141,13 @@ std::string ArmyMovementUpdater::toString() const
 
 JsonNode ArmyMovementUpdater::toJsonNode() const
 {
-	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
+	JsonNode root;
 
 	root["type"].String() = "ARMY_MOVEMENT";
-	root["parameters"].Vector().push_back(JsonUtils::intNode(base));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(divider));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(multiplier));
-	root["parameters"].Vector().push_back(JsonUtils::intNode(max));
+	root["parameters"].Vector().emplace_back(base);
+	root["parameters"].Vector().emplace_back(divider);
+	root["parameters"].Vector().emplace_back(multiplier);
+	root["parameters"].Vector().emplace_back(max);
 
 	return root;
 }
@@ -183,7 +183,7 @@ std::string TimesStackLevelUpdater::toString() const
 
 JsonNode TimesStackLevelUpdater::toJsonNode() const
 {
-	return JsonUtils::stringNode("TIMES_STACK_LEVEL");
+	return JsonNode("TIMES_STACK_LEVEL");
 }
 
 std::string OwnerUpdater::toString() const
@@ -193,7 +193,7 @@ std::string OwnerUpdater::toString() const
 
 JsonNode OwnerUpdater::toJsonNode() const
 {
-	return JsonUtils::stringNode("BONUS_OWNER_UPDATER");
+	return JsonNode("BONUS_OWNER_UPDATER");
 }
 
 std::shared_ptr<Bonus> OwnerUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const

+ 1 - 1
lib/campaign/CampaignHandler.cpp

@@ -46,7 +46,7 @@ void CampaignHandler::readCampaign(Campaign * ret, const std::vector<ui8> & inpu
 	}
 	else // text format (json)
 	{
-		JsonNode jsonCampaign((const char*)input.data(), input.size());
+		JsonNode jsonCampaign(reinterpret_cast<const std::byte*>(input.data()), input.size());
 		readHeaderFromJson(*ret, jsonCampaign, filename, modName, encoding);
 
 		for(auto & scenario : jsonCampaign["scenarios"].Vector())

+ 5 - 0
lib/constants/EntityIdentifiers.cpp

@@ -458,6 +458,11 @@ std::string FactionID::entityType()
 	return "faction";
 }
 
+const CFaction * FactionID::toFaction() const
+{
+	return dynamic_cast<const CFaction*>(toEntity(VLC));
+}
+
 const Faction * FactionID::toEntity(const Services * service) const
 {
 	return service->factions()->getByIndex(num);

+ 2 - 0
lib/constants/EntityIdentifiers.h

@@ -24,6 +24,7 @@ class CHero;
 class CHeroClass;
 class HeroClass;
 class HeroTypeService;
+class CFaction;
 class Faction;
 class Skill;
 class RoadType;
@@ -261,6 +262,7 @@ public:
 
 	static si32 decode(const std::string& identifier);
 	static std::string encode(const si32 index);
+	const CFaction * toFaction() const;
 	const Faction * toEntity(const Services * service) const;
 	static std::string entityType();
 

+ 2 - 2
lib/filesystem/Filesystem.cpp

@@ -117,7 +117,7 @@ void CFilesystemGenerator::loadJsonMap(const std::string &mountPoint, const Json
 	if (filename)
 	{
 		auto configData = CResourceHandler::get("initial")->load(JsonPath::builtin(URI))->readAll();
-		const JsonNode configInitial(reinterpret_cast<char *>(configData.first.get()), configData.second);
+		const JsonNode configInitial(reinterpret_cast<std::byte *>(configData.first.get()), configData.second);
 		filesystem->addLoader(new CMappedFileLoader(mountPoint, configInitial), false);
 	}
 }
@@ -212,7 +212,7 @@ void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives
 {
 	auto fsConfigData = get("initial")->load(JsonPath::builtin(fsConfigURI))->readAll();
 
-	const JsonNode fsConfig(reinterpret_cast<char *>(fsConfigData.first.get()), fsConfigData.second);
+	const JsonNode fsConfig(reinterpret_cast<std::byte *>(fsConfigData.first.get()), fsConfigData.second);
 
 	addFilesystem("data", ModScope::scopeBuiltin(), createFileSystem("", fsConfig["filesystem"], extractArchives));
 }

+ 49 - 11
lib/gameState/CGameState.cpp

@@ -222,6 +222,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog
 	initHeroes();
 	initStartingBonus();
 	initTowns();
+	initTownNames();
 	placeHeroesInTowns();
 	initMapObjects();
 	buildBonusSystemTree();
@@ -763,6 +764,50 @@ void CGameState::initStartingBonus()
 	}
 }
 
+void CGameState::initTownNames()
+{
+	std::map<FactionID, std::vector<int>> availableNames;
+	for(const auto & faction : VLC->townh->getDefaultAllowed())
+	{
+		std::vector<int> potentialNames;
+		if(faction.toFaction()->town->getRandomNamesCount() > 0)
+		{
+			for(int i = 0; i < faction.toFaction()->town->getRandomNamesCount(); ++i)
+				potentialNames.push_back(i);
+
+			availableNames[faction] = potentialNames;
+		}
+	}
+
+	for(auto & vti : map->towns)
+	{
+		assert(vti->town);
+
+		if(!vti->getNameTextID().empty())
+			continue;
+
+		FactionID faction = vti->getFaction();
+
+		if(availableNames.empty())
+		{
+			logGlobal->warn("Failed to find available name for a random town!");
+			vti->setNameTextId("core.genrltxt.508"); // Unnamed
+			continue;
+		}
+
+		// If town has no available names (for example - all were picked) - pick names from some other faction that still has names available
+		if(!availableNames.count(faction))
+			faction = RandomGeneratorUtil::nextItem(availableNames, getRandomGenerator())->first;
+
+		auto nameIt = RandomGeneratorUtil::nextItem(availableNames[faction], getRandomGenerator());
+		vti->setNameTextId(faction.toFaction()->town->getRandomNameTextID(*nameIt));
+
+		availableNames[faction].erase(nameIt);
+		if(availableNames[faction].empty())
+			availableNames.erase(faction);
+	}
+}
+
 void CGameState::initTowns()
 {
 	logGlobal->debug("\tTowns");
@@ -776,20 +821,13 @@ void CGameState::initTowns()
 	map->townUniversitySkills.push_back(SecondarySkill(SecondarySkill::WATER_MAGIC));
 	map->townUniversitySkills.push_back(SecondarySkill(SecondarySkill::EARTH_MAGIC));
 
-	for (auto & elem : map->towns)
+	for (auto & vti : map->towns)
 	{
-		CGTownInstance * vti =(elem);
 		assert(vti->town);
 
-		if(vti->getNameTextID().empty())
-		{
-			size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1);
-			vti->setNameTextId(vti->getTown()->getRandomNameTextID(nameID));
-		}
-
-		static const BuildingID basicDwellings[] = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
-		static const BuildingID upgradedDwellings[] = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
-		static const BuildingID hordes[] = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7 };
+		constexpr std::array basicDwellings = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
+		constexpr std::array upgradedDwellings = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
+		constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7 };
 
 		//init buildings
 		if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings

+ 1 - 0
lib/gameState/CGameState.h

@@ -212,6 +212,7 @@ private:
 	void initFogOfWar();
 	void initStartingBonus();
 	void initTowns();
+	void initTownNames();
 	void initMapObjects();
 	void initVisitingAndGarrisonedHeroes();
 	void initCampaign();

+ 107 - 111
lib/json/JsonBonus.cpp

@@ -13,22 +13,46 @@
 
 #include "JsonValidator.h"
 
-#include "../ScopeGuard.h"
+#include "../CGeneralTextHandler.h"
+#include "../VCMI_Lib.h"
 #include "../bonuses/BonusParams.h"
-#include "../bonuses/Bonus.h"
 #include "../bonuses/Limiters.h"
 #include "../bonuses/Propagators.h"
 #include "../bonuses/Updaters.h"
-#include "../filesystem/Filesystem.h"
-#include "../modding/IdentifierStorage.h"
-#include "../VCMI_Lib.h" //for identifier resolution
-#include "../CGeneralTextHandler.h"
 #include "../constants/StringConstants.h"
-#include "../battle/BattleHex.h"
+#include "../modding/IdentifierStorage.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+VCMI_LIB_USING_NAMESPACE
+
+template <typename T>
+const T parseByMap(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
+{
+	if (!val->isNull())
+	{
+		const std::string & type = val->String();
+		auto it = map.find(type);
+		if (it == map.end())
+		{
+			logMod->error("Error: invalid %s%s.", err, type);
+			return {};
+		}
+		else
+		{
+			return it->second;
+		}
+	}
+	else
+		return {};
+}
 
-static const JsonNode nullNode;
+template <typename T>
+const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
+{
+	if(val->isNumber())
+		return static_cast<T>(val->Integer());
+	else
+		return parseByMap<T>(map, val, err);
+}
 
 static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
 {
@@ -40,14 +64,14 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
 
 	if (node.isNumber()) // Compatibility code for 1.3 or older
 	{
-		logMod->warn("Bonus subtype must be string! (%s)", node.meta);
+		logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
 		subtype = BonusCustomSubtype(node.Integer());
 		return;
 	}
 
 	if (!node.isString())
 	{
-		logMod->warn("Bonus subtype must be string! (%s)", node.meta);
+		logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
 		subtype = BonusSubtypeID();
 		return;
 	}
@@ -270,6 +294,77 @@ static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource
 	}
 }
 
+static BonusParams convertDeprecatedBonus(const JsonNode &ability)
+{
+	if(vstd::contains(deprecatedBonusSet, ability["type"].String()))
+	{
+		logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toString());
+		auto params = BonusParams(ability["type"].String(),
+											ability["subtype"].isString() ? ability["subtype"].String() : "",
+											   ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1);
+		if(params.isConverted)
+		{
+			if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special
+			{
+				params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE;
+				params.targetType = BonusSource::SECONDARY_SKILL;
+			}
+
+			logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toString());
+			return params;
+		}
+		else
+			logMod->error("Cannot convert bonus!\n%s", ability.toString());
+	}
+	BonusParams ret;
+	ret.isConverted = false;
+	return ret;
+}
+
+static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
+{
+	switch(updaterJson.getType())
+	{
+	case JsonNode::JsonType::DATA_STRING:
+		return parseByMap(bonusUpdaterMap, &updaterJson, "updater type ");
+		break;
+	case JsonNode::JsonType::DATA_STRUCT:
+		if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
+		{
+			auto updater = std::make_shared<GrowsWithLevelUpdater>();
+			const JsonVector param = updaterJson["parameters"].Vector();
+			updater->valPer20 = static_cast<int>(param[0].Integer());
+			if(param.size() > 1)
+				updater->stepSize = static_cast<int>(param[1].Integer());
+			return updater;
+		}
+		else if (updaterJson["type"].String() == "ARMY_MOVEMENT")
+		{
+			auto updater = std::make_shared<ArmyMovementUpdater>();
+			if(updaterJson["parameters"].isVector())
+			{
+				const auto & param = updaterJson["parameters"].Vector();
+				if(param.size() < 4)
+					logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!");
+				else
+				{
+					updater->base = static_cast<si32>(param.at(0).Integer());
+					updater->divider = static_cast<si32>(param.at(1).Integer());
+					updater->multiplier = static_cast<si32>(param.at(2).Integer());
+					updater->max = static_cast<si32>(param.at(3).Integer());
+				}
+				return updater;
+			}
+		}
+		else
+			logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
+		break;
+	}
+	return nullptr;
+}
+
+VCMI_LIB_NAMESPACE_BEGIN
+
 std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
 {
 	auto b = std::make_shared<Bonus>();
@@ -290,36 +385,6 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
 	return b;
 }
 
-template <typename T>
-const T parseByMap(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
-{
-	if (!val->isNull())
-	{
-		const std::string & type = val->String();
-		auto it = map.find(type);
-		if (it == map.end())
-		{
-			logMod->error("Error: invalid %s%s.", err, type);
-			return {};
-		}
-		else
-		{
-			return it->second;
-		}
-	}
-	else
-		return {};
-}
-
-template <typename T>
-const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
-{
-	if(val->isNumber())
-		return static_cast<T>(val->Integer());
-	else
-		return parseByMap<T>(map, val, err);
-}
-
 void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node)
 {
 	const JsonNode & value = node["addInfo"];
@@ -553,7 +618,7 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 	if (!parseBonus(ability, b.get()))
 	{
 		// caller code can not handle this case and presumes that returned bonus is always valid
-		logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toJson());
+		logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toString());
 		b->type = BonusType::NONE;
 		return b;
 	}
@@ -573,75 +638,6 @@ std::shared_ptr<Bonus> JsonUtils::parseBuildingBonus(const JsonNode & ability, c
 	return b;
 }
 
-static BonusParams convertDeprecatedBonus(const JsonNode &ability)
-{
-	if(vstd::contains(deprecatedBonusSet, ability["type"].String()))
-	{
-		logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson());
-		auto params = BonusParams(ability["type"].String(),
-											ability["subtype"].isString() ? ability["subtype"].String() : "",
-											   ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1);
-		if(params.isConverted)
-		{
-			if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special
-			{
-				params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE;
-				params.targetType = BonusSource::SECONDARY_SKILL;
-			}
-
-			logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson());
-			return params;
-		}
-		else
-			logMod->error("Cannot convert bonus!\n%s", ability.toJson());
-	}
-	BonusParams ret;
-	ret.isConverted = false;
-	return ret;
-}
-
-static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
-{
-	switch(updaterJson.getType())
-	{
-	case JsonNode::JsonType::DATA_STRING:
-		return parseByMap(bonusUpdaterMap, &updaterJson, "updater type ");
-		break;
-	case JsonNode::JsonType::DATA_STRUCT:
-		if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
-		{
-			auto updater = std::make_shared<GrowsWithLevelUpdater>();
-			const JsonVector param = updaterJson["parameters"].Vector();
-			updater->valPer20 = static_cast<int>(param[0].Integer());
-			if(param.size() > 1)
-				updater->stepSize = static_cast<int>(param[1].Integer());
-			return updater;
-		}
-		else if (updaterJson["type"].String() == "ARMY_MOVEMENT")
-		{
-			auto updater = std::make_shared<ArmyMovementUpdater>();
-			if(updaterJson["parameters"].isVector())
-			{
-				const auto & param = updaterJson["parameters"].Vector();
-				if(param.size() < 4)
-					logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!");
-				else
-				{
-					updater->base = static_cast<si32>(param.at(0).Integer());
-					updater->divider = static_cast<si32>(param.at(1).Integer());
-					updater->multiplier = static_cast<si32>(param.at(2).Integer());
-					updater->max = static_cast<si32>(param.at(3).Integer());
-				}
-				return updater;
-			}
-		}
-		else
-			logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
-		break;
-	}
-	return nullptr;
-}
-
 bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 {
 	const JsonNode * value = nullptr;

+ 12 - 7
lib/json/JsonBonus.h

@@ -14,15 +14,20 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+struct Bonus;
+class ILimiter;
+class CSelector;
+class CAddInfo;
+
 namespace JsonUtils
 {
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonVector & ability_vec);
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBonus(const JsonNode & ability);
-	DLL_LINKAGE std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description);
-	DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement);
-	DLL_LINKAGE std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
-	DLL_LINKAGE CSelector parseSelector(const JsonNode &ability);
-	DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node);
+	std::shared_ptr<Bonus> parseBonus(const JsonVector & ability_vec);
+	std::shared_ptr<Bonus> parseBonus(const JsonNode & ability);
+	std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description);
+	bool parseBonus(const JsonNode & ability, Bonus * placement);
+	std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
+	CSelector parseSelector(const JsonNode &ability);
+	void resolveAddInfo(CAddInfo & var, const JsonNode & node);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 20 - 0
lib/json/JsonFormatException.h

@@ -0,0 +1,20 @@
+/*
+ * JsonFormatException.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class DLL_LINKAGE JsonFormatException : public std::runtime_error
+{
+public:
+	using runtime_error::runtime_error;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 158 - 82
lib/json/JsonNode.cpp

@@ -11,12 +11,10 @@
 #include "StdInc.h"
 #include "JsonNode.h"
 
-#include "filesystem/Filesystem.h"
 #include "JsonParser.h"
 #include "JsonWriter.h"
+#include "filesystem/Filesystem.h"
 
-namespace
-{
 // to avoid duplicating const and non-const code
 template<typename Node>
 Node & resolvePointer(Node & in, const std::string & pointer)
@@ -40,68 +38,101 @@ Node & resolvePointer(Node & in, const std::string & pointer)
 
 		auto index = boost::lexical_cast<size_t>(entry);
 
-		if (in.Vector().size() > index)
+		if(in.Vector().size() > index)
 			return in.Vector()[index].resolvePointer(remainer);
 	}
 	return in[entry].resolvePointer(remainer);
 }
-}
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-using namespace JsonDetail;
+static const JsonNode nullNode;
 
 class LibClasses;
 class CModHandler;
 
-static const JsonNode nullNode;
+JsonNode::JsonNode(bool boolean)
+	: data(boolean)
+{
+}
+
+JsonNode::JsonNode(int32_t number)
+	: data(static_cast<int64_t>(number))
+{
+}
+
+JsonNode::JsonNode(uint32_t number)
+	: data(static_cast<int64_t>(number))
+{
+}
+
+JsonNode::JsonNode(int64_t number)
+	: data(number)
+{
+}
+
+JsonNode::JsonNode(double number)
+	: data(number)
+{
+}
 
-JsonNode::JsonNode(JsonType Type)
+JsonNode::JsonNode(const char * string)
+	: data(std::string(string))
 {
-	setType(Type);
 }
 
-JsonNode::JsonNode(const std::byte *data, size_t datasize)
-	:JsonNode(reinterpret_cast<const char*>(data), datasize)
-{}
+JsonNode::JsonNode(const std::string & string)
+	: data(string)
+{
+}
+
+JsonNode::JsonNode(const std::byte * data, size_t datasize)
+	: JsonNode(data, datasize, JsonParsingSettings())
+{
+}
 
-JsonNode::JsonNode(const char *data, size_t datasize)
+JsonNode::JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings)
 {
-	JsonParser parser(data, datasize);
+	JsonParser parser(data, datasize, parserSettings);
 	*this = parser.parse("<unknown>");
 }
 
 JsonNode::JsonNode(const JsonPath & fileURI)
+	:JsonNode(fileURI, JsonParsingSettings())
+{
+}
+
+JsonNode::JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings)
 {
 	auto file = CResourceHandler::get()->load(fileURI)->readAll();
 
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, parserSettings);
 	*this = parser.parse(fileURI.getName());
 }
 
-JsonNode::JsonNode(const std::string & idx, const JsonPath & fileURI)
+JsonNode::JsonNode(const JsonPath & fileURI, const std::string & idx)
 {
 	auto file = CResourceHandler::get(idx)->load(fileURI)->readAll();
-	
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+
+	JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, JsonParsingSettings());
 	*this = parser.parse(fileURI.getName());
 }
 
-JsonNode::JsonNode(const JsonPath & fileURI, bool &isValidSyntax)
+JsonNode::JsonNode(const JsonPath & fileURI, bool & isValidSyntax)
 {
 	auto file = CResourceHandler::get()->load(fileURI)->readAll();
 
-	JsonParser parser(reinterpret_cast<char*>(file.first.get()), file.second);
+	JsonParser parser(reinterpret_cast<std::byte *>(file.first.get()), file.second, JsonParsingSettings());
 	*this = parser.parse(fileURI.getName());
 	isValidSyntax = parser.isValid();
 }
 
-bool JsonNode::operator == (const JsonNode &other) const
+bool JsonNode::operator==(const JsonNode & other) const
 {
 	return data == other.data;
 }
 
-bool JsonNode::operator != (const JsonNode &other) const
+bool JsonNode::operator!=(const JsonNode & other) const
 {
 	return !(*this == other);
 }
@@ -111,25 +142,42 @@ JsonNode::JsonType JsonNode::getType() const
 	return static_cast<JsonType>(data.index());
 }
 
-void JsonNode::setMeta(const std::string & metadata, bool recursive)
+const std::string & JsonNode::getModScope() const
+{
+	return modScope;
+}
+
+void JsonNode::setOverrideFlag(bool value)
 {
-	meta = metadata;
-	if (recursive)
+	overrideFlag = value;
+}
+
+bool JsonNode::getOverrideFlag() const
+{
+	return overrideFlag;
+}
+
+void JsonNode::setModScope(const std::string & metadata, bool recursive)
+{
+	modScope = metadata;
+	if(recursive)
 	{
-		switch (getType())
+		switch(getType())
 		{
-			break; case JsonType::DATA_VECTOR:
+			break;
+			case JsonType::DATA_VECTOR:
 			{
 				for(auto & node : Vector())
 				{
-					node.setMeta(metadata);
+					node.setModScope(metadata);
 				}
 			}
-			break; case JsonType::DATA_STRUCT:
+			break;
+			case JsonType::DATA_STRUCT:
 			{
 				for(auto & node : Struct())
 				{
-					node.second.setMeta(metadata);
+					node.second.setModScope(metadata);
 				}
 			}
 		}
@@ -138,7 +186,7 @@ void JsonNode::setMeta(const std::string & metadata, bool recursive)
 
 void JsonNode::setType(JsonType Type)
 {
-	if (getType() == Type)
+	if(getType() == Type)
 		return;
 
 	//float<->int conversion
@@ -158,13 +206,27 @@ void JsonNode::setType(JsonType Type)
 	//Set new node type
 	switch(Type)
 	{
-		break; case JsonType::DATA_NULL:    data = JsonData();
-		break; case JsonType::DATA_BOOL:    data = JsonData(false);
-		break; case JsonType::DATA_FLOAT:   data = JsonData(static_cast<double>(0.0));
-		break; case JsonType::DATA_STRING:  data = JsonData(std::string());
-		break; case JsonType::DATA_VECTOR:  data = JsonData(JsonVector());
-		break; case JsonType::DATA_STRUCT:  data = JsonData(JsonMap());
-		break; case JsonType::DATA_INTEGER: data = JsonData(static_cast<si64>(0));
+		case JsonType::DATA_NULL:
+			data = JsonData();
+			break;
+		case JsonType::DATA_BOOL:
+			data = JsonData(false);
+			break;
+		case JsonType::DATA_FLOAT:
+			data = JsonData(0.0);
+			break;
+		case JsonType::DATA_STRING:
+			data = JsonData(std::string());
+			break;
+		case JsonType::DATA_VECTOR:
+			data = JsonData(JsonVector());
+			break;
+		case JsonType::DATA_STRUCT:
+			data = JsonData(JsonMap());
+			break;
+		case JsonType::DATA_INTEGER:
+			data = JsonData(static_cast<si64>(0));
+			break;
 	}
 }
 
@@ -197,18 +259,18 @@ bool JsonNode::containsBaseData() const
 {
 	switch(getType())
 	{
-	case JsonType::DATA_NULL:
-		return false;
-	case JsonType::DATA_STRUCT:
-		for(const auto & elem : Struct())
-		{
-			if(elem.second.containsBaseData())
-				return true;
-		}
-		return false;
-	default:
-		//other types (including vector) cannot be extended via merge
-		return true;
+		case JsonType::DATA_NULL:
+			return false;
+		case JsonType::DATA_STRUCT:
+			for(const auto & elem : Struct())
+			{
+				if(elem.second.containsBaseData())
+					return true;
+			}
+			return false;
+		default:
+			//other types (including vector) cannot be extended via merge
+			return true;
 	}
 }
 
@@ -216,14 +278,14 @@ bool JsonNode::isCompact() const
 {
 	switch(getType())
 	{
-	case JsonType::DATA_VECTOR:
-		for(const JsonNode & elem : Vector())
-		{
-			if(!elem.isCompact())
-				return false;
-		}
-		return true;
-	case JsonType::DATA_STRUCT:
+		case JsonType::DATA_VECTOR:
+			for(const JsonNode & elem : Vector())
+			{
+				if(!elem.isCompact())
+					return false;
+			}
+			return true;
+		case JsonType::DATA_STRUCT:
 		{
 			auto propertyCount = Struct().size();
 			if(propertyCount == 0)
@@ -231,9 +293,9 @@ bool JsonNode::isCompact() const
 			else if(propertyCount == 1)
 				return Struct().begin()->second.isCompact();
 		}
-		return false;
-	default:
-		return true;
+			return false;
+		default:
+			return true;
 	}
 }
 
@@ -253,7 +315,7 @@ bool JsonNode::TryBoolFromString(bool & success) const
 
 		if(success)
 			return true;
-		
+
 		success = boolParamStr == "false";
 	}
 	return false;
@@ -300,20 +362,22 @@ JsonMap & JsonNode::Struct()
 	return std::get<JsonMap>(data);
 }
 
-const bool boolDefault = false;
 bool JsonNode::Bool() const
 {
+	static const bool boolDefault = false;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_BOOL);
 
-	if (getType() == JsonType::DATA_BOOL)
+	if(getType() == JsonType::DATA_BOOL)
 		return std::get<bool>(data);
 
 	return boolDefault;
 }
 
-const double floatDefault = 0;
 double JsonNode::Float() const
 {
+	static const double floatDefault = 0;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
 
 	if(getType() == JsonType::DATA_FLOAT)
@@ -325,9 +389,10 @@ double JsonNode::Float() const
 	return floatDefault;
 }
 
-const si64 integerDefault = 0;
 si64 JsonNode::Integer() const
 {
+	static const si64 integerDefault = 0;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_INTEGER || getType() == JsonType::DATA_FLOAT);
 
 	if(getType() == JsonType::DATA_INTEGER)
@@ -339,34 +404,37 @@ si64 JsonNode::Integer() const
 	return integerDefault;
 }
 
-const std::string stringDefault = std::string();
 const std::string & JsonNode::String() const
 {
+	static const std::string stringDefault;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRING);
 
-	if (getType() == JsonType::DATA_STRING)
+	if(getType() == JsonType::DATA_STRING)
 		return std::get<std::string>(data);
 
 	return stringDefault;
 }
 
-const JsonVector vectorDefault = JsonVector();
 const JsonVector & JsonNode::Vector() const
 {
+	static const JsonVector vectorDefault;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_VECTOR);
 
-	if (getType() == JsonType::DATA_VECTOR)
+	if(getType() == JsonType::DATA_VECTOR)
 		return std::get<JsonVector>(data);
 
 	return vectorDefault;
 }
 
-const JsonMap mapDefault = JsonMap();
 const JsonMap & JsonNode::Struct() const
 {
+	static const JsonMap mapDefault;
+
 	assert(getType() == JsonType::DATA_NULL || getType() == JsonType::DATA_STRUCT);
 
-	if (getType() == JsonType::DATA_STRUCT)
+	if(getType() == JsonType::DATA_STRUCT)
 		return std::get<JsonMap>(data);
 
 	return mapDefault;
@@ -380,49 +448,57 @@ JsonNode & JsonNode::operator[](const std::string & child)
 const JsonNode & JsonNode::operator[](const std::string & child) const
 {
 	auto it = Struct().find(child);
-	if (it != Struct().end())
+	if(it != Struct().end())
 		return it->second;
 	return nullNode;
 }
 
 JsonNode & JsonNode::operator[](size_t child)
 {
-	if (child >= Vector().size() )
+	if(child >= Vector().size())
 		Vector().resize(child + 1);
 	return Vector()[child];
 }
 
 const JsonNode & JsonNode::operator[](size_t child) const
 {
-	if (child < Vector().size() )
+	if(child < Vector().size())
 		return Vector()[child];
 
 	return nullNode;
 }
 
-const JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer) const
+const JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer) const
 {
 	return ::resolvePointer(*this, jsonPointer);
 }
 
-JsonNode & JsonNode::resolvePointer(const std::string &jsonPointer)
+JsonNode & JsonNode::resolvePointer(const std::string & jsonPointer)
 {
 	return ::resolvePointer(*this, jsonPointer);
 }
 
-std::vector<std::byte> JsonNode::toBytes(bool compact) const
+std::vector<std::byte> JsonNode::toBytes() const
 {
-	std::string jsonString = toJson(compact);
-	auto dataBegin = reinterpret_cast<const std::byte*>(jsonString.data());
+	std::string jsonString = toString();
+	auto dataBegin = reinterpret_cast<const std::byte *>(jsonString.data());
 	auto dataEnd = dataBegin + jsonString.size();
 	std::vector<std::byte> result(dataBegin, dataEnd);
 	return result;
 }
 
-std::string JsonNode::toJson(bool compact) const
+std::string JsonNode::toCompactString() const
+{
+	std::ostringstream out;
+	JsonWriter writer(out, true);
+	writer.writeNode(*this);
+	return out.str();
+}
+
+std::string JsonNode::toString() const
 {
 	std::ostringstream out;
-	JsonWriter writer(out, compact);
+	JsonWriter writer(out, false);
 	writer.writeNode(*this);
 	return out.str();
 }

+ 108 - 110
lib/json/JsonNode.h

@@ -17,10 +17,23 @@ class JsonNode;
 using JsonMap = std::map<std::string, JsonNode>;
 using JsonVector = std::vector<JsonNode>;
 
-struct Bonus;
-class CSelector;
-class CAddInfo;
-class ILimiter;
+struct DLL_LINKAGE JsonParsingSettings
+{
+	enum class JsonFormatMode
+	{
+		JSON, // strict implementation of json format
+		JSONC, // json format that also allows comments that start from '//'
+		JSON5 // Partial support of 'json5' format
+	};
+
+	JsonFormatMode mode = JsonFormatMode::JSON5;
+
+	/// Maximum depth of elements
+	uint32_t maxDepth = 30;
+
+	/// If set to true, parser will throw on any encountered error
+	bool strict = false;
+};
 
 class DLL_LINKAGE JsonNode
 {
@@ -37,30 +50,45 @@ public:
 	};
 
 private:
-	using JsonData = std::variant<std::monostate, bool, double, std::string, JsonVector, JsonMap, si64>;
+	using JsonData = std::variant<std::monostate, bool, double, std::string, JsonVector, JsonMap, int64_t>;
 
 	JsonData data;
 
+	/// Mod-origin of this particular field
+	std::string modScope;
+
+	bool overrideFlag = false;
+
 public:
-	/// free to use metadata fields
-	std::string meta;
-	// meta-flags like override
-	std::vector<std::string> flags;
-
-	//Create empty node
-	JsonNode(JsonType Type = JsonType::DATA_NULL);
-	//Create tree from Json-formatted input
-	explicit JsonNode(const char * data, size_t datasize);
+	JsonNode() = default;
+
+	/// Create single node with specified value
+	explicit JsonNode(bool boolean);
+	explicit JsonNode(int32_t number);
+	explicit JsonNode(uint32_t number);
+	explicit JsonNode(int64_t number);
+	explicit JsonNode(double number);
+	explicit JsonNode(const char * string);
+	explicit JsonNode(const std::string & string);
+
+	/// Create tree from Json-formatted input
 	explicit JsonNode(const std::byte * data, size_t datasize);
-	//Create tree from JSON file
+	explicit JsonNode(const std::byte * data, size_t datasize, const JsonParsingSettings & parserSettings);
+
+	/// Create tree from JSON file
 	explicit JsonNode(const JsonPath & fileURI);
-	explicit JsonNode(const std::string & modName, const JsonPath & fileURI);
+	explicit JsonNode(const JsonPath & fileURI, const JsonParsingSettings & parserSettings);
+	explicit JsonNode(const JsonPath & fileURI, const std::string & modName);
 	explicit JsonNode(const JsonPath & fileURI, bool & isValidSyntax);
 
-	bool operator == (const JsonNode &other) const;
-	bool operator != (const JsonNode &other) const;
+	bool operator==(const JsonNode & other) const;
+	bool operator!=(const JsonNode & other) const;
+
+	const std::string & getModScope() const;
+	void setModScope(const std::string & metadata, bool recursive = true);
 
-	void setMeta(const std::string & metadata, bool recursive = true);
+	void setOverrideFlag(bool value);
+	bool getOverrideFlag() const;
 
 	/// Convert node to another type. Converting to nullptr will clear all data
 	void setType(JsonType Type);
@@ -114,121 +142,91 @@ public:
 	const JsonNode & operator[](const std::string & child) const;
 
 	JsonNode & operator[](size_t child);
-	const JsonNode & operator[](size_t  child) const;
+	const JsonNode & operator[](size_t child) const;
 
-	std::string toJson(bool compact = false) const;
-	std::vector<std::byte> toBytes(bool compact = false) const;
+	std::string toCompactString() const;
+	std::string toString() const;
+	std::vector<std::byte> toBytes() const;
 
-	template <typename Handler> void serialize(Handler &h)
+	template<typename Handler>
+	void serialize(Handler & h)
 	{
-		h & meta;
-		h & flags;
+		h & modScope;
+
+		if(h.version >= Handler::Version::JSON_FLAGS)
+		{
+			h & overrideFlag;
+		}
+		else
+		{
+			std::vector<std::string> oldFlags;
+			h & oldFlags;
+		}
 		h & data;
 	}
 };
 
 namespace JsonDetail
 {
-	// conversion helpers for JsonNode::convertTo (partial template function instantiation is illegal in c++)
-
-	template <typename T, int arithm>
-	struct JsonConvImpl;
-
-	template <typename T>
-	struct JsonConvImpl<T, 1>
-	{
-		static T convertImpl(const JsonNode & node)
-		{
-			return T((int)node.Float());
-		}
-	};
 
-	template <typename T>
-	struct JsonConvImpl<T, 0>
-	{
-		static T convertImpl(const JsonNode & node)
-		{
-			return T(node.Float());
-		}
-	};
+inline void convert(bool & value, const JsonNode & node)
+{
+	value = node.Bool();
+}
 
-	template<typename Type>
-	struct JsonConverter
-	{
-		static Type convert(const JsonNode & node)
-		{
-			///this should be triggered only for numeric types and enums
-			static_assert(std::is_arithmetic_v<Type> || std::is_enum_v<Type> || std::is_class_v<Type>, "Unsupported type for JsonNode::convertTo()!");
-			return JsonConvImpl<Type, std::is_enum_v<Type> || std::is_class_v<Type> >::convertImpl(node);
+template<typename T>
+auto convert(T & value, const JsonNode & node) -> std::enable_if_t<std::is_integral_v<T>>
+{
+	value = node.Integer();
+}
 
-		}
-	};
+template<typename T>
+auto convert(T & value, const JsonNode & node) -> std::enable_if_t<std::is_floating_point_v<T>>
+{
+	value = node.Float();
+}
 
-	template<typename Type>
-	struct JsonConverter<std::map<std::string, Type> >
-	{
-		static std::map<std::string, Type> convert(const JsonNode & node)
-		{
-			std::map<std::string, Type> ret;
-			for (const JsonMap::value_type & entry : node.Struct())
-			{
-				ret.insert(entry.first, entry.second.convertTo<Type>());
-			}
-			return ret;
-		}
-	};
+inline void convert(std::string & value, const JsonNode & node)
+{
+	value = node.String();
+}
 
-	template<typename Type>
-	struct JsonConverter<std::set<Type> >
-	{
-		static std::set<Type> convert(const JsonNode & node)
-		{
-			std::set<Type> ret;
-			for(const JsonVector::value_type & entry : node.Vector())
-			{
-				ret.insert(entry.convertTo<Type>());
-			}
-			return ret;
-		}
-	};
+template<typename Type>
+void convert(std::map<std::string, Type> & value, const JsonNode & node)
+{
+	value.clear();
+	for(const JsonMap::value_type & entry : node.Struct())
+		value.insert(entry.first, entry.second.convertTo<Type>());
+}
 
-	template<typename Type>
-	struct JsonConverter<std::vector<Type> >
+template<typename Type>
+void convert(std::set<Type> & value, const JsonNode & node)
+{
+	value.clear();
+	for(const JsonVector::value_type & entry : node.Vector())
 	{
-		static std::vector<Type> convert(const JsonNode & node)
-		{
-			std::vector<Type> ret;
-			for (const JsonVector::value_type & entry: node.Vector())
-			{
-				ret.push_back(entry.convertTo<Type>());
-			}
-			return ret;
-		}
-	};
+		value.insert(entry.convertTo<Type>());
+	}
+}
 
-	template<>
-	struct JsonConverter<std::string>
+template<typename Type>
+void convert(std::vector<Type> & value, const JsonNode & node)
+{
+	value.clear();
+	for(const JsonVector::value_type & entry : node.Vector())
 	{
-		static std::string convert(const JsonNode & node)
-		{
-			return node.String();
-		}
-	};
+		value.push_back(entry.convertTo<Type>());
+	}
+}
 
-	template<>
-	struct JsonConverter<bool>
-	{
-		static bool convert(const JsonNode & node)
-		{
-			return node.Bool();
-		}
-	};
 }
 
 template<typename Type>
 Type JsonNode::convertTo() const
 {
-	return JsonDetail::JsonConverter<Type>::convert(*this);
+	Type result;
+	JsonDetail::convert(result, *this);
+	return result;
 }
 
 VCMI_LIB_NAMESPACE_END

+ 258 - 127
lib/json/JsonParser.cpp

@@ -11,15 +11,19 @@
 #include "StdInc.h"
 #include "JsonParser.h"
 
+#include "../ScopeGuard.h"
 #include "../TextOperations.h"
+#include "JsonFormatException.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-JsonParser::JsonParser(const char * inputString, size_t stringSize):
-	input(inputString, stringSize),
-	lineCount(1),
-	lineStart(0),
-	pos(0)
+JsonParser::JsonParser(const std::byte * inputString, size_t stringSize, const JsonParsingSettings & settings)
+	: settings(settings)
+	, input(reinterpret_cast<const char *>(inputString), stringSize)
+	, lineCount(1)
+	, currentDepth(0)
+	, lineStart(0)
+	, pos(0)
 {
 }
 
@@ -27,24 +31,29 @@ JsonNode JsonParser::parse(const std::string & fileName)
 {
 	JsonNode root;
 
-	if (input.size() == 0)
+	if(input.empty())
 	{
 		error("File is empty", false);
 	}
 	else
 	{
-		if (!TextOperations::isValidUnicodeString(&input[0], input.size()))
+		if(!TextOperations::isValidUnicodeString(input.data(), input.size()))
 			error("Not a valid UTF-8 file", false);
 
+		// If file starts with BOM - skip it
+		uint32_t firstCharacter = TextOperations::getUnicodeCodepoint(input.data(), input.size());
+		if (firstCharacter == 0xFEFF)
+			pos += TextOperations::getUnicodeCharacterSize(input[0]);
+
 		extractValue(root);
 		extractWhitespace(false);
 
 		//Warn if there are any non-whitespace symbols left
-		if (pos < input.size())
+		if(pos < input.size())
 			error("Not all file was parsed!", true);
 	}
 
-	if (!errors.empty())
+	if(!errors.empty())
 	{
 		logMod->warn("File %s is not a valid JSON file!", fileName);
 		logMod->warn(errors);
@@ -59,33 +68,43 @@ bool JsonParser::isValid()
 
 bool JsonParser::extractSeparator()
 {
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
-	if ( input[pos] !=':')
+	if(input[pos] != ':')
 		return error("Separator expected");
 
 	pos++;
 	return true;
 }
 
-bool JsonParser::extractValue(JsonNode &node)
+bool JsonParser::extractValue(JsonNode & node)
 {
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
-	switch (input[pos])
+	switch(input[pos])
 	{
-		case '\"': return extractString(node);
-		case 'n' : return extractNull(node);
-		case 't' : return extractTrue(node);
-		case 'f' : return extractFalse(node);
-		case '{' : return extractStruct(node);
-		case '[' : return extractArray(node);
-		case '-' : return extractFloat(node);
+		case '\"':
+		case '\'':
+			return extractString(node);
+		case 'n':
+			return extractNull(node);
+		case 't':
+			return extractTrue(node);
+		case 'f':
+			return extractFalse(node);
+		case '{':
+			return extractStruct(node);
+		case '[':
+			return extractArray(node);
+		case '-':
+		case '+':
+		case '.':
+			return extractFloat(node);
 		default:
 		{
-			if (input[pos] >= '0' && input[pos] <= '9')
+			if(input[pos] >= '0' && input[pos] <= '9')
 				return extractFloat(node);
 			return error("Value expected!");
 		}
@@ -94,88 +113,127 @@ bool JsonParser::extractValue(JsonNode &node)
 
 bool JsonParser::extractWhitespace(bool verbose)
 {
-	while (true)
+	//TODO: JSON5 - C-style multi-line comments
+	//TODO: JSON5 - Additional white space characters are allowed
+
+	while(true)
 	{
 		while(pos < input.size() && static_cast<ui8>(input[pos]) <= ' ')
 		{
-			if (input[pos] == '\n')
+			if(input[pos] == '\n')
 			{
 				lineCount++;
-				lineStart = pos+1;
+				lineStart = pos + 1;
 			}
 			pos++;
 		}
-		if (pos >= input.size() || input[pos] != '/')
+
+		if(pos >= input.size() || input[pos] != '/')
 			break;
 
+		if(settings.mode == JsonParsingSettings::JsonFormatMode::JSON)
+			error("Comments are not permitted in json!", true);
+
 		pos++;
-		if (pos == input.size())
+		if(pos == input.size())
 			break;
-		if (input[pos] == '/')
+		if(input[pos] == '/')
 			pos++;
 		else
 			error("Comments must consist of two slashes!", true);
 
-		while (pos < input.size() && input[pos] != '\n')
+		while(pos < input.size() && input[pos] != '\n')
 			pos++;
 	}
 
-	if (pos >= input.size() && verbose)
+	if(pos >= input.size() && verbose)
 		return error("Unexpected end of file!");
 	return true;
 }
 
-bool JsonParser::extractEscaping(std::string &str)
+bool JsonParser::extractEscaping(std::string & str)
 {
+	// TODO: support unicode escaping:
+	// \u1234
+
 	switch(input[pos])
 	{
-		break; case '\"': str += '\"';
-		break; case '\\': str += '\\';
-		break; case 'b': str += '\b';
-		break; case 'f': str += '\f';
-		break; case 'n': str += '\n';
-		break; case 'r': str += '\r';
-		break; case 't': str += '\t';
-		break; case '/': str += '/';
-		break; default: return error("Unknown escape sequence!", true);
+		case '\"':
+			str += '\"';
+			break;
+		case '\\':
+			str += '\\';
+			break;
+		case 'b':
+			str += '\b';
+			break;
+		case 'f':
+			str += '\f';
+			break;
+		case 'n':
+			str += '\n';
+			break;
+		case 'r':
+			str += '\r';
+			break;
+		case 't':
+			str += '\t';
+			break;
+		case '/':
+			str += '/';
+			break;
+		default:
+			return error("Unknown escape sequence!", true);
 	}
 	return true;
 }
 
-bool JsonParser::extractString(std::string &str)
+bool JsonParser::extractString(std::string & str)
 {
-	if (input[pos] != '\"')
-		return error("String expected!");
+	//TODO: JSON5 - line breaks escaping
+
+	if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+	{
+		if(input[pos] != '\"')
+			return error("String expected!");
+	}
+	else
+	{
+		if(input[pos] != '\"' && input[pos] != '\'')
+			return error("String expected!");
+	}
+
+	char lineTerminator = input[pos];
 	pos++;
 
 	size_t first = pos;
 
-	while (pos != input.size())
+	while(pos != input.size())
 	{
-		if (input[pos] == '\"') // Correct end of string
+		if(input[pos] == lineTerminator) // Correct end of string
 		{
-			str.append( &input[first], pos-first);
+			str.append(&input[first], pos - first);
 			pos++;
 			return true;
 		}
-		if (input[pos] == '\\') // Escaping
+		if(input[pos] == '\\') // Escaping
 		{
-			str.append( &input[first], pos-first);
+			str.append(&input[first], pos - first);
 			pos++;
-			if (pos == input.size())
+			if(pos == input.size())
 				break;
 			extractEscaping(str);
 			first = pos + 1;
 		}
-		if (input[pos] == '\n') // end-of-line
+		if(input[pos] == '\n') // end-of-line
 		{
-			str.append( &input[first], pos-first);
+			str.append(&input[first], pos - first);
 			return error("Closing quote not found!", true);
 		}
 		if(static_cast<unsigned char>(input[pos]) < ' ') // control character
 		{
-			str.append( &input[first], pos-first);
-			first = pos+1;
+			str.append(&input[first], pos - first);
+			first = pos + 1;
 			error("Illegal character in the string!", true);
 		}
 		pos++;
@@ -183,10 +241,10 @@ bool JsonParser::extractString(std::string &str)
 	return error("Unterminated string!");
 }
 
-bool JsonParser::extractString(JsonNode &node)
+bool JsonParser::extractString(JsonNode & node)
 {
 	std::string str;
-	if (!extractString(str))
+	if(!extractString(str))
 		return false;
 
 	node.setType(JsonNode::JsonType::DATA_STRING);
@@ -194,97 +252,146 @@ bool JsonParser::extractString(JsonNode &node)
 	return true;
 }
 
-bool JsonParser::extractLiteral(const std::string &literal)
+bool JsonParser::extractLiteral(std::string & literal)
 {
-	if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0)
+	while(pos < input.size())
 	{
-		while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z')
-								   || (input[pos]>'A' && input[pos]<'Z')))
-			pos++;
-		return error("Unknown literal found", true);
+		bool isUpperCase = input[pos] >= 'A' && input[pos] <= 'Z';
+		bool isLowerCase = input[pos] >= 'a' && input[pos] <= 'z';
+		bool isNumber = input[pos] >= '0' && input[pos] <= '9';
+
+		if(!isUpperCase && !isLowerCase && !isNumber)
+			break;
+
+		literal += input[pos];
+		pos++;
+	}
+
+	return true;
+}
+
+bool JsonParser::extractAndCompareLiteral(const std::string & expectedLiteral)
+{
+	std::string literal;
+	if(!extractLiteral(literal))
+		return false;
+
+	if(literal != expectedLiteral)
+	{
+		return error("Expected " + expectedLiteral + ", but unknown literal found", true);
+		return false;
 	}
 
-	pos += literal.size();
 	return true;
 }
 
-bool JsonParser::extractNull(JsonNode &node)
+bool JsonParser::extractNull(JsonNode & node)
 {
-	if (!extractLiteral("null"))
+	if(!extractAndCompareLiteral("null"))
 		return false;
 
 	node.clear();
 	return true;
 }
 
-bool JsonParser::extractTrue(JsonNode &node)
+bool JsonParser::extractTrue(JsonNode & node)
 {
-	if (!extractLiteral("true"))
+	if(!extractAndCompareLiteral("true"))
 		return false;
 
 	node.Bool() = true;
 	return true;
 }
 
-bool JsonParser::extractFalse(JsonNode &node)
+bool JsonParser::extractFalse(JsonNode & node)
 {
-	if (!extractLiteral("false"))
+	if(!extractAndCompareLiteral("false"))
 		return false;
 
 	node.Bool() = false;
 	return true;
 }
 
-bool JsonParser::extractStruct(JsonNode &node)
+bool JsonParser::extractStruct(JsonNode & node)
 {
 	node.setType(JsonNode::JsonType::DATA_STRUCT);
+
+	if(currentDepth > settings.maxDepth)
+		error("Maximum allowed depth of json structure has been reached", true);
+
 	pos++;
+	currentDepth++;
+	auto guard = vstd::makeScopeGuard([this]()
+	{
+		currentDepth--;
+	});
 
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
 	//Empty struct found
-	if (input[pos] == '}')
+	if(input[pos] == '}')
 	{
 		pos++;
 		return true;
 	}
 
-	while (true)
+	while(true)
 	{
-		if (!extractWhitespace())
+		if(!extractWhitespace())
 			return false;
 
+		bool overrideFlag = false;
 		std::string key;
-		if (!extractString(key))
-			return false;
 
-		// split key string into actual key and meta-flags
-		std::vector<std::string> keyAndFlags;
-		boost::split(keyAndFlags, key, boost::is_any_of("#"));
-		key = keyAndFlags[0];
-		// check for unknown flags - helps with debugging
-		std::vector<std::string> knownFlags = { "override" };
-		for(int i = 1; i < keyAndFlags.size(); i++)
+		if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+		{
+			if(!extractString(key))
+				return false;
+		}
+		else
+		{
+			if(input[pos] == '\'' || input[pos] == '\"')
+			{
+				if(!extractString(key))
+					return false;
+			}
+			else
+			{
+				if(!extractLiteral(key))
+					return false;
+			}
+		}
+
+		if(key.find('#') != std::string::npos)
 		{
-			if(!vstd::contains(knownFlags, keyAndFlags[i]))
-				error("Encountered unknown flag #" + keyAndFlags[i], true);
+			// split key string into actual key and meta-flags
+			std::vector<std::string> keyAndFlags;
+			boost::split(keyAndFlags, key, boost::is_any_of("#"));
+
+			key = keyAndFlags[0];
+
+			for(int i = 1; i < keyAndFlags.size(); i++)
+			{
+				if(keyAndFlags[i] == "override")
+					overrideFlag = true;
+				else
+					error("Encountered unknown flag #" + keyAndFlags[i], true);
+			}
 		}
 
-		if (node.Struct().find(key) != node.Struct().end())
+		if(node.Struct().find(key) != node.Struct().end())
 			error("Duplicate element encountered!", true);
 
-		if (!extractSeparator())
+		if(!extractSeparator())
 			return false;
 
-		if (!extractElement(node.Struct()[key], '}'))
+		if(!extractElement(node.Struct()[key], '}'))
 			return false;
 
-		// flags from key string belong to referenced element
-		for(int i = 1; i < keyAndFlags.size(); i++)
-			node.Struct()[key].flags.push_back(keyAndFlags[i]);
+		node.Struct()[key].setOverrideFlag(overrideFlag);
 
-		if (input[pos] == '}')
+		if(input[pos] == '}')
 		{
 			pos++;
 			return true;
@@ -292,31 +399,40 @@ bool JsonParser::extractStruct(JsonNode &node)
 	}
 }
 
-bool JsonParser::extractArray(JsonNode &node)
+bool JsonParser::extractArray(JsonNode & node)
 {
+	if(currentDepth > settings.maxDepth)
+		error("Macimum allowed depth of json structure has been reached", true);
+
+	currentDepth++;
+	auto guard = vstd::makeScopeGuard([this]()
+	{
+		currentDepth--;
+	});
+
 	pos++;
 	node.setType(JsonNode::JsonType::DATA_VECTOR);
 
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
 	//Empty array found
-	if (input[pos] == ']')
+	if(input[pos] == ']')
 	{
 		pos++;
 		return true;
 	}
 
-	while (true)
+	while(true)
 	{
 		//NOTE: currently 50% of time is this vector resizing.
 		//May be useful to use list during parsing and then swap() all items to vector
-		node.Vector().resize(node.Vector().size()+1);
+		node.Vector().resize(node.Vector().size() + 1);
 
-		if (!extractElement(node.Vector().back(), ']'))
+		if(!extractElement(node.Vector().back(), ']'))
 			return false;
 
-		if (input[pos] == ']')
+		if(input[pos] == ']')
 		{
 			pos++;
 			return true;
@@ -324,74 +440,87 @@ bool JsonParser::extractArray(JsonNode &node)
 	}
 }
 
-bool JsonParser::extractElement(JsonNode &node, char terminator)
+bool JsonParser::extractElement(JsonNode & node, char terminator)
 {
-	if (!extractValue(node))
+	if(!extractValue(node))
 		return false;
 
-	if (!extractWhitespace())
+	if(!extractWhitespace())
 		return false;
 
 	bool comma = (input[pos] == ',');
-	if (comma )
+	if(comma)
 	{
 		pos++;
-		if (!extractWhitespace())
+		if(!extractWhitespace())
 			return false;
 	}
 
-	if (input[pos] == terminator)
+	if(input[pos] == terminator)
 	{
-		//FIXME: MOD COMPATIBILITY: Too many of these right now, re-enable later
-		//if (comma)
-			//error("Extra comma found!", true);
+		if(comma && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+			error("Extra comma found!", true);
+
 		return true;
 	}
 
-	if (!comma)
+	if(!comma)
 		error("Comma expected!", true);
 
 	return true;
 }
 
-bool JsonParser::extractFloat(JsonNode &node)
+bool JsonParser::extractFloat(JsonNode & node)
 {
+	//TODO: JSON5 - hexacedimal support
+	//TODO: JSON5 - Numbers may be IEEE 754 positive infinity, negative infinity, and NaN (why?)
+
 	assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9'));
-	bool negative=false;
-	double result=0;
+	bool negative = false;
+	double result = 0;
 	si64 integerPart = 0;
 	bool isFloat = false;
 
-	if (input[pos] == '-')
+	if(input[pos] == '+')
+	{
+		if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+			error("Positive numbers should not have plus sign!", true);
+		pos++;
+	}
+	else if(input[pos] == '-')
 	{
 		pos++;
 		negative = true;
 	}
 
-	if (input[pos] < '0' || input[pos] > '9')
-		return error("Number expected!");
+	if(input[pos] < '0' || input[pos] > '9')
+	{
+		if(input[pos] != '.' && settings.mode < JsonParsingSettings::JsonFormatMode::JSON5)
+			return error("Number expected!");
+	}
 
 	//Extract integer part
-	while (input[pos] >= '0' && input[pos] <= '9')
+	while(input[pos] >= '0' && input[pos] <= '9')
 	{
-		integerPart = integerPart*10+(input[pos]-'0');
+		integerPart = integerPart * 10 + (input[pos] - '0');
 		pos++;
 	}
 
 	result = static_cast<double>(integerPart);
 
-	if (input[pos] == '.')
+	if(input[pos] == '.')
 	{
 		//extract fractional part
 		isFloat = true;
 		pos++;
 		double fractMult = 0.1;
-		if (input[pos] < '0' || input[pos] > '9')
+
+		if(settings.mode < JsonParsingSettings::JsonFormatMode::JSON5 && (input[pos] < '0' || input[pos] > '9'))
 			return error("Decimal part expected!");
 
-		while (input[pos] >= '0' && input[pos] <= '9')
+		while(input[pos] >= '0' && input[pos] <= '9')
 		{
-			result = result + fractMult*(input[pos]-'0');
+			result = result + fractMult * (input[pos] - '0');
 			fractMult /= 10;
 			pos++;
 		}
@@ -415,12 +544,12 @@ bool JsonParser::extractFloat(JsonNode &node)
 			pos++;
 		}
 
-		if (input[pos] < '0' || input[pos] > '9')
+		if(input[pos] < '0' || input[pos] > '9')
 			return error("Exponential part expected!");
 
-		while (input[pos] >= '0' && input[pos] <= '9')
+		while(input[pos] >= '0' && input[pos] <= '9')
 		{
-			power = power*10 + (input[pos]-'0');
+			power = power * 10 + (input[pos] - '0');
 			pos++;
 		}
 
@@ -450,13 +579,15 @@ bool JsonParser::extractFloat(JsonNode &node)
 	return true;
 }
 
-bool JsonParser::error(const std::string &message, bool warning)
+bool JsonParser::error(const std::string & message, bool warning)
 {
+	if(settings.strict)
+		throw JsonFormatException(message);
+
 	std::ostringstream stream;
-	std::string type(warning?" warning: ":" error: ");
+	std::string type(warning ? " warning: " : " error: ");
 
-	stream << "At line " << lineCount << ", position "<<pos-lineStart
-		   << type << message <<"\n";
+	stream << "At line " << lineCount << ", position " << pos - lineStart << type << message << "\n";
 	errors += stream.str();
 
 	return warning;

+ 23 - 45
lib/json/JsonParser.h

@@ -13,64 +13,42 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-//Tiny string class that uses const char* as data for speed, members are private
-//for ease of debugging and some compatibility with std::string
-class constString
-{
-	const char *data;
-	const size_t datasize;
-
-public:
-	constString(const char * inputString, size_t stringSize):
-		data(inputString),
-		datasize(stringSize)
-	{
-	}
-
-	inline size_t size() const
-	{
-		return datasize;
-	};
-
-	inline const char& operator[] (size_t position)
-	{
-		assert (position < datasize);
-		return data[position];
-	}
-};
-
 //Internal class for string -> JsonNode conversion
 class JsonParser
 {
-	std::string errors;     // Contains description of all encountered errors
-	constString input;      // Input data
-	ui32 lineCount; // Currently parsed line, starting from 1
-	size_t lineStart;       // Position of current line start
-	size_t pos;             // Current position of parser
+	const JsonParsingSettings settings;
+
+	std::string errors; // Contains description of all encountered errors
+	std::string_view input; // Input data
+	uint32_t lineCount; // Currently parsed line, starting from 1
+	uint32_t currentDepth;
+	size_t lineStart; // Position of current line start
+	size_t pos; // Current position of parser
 
 	//Helpers
-	bool extractEscaping(std::string &str);
-	bool extractLiteral(const std::string &literal);
-	bool extractString(std::string &string);
+	bool extractEscaping(std::string & str);
+	bool extractLiteral(std::string & literal);
+	bool extractAndCompareLiteral(const std::string & expectedLiteral);
+	bool extractString(std::string & string);
 	bool extractWhitespace(bool verbose = true);
 	bool extractSeparator();
-	bool extractElement(JsonNode &node, char terminator);
+	bool extractElement(JsonNode & node, char terminator);
 
 	//Methods for extracting JSON data
-	bool extractArray(JsonNode &node);
-	bool extractFalse(JsonNode &node);
-	bool extractFloat(JsonNode &node);
-	bool extractNull(JsonNode &node);
-	bool extractString(JsonNode &node);
-	bool extractStruct(JsonNode &node);
-	bool extractTrue(JsonNode &node);
-	bool extractValue(JsonNode &node);
+	bool extractArray(JsonNode & node);
+	bool extractFalse(JsonNode & node);
+	bool extractFloat(JsonNode & node);
+	bool extractNull(JsonNode & node);
+	bool extractString(JsonNode & node);
+	bool extractStruct(JsonNode & node);
+	bool extractTrue(JsonNode & node);
+	bool extractValue(JsonNode & node);
 
 	//Add error\warning message to list
-	bool error(const std::string &message, bool warning=false);
+	bool error(const std::string & message, bool warning = false);
 
 public:
-	JsonParser(const char * inputString, size_t stringSize);
+	JsonParser(const std::byte * inputString, size_t stringSize, const JsonParsingSettings & settings);
 
 	/// do actual parsing. filename is name of file that will printed to console if any errors were found
 	JsonNode parse(const std::string & fileName);

+ 4 - 4
lib/json/JsonRandom.cpp

@@ -235,9 +235,9 @@ VCMI_LIB_NAMESPACE_BEGIN
 					filteredAnyOf.insert(subset.begin(), subset.end());
 				}
 
-				vstd::erase_if(filteredTypes, [&](const IdentifierType & value)
+				vstd::erase_if(filteredTypes, [&filteredAnyOf](const IdentifierType & filteredValue)
 				{
-					return filteredAnyOf.count(value) == 0;
+					return filteredAnyOf.count(filteredValue) == 0;
 				});
 			}
 
@@ -321,7 +321,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 		{
 			for(const auto & pair : value.Struct())
 			{
-				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.meta, pair.first, variables);
+				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.getModScope(), pair.first, variables);
 				ret[id.getNum()] += loadValue(pair.second, rng, variables);
 			}
 		}
@@ -357,7 +357,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 		{
 			for(const auto & pair : value.Struct())
 			{
-				SecondarySkill id = decodeKey<SecondarySkill>(pair.second.meta, pair.first, variables);
+				SecondarySkill id = decodeKey<SecondarySkill>(pair.second.getModScope(), pair.first, variables);
 				ret[id] = loadValue(pair.second, rng, variables);
 			}
 		}

+ 3 - 3
lib/json/JsonRandom.h

@@ -9,9 +9,9 @@
  */
 #pragma once
 
+#include "GameCallbackHolder.h"
 #include "GameConstants.h"
 #include "ResourceSet.h"
-#include "GameCallbackHolder.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -23,7 +23,7 @@ struct Bonus;
 struct Component;
 class CStackBasicDescriptor;
 
-class DLL_LINKAGE JsonRandom : public GameCallbackHolder
+class JsonRandom : public GameCallbackHolder
 {
 public:
 	using Variables = std::map<std::string, int>;
@@ -46,7 +46,7 @@ private:
 public:
 	using GameCallbackHolder::GameCallbackHolder;
 
-	struct DLL_LINKAGE RandomStackInfo
+	struct RandomStackInfo
 	{
 		std::vector<const CCreature *> allowedCreatures;
 		si32 minAmount;

+ 12 - 151
lib/json/JsonUtils.cpp

@@ -13,38 +13,12 @@
 
 #include "JsonValidator.h"
 
-#include "../ScopeGuard.h"
-#include "../bonuses/BonusParams.h"
-#include "../bonuses/Bonus.h"
-#include "../bonuses/Limiters.h"
-#include "../bonuses/Propagators.h"
-#include "../bonuses/Updaters.h"
 #include "../filesystem/Filesystem.h"
-#include "../modding/IdentifierStorage.h"
-#include "../VCMI_Lib.h" //for identifier resolution
-#include "../CGeneralTextHandler.h"
-#include "../constants/StringConstants.h"
-#include "../battle/BattleHex.h"
 
-VCMI_LIB_NAMESPACE_BEGIN
+VCMI_LIB_USING_NAMESPACE
 
 static const JsonNode nullNode;
 
-//returns first Key with value equal to given one
-template<class Key, class Val>
-Key reverseMapFirst(const Val & val, const std::map<Key, Val> & map)
-{
-	for(auto it : map)
-	{
-		if(it.second == val)
-		{
-			return it.first;
-		}
-	}
-	assert(0);
-	return "";
-}
-
 static JsonNode getDefaultValue(const JsonNode & schema, std::string fieldName)
 {
 	const JsonNode & fieldProps = schema["properties"][fieldName];
@@ -76,8 +50,8 @@ static void eraseOptionalNodes(JsonNode & node, const JsonNode & schema)
 	for(const auto & entry : schema["required"].Vector())
 		foundEntries.insert(entry.String());
 
-	vstd::erase_if(node.Struct(), [&](const auto & node){
-		return !vstd::contains(foundEntries, node.first);
+	vstd::erase_if(node.Struct(), [&foundEntries](const auto & structEntry){
+		return !vstd::contains(foundEntries, structEntry.first);
 	});
 }
 
@@ -117,6 +91,8 @@ static void maximizeNode(JsonNode & node, const JsonNode & schema)
 	eraseOptionalNodes(node, schema);
 }
 
+VCMI_LIB_NAMESPACE_BEGIN
+
 void JsonUtils::minimize(JsonNode & node, const std::string & schemaName)
 {
 	minimizeNode(node, getSchema(schemaName));
@@ -129,12 +105,13 @@ void JsonUtils::maximize(JsonNode & node, const std::string & schemaName)
 
 bool JsonUtils::validate(const JsonNode & node, const std::string & schemaName, const std::string & dataName)
 {
-	std::string log = Validation::check(schemaName, node);
+	JsonValidator validator;
+	std::string log = validator.check(schemaName, node);
 	if (!log.empty())
 	{
 		logMod->warn("Data in %s is invalid!", dataName);
 		logMod->warn(log);
-		logMod->trace("%s json: %s", dataName, node.toJson(true));
+		logMod->trace("%s json: %s", dataName, node.toCompactString());
 	}
 	return log.empty();
 }
@@ -223,14 +200,14 @@ void JsonUtils::merge(JsonNode & dest, JsonNode & source, bool ignoreOverride, b
 		}
 		case JsonNode::JsonType::DATA_STRUCT:
 		{
-			if(!ignoreOverride && vstd::contains(source.flags, "override"))
+			if(!ignoreOverride && source.getOverrideFlag())
 			{
 				std::swap(dest, source);
 			}
 			else
 			{
 				if (copyMeta)
-					dest.meta = source.meta;
+					dest.setModScope(source.getModScope(), false);
 
 				//recursively merge all entries from struct
 				for(auto & node : source.Struct())
@@ -253,90 +230,6 @@ void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base)
 	std::swap(descendant, inheritedNode);
 }
 
-JsonNode JsonUtils::intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty)
-{
-	if(nodes.empty())
-		return nullNode;
-
-	JsonNode result = nodes[0];
-	for(int i = 1; i < nodes.size(); i++)
-	{
-		if(result.isNull())
-			break;
-		result = JsonUtils::intersect(result, nodes[i], pruneEmpty);
-	}
-	return result;
-}
-
-JsonNode JsonUtils::intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty)
-{
-	if(a.getType() == JsonNode::JsonType::DATA_STRUCT && b.getType() == JsonNode::JsonType::DATA_STRUCT)
-	{
-		// intersect individual properties
-		JsonNode result(JsonNode::JsonType::DATA_STRUCT);
-		for(const auto & property : a.Struct())
-		{
-			if(vstd::contains(b.Struct(), property.first))
-			{
-				JsonNode propertyIntersect = JsonUtils::intersect(property.second, b.Struct().find(property.first)->second);
-				if(pruneEmpty && !propertyIntersect.containsBaseData())
-					continue;
-				result[property.first] = propertyIntersect;
-			}
-		}
-		return result;
-	}
-	else
-	{
-		// not a struct - same or different, no middle ground
-		if(a == b)
-			return a;
-	}
-	return nullNode;
-}
-
-JsonNode JsonUtils::difference(const JsonNode & node, const JsonNode & base)
-{
-	auto addsInfo = [](JsonNode diff) -> bool
-	{
-		switch(diff.getType())
-		{
-		case JsonNode::JsonType::DATA_NULL:
-			return false;
-		case JsonNode::JsonType::DATA_STRUCT:
-			return !diff.Struct().empty();
-		default:
-			return true;
-		}
-	};
-
-	if(node.getType() == JsonNode::JsonType::DATA_STRUCT && base.getType() == JsonNode::JsonType::DATA_STRUCT)
-	{
-		// subtract individual properties
-		JsonNode result(JsonNode::JsonType::DATA_STRUCT);
-		for(const auto & property : node.Struct())
-		{
-			if(vstd::contains(base.Struct(), property.first))
-			{
-				const JsonNode propertyDifference = JsonUtils::difference(property.second, base.Struct().find(property.first)->second);
-				if(addsInfo(propertyDifference))
-					result[property.first] = propertyDifference;
-			}
-			else
-			{
-				result[property.first] = property.second;
-			}
-		}
-		return result;
-	}
-	else
-	{
-		if(node == base)
-			return nullNode;
-	}
-	return node;
-}
-
 JsonNode JsonUtils::assembleFromFiles(const std::vector<std::string> & files)
 {
 	bool isValid = false;
@@ -365,43 +258,11 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
 
 	for(auto & loader : CResourceHandler::get()->getResourcesWithName(resID))
 	{
-		// FIXME: some way to make this code more readable
-		auto stream = loader->load(resID);
-		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
-		stream->read(textData.get(), stream->getSize());
-
-		JsonNode section(reinterpret_cast<char *>(textData.get()), stream->getSize());
+		auto textData = loader->load(resID)->readAll();
+		JsonNode section(reinterpret_cast<std::byte *>(textData.first.get()), textData.second);
 		merge(result, section);
 	}
 	return result;
 }
 
-DLL_LINKAGE JsonNode JsonUtils::boolNode(bool value)
-{
-	JsonNode node;
-	node.Bool() = value;
-	return node;
-}
-
-DLL_LINKAGE JsonNode JsonUtils::floatNode(double value)
-{
-	JsonNode node;
-	node.Float() = value;
-	return node;
-}
-
-DLL_LINKAGE JsonNode JsonUtils::stringNode(const std::string & value)
-{
-	JsonNode node;
-	node.String() = value;
-	return node;
-}
-
-DLL_LINKAGE JsonNode JsonUtils::intNode(si64 value)
-{
-	JsonNode node;
-	node.Integer() = value;
-	return node;
-}
-
 VCMI_LIB_NAMESPACE_END

+ 0 - 22
lib/json/JsonUtils.h

@@ -40,22 +40,6 @@ namespace JsonUtils
 	*/
 	DLL_LINKAGE void inherit(JsonNode & descendant, const JsonNode & base);
 
-	/**
-	 * @brief construct node representing the common structure of input nodes
-	 * @param pruneEmpty - omit common properties whose intersection is empty
-	 * different types: null
-	 * struct: recursive intersect on common properties
-	 * other: input if equal, null otherwise
-	 */
-	DLL_LINKAGE JsonNode intersect(const JsonNode & a, const JsonNode & b, bool pruneEmpty = true);
-	DLL_LINKAGE JsonNode intersect(const std::vector<JsonNode> & nodes, bool pruneEmpty = true);
-
-	/**
-	 * @brief construct node representing the difference "node - base"
-	 * merging difference with base gives node
-	 */
-	DLL_LINKAGE JsonNode difference(const JsonNode & node, const JsonNode & base);
-
 	/**
 	 * @brief generate one Json structure from multiple files
 	 * @param files - list of filenames with parts of json structure
@@ -88,12 +72,6 @@ namespace JsonUtils
 	/// get schema by json URI: vcmi:<name of file in schemas directory>#<entry in file, optional>
 	/// example: schema "vcmi:settings" is used to check user settings
 	DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
-
-	/// for easy construction of JsonNodes; helps with inserting primitives into vector node
-	DLL_LINKAGE JsonNode boolNode(bool value);
-	DLL_LINKAGE JsonNode floatNode(double value);
-	DLL_LINKAGE JsonNode stringNode(const std::string & value);
-	DLL_LINKAGE JsonNode intNode(si64 value);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 523 - 552
lib/json/JsonValidator.cpp

@@ -21,667 +21,638 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-//TODO: integer support
+static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	// check is not needed - e.g. incorporated into another check
+	return "";
+}
 
-static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
+static std::string notImplementedCheck(JsonValidator & validator,
+								const JsonNode & baseSchema,
+								const JsonNode & schema,
+								const JsonNode & data)
 {
-	{"null",   JsonNode::JsonType::DATA_NULL},
-	{"boolean", JsonNode::JsonType::DATA_BOOL},
-	{"number", JsonNode::JsonType::DATA_FLOAT},
-	{"string",  JsonNode::JsonType::DATA_STRING},
-	{"array",  JsonNode::JsonType::DATA_VECTOR},
-	{"object",  JsonNode::JsonType::DATA_STRUCT}
-};
+	return "Not implemented entry in schema";
+}
 
-namespace
+static std::string schemaListCheck(JsonValidator & validator,
+							const JsonNode & baseSchema,
+							const JsonNode & schema,
+							const JsonNode & data,
+							const std::string & errorMsg,
+							const std::function<bool(size_t)> & isValid)
 {
-	namespace Common
+	std::string errors = "<tested schemas>\n";
+	size_t result = 0;
+
+	for(const auto & schemaEntry : schema.Vector())
 	{
-		std::string emptyCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		std::string error = validator.check(schemaEntry, data);
+		if (error.empty())
 		{
-			// check is not needed - e.g. incorporated into another check
-			return "";
+			result++;
 		}
-
-		std::string notImplementedCheck(Validation::ValidationData & validator,
-										const JsonNode & baseSchema,
-										const JsonNode & schema,
-										const JsonNode & data)
+		else
 		{
-			return "Not implemented entry in schema";
+			errors += error;
+			errors += "<end of schema>\n";
 		}
+	}
+	if (isValid(result))
+		return "";
+	else
+		return validator.makeErrorMessage(errorMsg) + errors;
+}
 
-		std::string schemaListCheck(Validation::ValidationData & validator,
-									const JsonNode & baseSchema,
-									const JsonNode & schema,
-									const JsonNode & data,
-									const std::string & errorMsg,
-									const std::function<bool(size_t)> & isValid)
-		{
-			std::string errors = "<tested schemas>\n";
-			size_t result = 0;
-
-			for(const auto & schemaEntry : schema.Vector())
-			{
-				std::string error = check(schemaEntry, data, validator);
-				if (error.empty())
-				{
-					result++;
-				}
-				else
-				{
-					errors += error;
-					errors += "<end of schema>\n";
-				}
-			}
-			if (isValid(result))
-				return "";
-			else
-				return validator.makeErrorMessage(errorMsg) + errors;
-		}
+static std::string allOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count)
+	{
+		return count == schema.Vector().size();
+	});
+}
 
-		std::string allOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&](size_t count)
-			{
-				return count == schema.Vector().size();
-			});
-		}
+static std::string anyOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count)
+	{
+		return count > 0;
+	});
+}
 
-		std::string anyOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [&](size_t count)
-			{
-				return count > 0;
-			});
-		}
+static std::string oneOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count)
+	{
+		return count == 1;
+	});
+}
 
-		std::string oneOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [&](size_t count)
-			{
-				return count == 1;
-			});
-		}
+static std::string notCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (validator.check(schema, data).empty())
+		return validator.makeErrorMessage("Successful validation against negative check");
+	return "";
+}
 
-		std::string notCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (check(schema, data, validator).empty())
-				return validator.makeErrorMessage("Successful validation against negative check");
+static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	for(const auto & enumEntry : schema.Vector())
+	{
+		if (data == enumEntry)
 			return "";
-		}
-
-		std::string enumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			for(const auto & enumEntry : schema.Vector())
-			{
-				if (data == enumEntry)
-					return "";
-			}
-			return validator.makeErrorMessage("Key must have one of predefined values");
-		}
-
-		std::string typeCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			const auto & typeName = schema.String();
-			auto it = stringToType.find(typeName);
-			if(it == stringToType.end())
-			{
-				return validator.makeErrorMessage("Unknown type in schema:" + typeName);
-			}
+	}
+	return validator.makeErrorMessage("Key must have one of predefined values");
+}
 
-			JsonNode::JsonType type = it->second;
+static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
+	{
+		{"null",   JsonNode::JsonType::DATA_NULL},
+		{"boolean", JsonNode::JsonType::DATA_BOOL},
+		{"number", JsonNode::JsonType::DATA_FLOAT},
+		{"integer", JsonNode::JsonType::DATA_INTEGER},
+		{"string",  JsonNode::JsonType::DATA_STRING},
+		{"array",  JsonNode::JsonType::DATA_VECTOR},
+		{"object",  JsonNode::JsonType::DATA_STRUCT}
+	};
+
+	const auto & typeName = schema.String();
+	auto it = stringToType.find(typeName);
+	if(it == stringToType.end())
+	{
+		return validator.makeErrorMessage("Unknown type in schema:" + typeName);
+	}
 
-			//FIXME: hack for integer values
-			if(data.isNumber() && type == JsonNode::JsonType::DATA_FLOAT)
-				return "";
+	JsonNode::JsonType type = it->second;
 
-			if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL)
-				return validator.makeErrorMessage("Type mismatch! Expected " + schema.String());
-			return "";
-		}
+	// for "number" type both float and integer are allowed
+	if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber())
+		return "";
 
-		std::string refCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string URI = schema.String();
-			//node must be validated using schema pointed by this reference and not by data here
-			//Local reference. Turn it into more easy to handle remote ref
-			if (boost::algorithm::starts_with(URI, "#"))
-			{
-				const std::string name = validator.usedSchemas.back();
-				const std::string nameClean = name.substr(0, name.find('#'));
-				URI = nameClean + URI;
-			}
-			return check(URI, data, validator);
-		}
+	if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL)
+		return validator.makeErrorMessage("Type mismatch! Expected " + schema.String());
+	return "";
+}
 
-		std::string formatCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			auto formats = Validation::getKnownFormats();
-			std::string errors;
-			auto checker = formats.find(schema.String());
-			if (checker != formats.end())
-			{
-				if (data.isString())
-				{
-					std::string result = checker->second(data);
-					if (!result.empty())
-						errors += validator.makeErrorMessage(result);
-				}
-				else
-				{
-					errors += validator.makeErrorMessage("Format value must be string: " + schema.String());
-				}
-			}
-			else
-				errors += validator.makeErrorMessage("Unsupported format type: " + schema.String());
-			return errors;
-		}
+static std::string refCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string URI = schema.String();
+	//node must be validated using schema pointed by this reference and not by data here
+	//Local reference. Turn it into more easy to handle remote ref
+	if (boost::algorithm::starts_with(URI, "#"))
+	{
+		const std::string name = validator.usedSchemas.back();
+		const std::string nameClean = name.substr(0, name.find('#'));
+		URI = nameClean + URI;
 	}
+	return validator.check(URI, data);
+}
 
-	namespace String
+static std::string formatCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	auto formats = validator.getKnownFormats();
+	std::string errors;
+	auto checker = formats.find(schema.String());
+	if (checker != formats.end())
 	{
-		std::string maxLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		if (data.isString())
 		{
-			if (data.String().size() > schema.Float())
-				return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str());
-			return "";
+			std::string result = checker->second(data);
+			if (!result.empty())
+				errors += validator.makeErrorMessage(result);
 		}
-
-		std::string minLengthCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		else
 		{
-			if (data.String().size() < schema.Float())
-				return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str());
-			return "";
+			errors += validator.makeErrorMessage("Format value must be string: " + schema.String());
 		}
 	}
+	else
+		errors += validator.makeErrorMessage("Unsupported format type: " + schema.String());
+	return errors;
+}
+
+static std::string maxLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.String().size() > schema.Float())
+		return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str());
+	return "";
+}
+
+static std::string minLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.String().size() < schema.Float())
+		return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str());
+	return "";
+}
+
+static std::string maximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() > schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string minimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() < schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string exclusiveMaximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() >= schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string exclusiveMinimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Float() <= schema.Float())
+		return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
+	return "";
+}
 
-	namespace Number
+static std::string multipleOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	double result = data.Integer() / schema.Integer();
+	if (!vstd::isAlmostEqual(floor(result), result))
+		return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str());
+	return "";
+}
+
+static std::string itemEntryCheck(JsonValidator & validator, const JsonVector & items, const JsonNode & schema, size_t index)
+{
+	validator.currentPath.emplace_back();
+	validator.currentPath.back().Float() = static_cast<double>(index);
+	auto onExit = vstd::makeScopeGuard([&validator]()
 	{
+		validator.currentPath.pop_back();
+	});
 
-		std::string maximumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (baseSchema["exclusiveMaximum"].Bool())
-			{
-				if (data.Float() >= schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
-			}
-			else
-			{
-				if (data.Float() >  schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
-			}
-			return "";
-		}
+	if (!schema.isNull())
+		return validator.check(schema, items[index]);
+	return "";
+}
 
-		std::string minimumCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string itemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for (size_t i=0; i<data.Vector().size(); i++)
+	{
+		if (schema.getType() == JsonNode::JsonType::DATA_VECTOR)
 		{
-			if (baseSchema["exclusiveMinimum"].Bool())
-			{
-				if (data.Float() <= schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
-			}
-			else
-			{
-				if (data.Float() <  schema.Float())
-					return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
-			}
-			return "";
+			if (schema.Vector().size() > i)
+				errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i);
 		}
-
-		std::string multipleOfCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+		else
 		{
-			double result = data.Float() / schema.Float();
-			if (!vstd::isAlmostEqual(floor(result), result))
-				return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str());
-			return "";
+			errors += itemEntryCheck(validator, data.Vector(), schema, i);
 		}
 	}
+	return errors;
+}
+
+static std::string additionalItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	// "items" is struct or empty (defaults to empty struct) - validation always successful
+	const JsonNode & items = baseSchema["items"];
+	if (items.getType() != JsonNode::JsonType::DATA_VECTOR)
+		return "";
 
-	namespace Vector
+	for (size_t i=items.Vector().size(); i<data.Vector().size(); i++)
 	{
-		std::string itemEntryCheck(Validation::ValidationData & validator, const JsonVector & items, const JsonNode & schema, size_t index)
-		{
-			validator.currentPath.emplace_back();
-			validator.currentPath.back().Float() = static_cast<double>(index);
-			auto onExit = vstd::makeScopeGuard([&]()
-			{
-				validator.currentPath.pop_back();
-			});
+		if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
+			errors += itemEntryCheck(validator, data.Vector(), schema, i);
+		else if(!schema.isNull() && !schema.Bool())
+			errors += validator.makeErrorMessage("Unknown entry found");
+	}
+	return errors;
+}
 
-			if (!schema.isNull())
-				return check(schema, items[index], validator);
-			return "";
-		}
+static std::string minItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Vector().size() < schema.Float())
+		return validator.makeErrorMessage((boost::format("Length is smaller than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string itemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
-			for (size_t i=0; i<data.Vector().size(); i++)
-			{
-				if (schema.getType() == JsonNode::JsonType::DATA_VECTOR)
-				{
-					if (schema.Vector().size() > i)
-						errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i);
-				}
-				else
-				{
-					errors += itemEntryCheck(validator, data.Vector(), schema, i);
-				}
-			}
-			return errors;
-		}
+static std::string maxItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Vector().size() > schema.Float())
+		return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string additionalItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string uniqueItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (schema.Bool())
+	{
+		for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++)
 		{
-			std::string errors;
-			// "items" is struct or empty (defaults to empty struct) - validation always successful
-			const JsonNode & items = baseSchema["items"];
-			if (items.getType() != JsonNode::JsonType::DATA_VECTOR)
-				return "";
-
-			for (size_t i=items.Vector().size(); i<data.Vector().size(); i++)
+			auto itB = itA;
+			while (++itB != schema.Vector().end())
 			{
-				if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
-					errors += itemEntryCheck(validator, data.Vector(), schema, i);
-				else if(!schema.isNull() && !schema.Bool())
-					errors += validator.makeErrorMessage("Unknown entry found");
+				if (*itA == *itB)
+					return validator.makeErrorMessage("List must consist from unique items");
 			}
-			return errors;
 		}
+	}
+	return "";
+}
 
-		std::string minItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Vector().size() < schema.Float())
-				return validator.makeErrorMessage((boost::format("Length is smaller than %d") % schema.Float()).str());
-			return "";
-		}
+static std::string maxPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Struct().size() > schema.Float())
+		return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string maxItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Vector().size() > schema.Float())
-				return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str());
-			return "";
-		}
+static std::string minPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	if (data.Struct().size() < schema.Float())
+		return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str());
+	return "";
+}
 
-		std::string uniqueItemsCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string uniquePropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++)
+	{
+		auto itB = itA;
+		while (++itB != data.Struct().end())
 		{
-			if (schema.Bool())
-			{
-				for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++)
-				{
-					auto itB = itA;
-					while (++itB != schema.Vector().end())
-					{
-						if (*itA == *itB)
-							return validator.makeErrorMessage("List must consist from unique items");
-					}
-				}
-			}
-			return "";
+			if (itA->second == itB->second)
+				return validator.makeErrorMessage("List must consist from unique items");
 		}
 	}
+	return "";
+}
 
-	namespace Struct
+static std::string requiredCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for(const auto & required : schema.Vector())
 	{
-		std::string maxPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Struct().size() > schema.Float())
-				return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str());
-			return "";
-		}
-
-		std::string minPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			if (data.Struct().size() < schema.Float())
-				return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str());
-			return "";
-		}
+		if (data[required.String()].isNull())
+			errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing");
+	}
+	return errors;
+}
 
-		std::string uniquePropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string dependenciesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for(const auto & deps : schema.Struct())
+	{
+		if (!data[deps.first].isNull())
 		{
-			for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++)
+			if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR)
 			{
-				auto itB = itA;
-				while (++itB != data.Struct().end())
+				JsonVector depList = deps.second.Vector();
+				for(auto & depEntry : depList)
 				{
-					if (itA->second == itB->second)
-						return validator.makeErrorMessage("List must consist from unique items");
+					if (data[depEntry.String()].isNull())
+						errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing");
 				}
 			}
-			return "";
-		}
-
-		std::string requiredCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
-			for(const auto & required : schema.Vector())
-			{
-				if (data[required.String()].isNull())
-					errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing");
-			}
-			return errors;
-		}
-
-		std::string dependenciesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
-			for(const auto & deps : schema.Struct())
+			else
 			{
-				if (!data[deps.first].isNull())
-				{
-					if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR)
-					{
-						JsonVector depList = deps.second.Vector();
-						for(auto & depEntry : depList)
-						{
-							if (data[depEntry.String()].isNull())
-								errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing");
-						}
-					}
-					else
-					{
-						if (!check(deps.second, data, validator).empty())
-							errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled");
-					}
-				}
+				if (!validator.check(deps.second, data).empty())
+					errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled");
 			}
-			return errors;
 		}
+	}
+	return errors;
+}
 
-		std::string propertyEntryCheck(Validation::ValidationData & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName)
-		{
-			validator.currentPath.emplace_back();
-			validator.currentPath.back().String() = nodeName;
-			auto onExit = vstd::makeScopeGuard([&]()
-			{
-				validator.currentPath.pop_back();
-			});
+static std::string propertyEntryCheck(JsonValidator & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName)
+{
+	validator.currentPath.emplace_back();
+	validator.currentPath.back().String() = nodeName;
+	auto onExit = vstd::makeScopeGuard([&validator]()
+	{
+		validator.currentPath.pop_back();
+	});
 
-			// there is schema specifically for this item
-			if (!schema.isNull())
-				return check(schema, node, validator);
-			return "";
-		}
+	// there is schema specifically for this item
+	if (!schema.isNull())
+		return validator.check(schema, node);
+	return "";
+}
 
-		std::string propertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
-		{
-			std::string errors;
+static std::string propertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
 
-			for(const auto & entry : data.Struct())
-				errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first);
-			return errors;
-		}
+	for(const auto & entry : data.Struct())
+		errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first);
+	return errors;
+}
 
-		std::string additionalPropertiesCheck(Validation::ValidationData & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+static std::string additionalPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
+{
+	std::string errors;
+	for(const auto & entry : data.Struct())
+	{
+		if (baseSchema["properties"].Struct().count(entry.first) == 0)
 		{
-			std::string errors;
-			for(const auto & entry : data.Struct())
-			{
-				if (baseSchema["properties"].Struct().count(entry.first) == 0)
-				{
-					// try generic additionalItems schema
-					if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
-						errors += propertyEntryCheck(validator, entry.second, schema, entry.first);
+			// try generic additionalItems schema
+			if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
+				errors += propertyEntryCheck(validator, entry.second, schema, entry.first);
 
-					// or, additionalItems field can be bool which indicates if such items are allowed
-					else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
-						errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
-				}
-			}
-			return errors;
+			// or, additionalItems field can be bool which indicates if such items are allowed
+			else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
+				errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
 		}
 	}
+	return errors;
+}
 
-	namespace Formats
+static bool testFilePresence(const std::string & scope, const ResourcePath & resource)
+{
+	std::set<std::string> allowedScopes;
+	if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
 	{
-		bool testFilePresence(const std::string & scope, const ResourcePath & resource)
-		{
-			std::set<std::string> allowedScopes;
-			if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
-			{
-				//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
-				bool found = true;
-				allowedScopes = VLC->modh->getModDependencies(scope, found);
-
-				if(!found)
-					return false;
+		//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
+		bool found = true;
+		allowedScopes = VLC->modh->getModDependencies(scope, found);
 
-				allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files
-			}
-			allowedScopes.insert(scope); // mods can use their own files
-
-			for(const auto & entry : allowedScopes)
-			{
-				if (CResourceHandler::get(entry)->existsResource(resource))
-					return true;
-			}
+		if(!found)
 			return false;
-		}
 
-		#define TEST_FILE(scope, prefix, file, type) \
-			if (testFilePresence(scope, ResourcePath(prefix + file, type))) \
-				return ""
+		allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files
+	}
+	allowedScopes.insert(scope); // mods can use their own files
 
-		std::string testAnimation(const std::string & path, const std::string & scope)
-		{
-			TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION);
-			TEST_FILE(scope, "Sprites/", path, EResType::JSON);
-			return "Animation file \"" + path + "\" was not found";
-		}
+	for(const auto & entry : allowedScopes)
+	{
+		if (CResourceHandler::get(entry)->existsResource(resource))
+			return true;
+	}
+	return false;
+}
 
-		std::string textFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "", node.String(), EResType::JSON);
-			return "Text file \"" + node.String() + "\" was not found";
-		}
+#define TEST_FILE(scope, prefix, file, type) \
+	if (testFilePresence(scope, ResourcePath(prefix + file, type))) \
+	return ""
 
-		std::string musicFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Music/", node.String(), EResType::SOUND);
-			TEST_FILE(node.meta, "", node.String(), EResType::SOUND);
-			return "Music file \"" + node.String() + "\" was not found";
-		}
+static std::string testAnimation(const std::string & path, const std::string & scope)
+{
+	TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION);
+	TEST_FILE(scope, "Sprites/", path, EResType::JSON);
+	return "Animation file \"" + path + "\" was not found";
+}
 
-		std::string soundFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Sounds/", node.String(), EResType::SOUND);
-			return "Sound file \"" + node.String() + "\" was not found";
-		}
+static std::string textFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON);
+	return "Text file \"" + node.String() + "\" was not found";
+}
 
-		std::string defFile(const JsonNode & node)
-		{
-			return testAnimation(node.String(), node.meta);
-		}
+static std::string musicFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND);
+	TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND);
+	return "Music file \"" + node.String() + "\" was not found";
+}
 
-		std::string animationFile(const JsonNode & node)
-		{
-			return testAnimation(node.String(), node.meta);
-		}
+static std::string soundFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND);
+	return "Sound file \"" + node.String() + "\" was not found";
+}
 
-		std::string imageFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Data/", node.String(), EResType::IMAGE);
-			TEST_FILE(node.meta, "Sprites/", node.String(), EResType::IMAGE);
-			if (node.String().find(':') != std::string::npos)
-				return testAnimation(node.String().substr(0, node.String().find(':')), node.meta);
-			return "Image file \"" + node.String() + "\" was not found";
-		}
+static std::string animationFile(const JsonNode & node)
+{
+	return testAnimation(node.String(), node.getModScope());
+}
 
-		std::string videoFile(const JsonNode & node)
-		{
-			TEST_FILE(node.meta, "Video/", node.String(), EResType::VIDEO);
-			return "Video file \"" + node.String() + "\" was not found";
-		}
+static std::string imageFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE);
+	TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE);
+	if (node.String().find(':') != std::string::npos)
+		return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope());
+	return "Image file \"" + node.String() + "\" was not found";
+}
 
-		#undef TEST_FILE
-	}
+static std::string videoFile(const JsonNode & node)
+{
+	TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO);
+	return "Video file \"" + node.String() + "\" was not found";
+}
+#undef TEST_FILE
 
-	Validation::TValidatorMap createCommonFields()
-	{
-		Validation::TValidatorMap ret;
-
-		ret["format"] =  Common::formatCheck;
-		ret["allOf"] = Common::allOfCheck;
-		ret["anyOf"] = Common::anyOfCheck;
-		ret["oneOf"] = Common::oneOfCheck;
-		ret["enum"]  = Common::enumCheck;
-		ret["type"]  = Common::typeCheck;
-		ret["not"]   = Common::notCheck;
-		ret["$ref"]  = Common::refCheck;
-
-		// fields that don't need implementation
-		ret["title"] = Common::emptyCheck;
-		ret["$schema"] = Common::emptyCheck;
-		ret["default"] = Common::emptyCheck;
-		ret["description"] = Common::emptyCheck;
-		ret["definitions"] = Common::emptyCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createCommonFields()
+{
+	JsonValidator::TValidatorMap ret;
+
+	ret["format"] =  formatCheck;
+	ret["allOf"] = allOfCheck;
+	ret["anyOf"] = anyOfCheck;
+	ret["oneOf"] = oneOfCheck;
+	ret["enum"]  = enumCheck;
+	ret["type"]  = typeCheck;
+	ret["not"]   = notCheck;
+	ret["$ref"]  = refCheck;
+
+	// fields that don't need implementation
+	ret["title"] = emptyCheck;
+	ret["$schema"] = emptyCheck;
+	ret["default"] = emptyCheck;
+	ret["defaultIOS"] = emptyCheck;
+	ret["defaultAndroid"] = emptyCheck;
+	ret["defaultWindows"] = emptyCheck;
+	ret["description"] = emptyCheck;
+	ret["definitions"] = emptyCheck;
+
+	// Not implemented
+	ret["propertyNames"] = notImplementedCheck;
+	ret["contains"] = notImplementedCheck;
+	ret["const"] = notImplementedCheck;
+	ret["examples"] = notImplementedCheck;
+
+	return ret;
+}
 
-	Validation::TValidatorMap createStringFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["maxLength"] = String::maxLengthCheck;
-		ret["minLength"] = String::minLengthCheck;
+JsonValidator::TValidatorMap createStringFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["maxLength"] = maxLengthCheck;
+	ret["minLength"] = minLengthCheck;
 
-		ret["pattern"] = Common::notImplementedCheck;
-		return ret;
-	}
+	ret["pattern"] = notImplementedCheck;
+	return ret;
+}
 
-	Validation::TValidatorMap createNumberFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["maximum"]    = Number::maximumCheck;
-		ret["minimum"]    = Number::minimumCheck;
-		ret["multipleOf"] = Number::multipleOfCheck;
-
-		ret["exclusiveMaximum"] = Common::emptyCheck;
-		ret["exclusiveMinimum"] = Common::emptyCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createNumberFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["maximum"]    = maximumCheck;
+	ret["minimum"]    = minimumCheck;
+	ret["multipleOf"] = multipleOfCheck;
+
+	ret["exclusiveMaximum"] = exclusiveMaximumCheck;
+	ret["exclusiveMinimum"] = exclusiveMinimumCheck;
+	return ret;
+}
 
-	Validation::TValidatorMap createVectorFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["items"]           = Vector::itemsCheck;
-		ret["minItems"]        = Vector::minItemsCheck;
-		ret["maxItems"]        = Vector::maxItemsCheck;
-		ret["uniqueItems"]     = Vector::uniqueItemsCheck;
-		ret["additionalItems"] = Vector::additionalItemsCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createVectorFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["items"]           = itemsCheck;
+	ret["minItems"]        = minItemsCheck;
+	ret["maxItems"]        = maxItemsCheck;
+	ret["uniqueItems"]     = uniqueItemsCheck;
+	ret["additionalItems"] = additionalItemsCheck;
+	return ret;
+}
 
-	Validation::TValidatorMap createStructFields()
-	{
-		Validation::TValidatorMap ret = createCommonFields();
-		ret["additionalProperties"]  = Struct::additionalPropertiesCheck;
-		ret["uniqueProperties"]      = Struct::uniquePropertiesCheck;
-		ret["maxProperties"]         = Struct::maxPropertiesCheck;
-		ret["minProperties"]         = Struct::minPropertiesCheck;
-		ret["dependencies"]          = Struct::dependenciesCheck;
-		ret["properties"]            = Struct::propertiesCheck;
-		ret["required"]              = Struct::requiredCheck;
-
-		ret["patternProperties"] = Common::notImplementedCheck;
-		return ret;
-	}
+JsonValidator::TValidatorMap createStructFields()
+{
+	JsonValidator::TValidatorMap ret = createCommonFields();
+	ret["additionalProperties"]  = additionalPropertiesCheck;
+	ret["uniqueProperties"]      = uniquePropertiesCheck;
+	ret["maxProperties"]         = maxPropertiesCheck;
+	ret["minProperties"]         = minPropertiesCheck;
+	ret["dependencies"]          = dependenciesCheck;
+	ret["properties"]            = propertiesCheck;
+	ret["required"]              = requiredCheck;
+
+	ret["patternProperties"] = notImplementedCheck;
+	return ret;
+}
 
-	Validation::TFormatMap createFormatMap()
-	{
-		Validation::TFormatMap ret;
-		ret["textFile"]      = Formats::textFile;
-		ret["musicFile"]     = Formats::musicFile;
-		ret["soundFile"]     = Formats::soundFile;
-		ret["defFile"]       = Formats::defFile;
-		ret["animationFile"] = Formats::animationFile;
-		ret["imageFile"]     = Formats::imageFile;
-		ret["videoFile"]     = Formats::videoFile;
-
-		return ret;
-	}
+JsonValidator::TFormatMap createFormatMap()
+{
+	JsonValidator::TFormatMap ret;
+	ret["textFile"]      = textFile;
+	ret["musicFile"]     = musicFile;
+	ret["soundFile"]     = soundFile;
+	ret["animationFile"] = animationFile;
+	ret["imageFile"]     = imageFile;
+	ret["videoFile"]     = videoFile;
+
+	//TODO:
+	// uri-reference
+	// uri-template
+	// json-pointer
+
+	return ret;
 }
 
-namespace Validation
+std::string JsonValidator::makeErrorMessage(const std::string &message)
 {
-	std::string ValidationData::makeErrorMessage(const std::string &message)
+	std::string errors;
+	errors += "At ";
+	if (!currentPath.empty())
 	{
-		std::string errors;
-		errors += "At ";
-		if (!currentPath.empty())
+		for(const JsonNode &path : currentPath)
 		{
-			for(const JsonNode &path : currentPath)
-			{
-				errors += "/";
-				if (path.getType() == JsonNode::JsonType::DATA_STRING)
-					errors += path.String();
-				else
-					errors += std::to_string(static_cast<unsigned>(path.Float()));
-			}
+			errors += "/";
+			if (path.getType() == JsonNode::JsonType::DATA_STRING)
+				errors += path.String();
+			else
+				errors += std::to_string(static_cast<unsigned>(path.Float()));
 		}
-		else
-			errors += "<root>";
-		errors += "\n\t Error: " + message + "\n";
-		return errors;
-	}
-
-	std::string check(const std::string & schemaName, const JsonNode & data)
-	{
-		ValidationData validator;
-		return check(schemaName, data, validator);
 	}
+	else
+		errors += "<root>";
+	errors += "\n\t Error: " + message + "\n";
+	return errors;
+}
 
-	std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator)
+std::string JsonValidator::check(const std::string & schemaName, const JsonNode & data)
+{
+	usedSchemas.push_back(schemaName);
+	auto onscopeExit = vstd::makeScopeGuard([this]()
 	{
-		validator.usedSchemas.push_back(schemaName);
-		auto onscopeExit = vstd::makeScopeGuard([&]()
-		{
-			validator.usedSchemas.pop_back();
-		});
-		return check(JsonUtils::getSchema(schemaName), data, validator);
-	}
+		usedSchemas.pop_back();
+	});
+	return check(JsonUtils::getSchema(schemaName), data);
+}
 
-	std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator)
+std::string JsonValidator::check(const JsonNode & schema, const JsonNode & data)
+{
+	const TValidatorMap & knownFields = getKnownFieldsFor(data.getType());
+	std::string errors;
+	for(const auto & entry : schema.Struct())
 	{
-		const TValidatorMap & knownFields = getKnownFieldsFor(data.getType());
-		std::string errors;
-		for(const auto & entry : schema.Struct())
-		{
-			auto checker = knownFields.find(entry.first);
-			if (checker != knownFields.end())
-				errors += checker->second(validator, schema, entry.second, data);
-			//else
-			//	errors += validator.makeErrorMessage("Unknown entry in schema " + entry.first);
-		}
-		return errors;
+		auto checker = knownFields.find(entry.first);
+		if (checker != knownFields.end())
+			errors += checker->second(*this, schema, entry.second, data);
 	}
+	return errors;
+}
 
-	const TValidatorMap & getKnownFieldsFor(JsonNode::JsonType type)
-	{
-		static const TValidatorMap commonFields = createCommonFields();
-		static const TValidatorMap numberFields = createNumberFields();
-		static const TValidatorMap stringFields = createStringFields();
-		static const TValidatorMap vectorFields = createVectorFields();
-		static const TValidatorMap structFields = createStructFields();
-
-		switch (type)
-		{
-			case JsonNode::JsonType::DATA_FLOAT:
-			case JsonNode::JsonType::DATA_INTEGER:
-				return numberFields;
-			case JsonNode::JsonType::DATA_STRING: return stringFields;
-			case JsonNode::JsonType::DATA_VECTOR: return vectorFields;
-			case JsonNode::JsonType::DATA_STRUCT: return structFields;
-			default: return commonFields;
-		}
-	}
+const JsonValidator::TValidatorMap & JsonValidator::getKnownFieldsFor(JsonNode::JsonType type)
+{
+	static const TValidatorMap commonFields = createCommonFields();
+	static const TValidatorMap numberFields = createNumberFields();
+	static const TValidatorMap stringFields = createStringFields();
+	static const TValidatorMap vectorFields = createVectorFields();
+	static const TValidatorMap structFields = createStructFields();
 
-	const TFormatMap & getKnownFormats()
+	switch (type)
 	{
-		static TFormatMap knownFormats = createFormatMap();
-		return knownFormats;
+		case JsonNode::JsonType::DATA_FLOAT:
+		case JsonNode::JsonType::DATA_INTEGER:
+			return numberFields;
+		case JsonNode::JsonType::DATA_STRING: return stringFields;
+		case JsonNode::JsonType::DATA_VECTOR: return vectorFields;
+		case JsonNode::JsonType::DATA_STRUCT: return structFields;
+		default: return commonFields;
 	}
+}
 
-} // Validation namespace
+const JsonValidator::TFormatMap & JsonValidator::getKnownFormats()
+{
+	static const TFormatMap knownFormats = createFormatMap();
+	return knownFormats;
+}
 
 VCMI_LIB_NAMESPACE_END

+ 13 - 19
lib/json/JsonValidator.h

@@ -13,28 +13,23 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-
-//Internal class for Json validation. Mostly compilant with json-schema v4 draft
-namespace Validation
+/// Class for Json validation. Mostly compilant with json-schema v6 draf
+struct JsonValidator
 {
-	/// struct used to pass data around during validation
-	struct ValidationData
-	{
-		/// path from root node to current one.
-		/// JsonNode is used as variant - either string (name of node) or as float (index in list)
-		std::vector<JsonNode> currentPath;
+	/// path from root node to current one.
+	/// JsonNode is used as variant - either string (name of node) or as float (index in list)
+	std::vector<JsonNode> currentPath;
 
-		/// Stack of used schemas. Last schema is the one used currently.
-		/// May contain multiple items in case if remote references were found
-		std::vector<std::string> usedSchemas;
+	/// Stack of used schemas. Last schema is the one used currently.
+	/// May contain multiple items in case if remote references were found
+	std::vector<std::string> usedSchemas;
 
-		/// generates error message
-		std::string makeErrorMessage(const std::string &message);
-	};
+	/// generates error message
+	std::string makeErrorMessage(const std::string &message);
 
 	using TFormatValidator = std::function<std::string(const JsonNode &)>;
 	using TFormatMap = std::unordered_map<std::string, TFormatValidator>;
-	using TFieldValidator = std::function<std::string(ValidationData &, const JsonNode &, const JsonNode &, const JsonNode &)>;
+	using TFieldValidator = std::function<std::string(JsonValidator &, const JsonNode &, const JsonNode &, const JsonNode &)>;
 	using TValidatorMap = std::unordered_map<std::string, TFieldValidator>;
 
 	/// map of known fields in schema
@@ -42,8 +37,7 @@ namespace Validation
 	const TFormatMap & getKnownFormats();
 
 	std::string check(const std::string & schemaName, const JsonNode & data);
-	std::string check(const std::string & schemaName, const JsonNode & data, ValidationData & validator);
-	std::string check(const JsonNode & schema, const JsonNode & data, ValidationData & validator);
-}
+	std::string check(const JsonNode & schema, const JsonNode & data);
+};
 
 VCMI_LIB_NAMESPACE_END

+ 37 - 37
lib/json/JsonWriter.cpp

@@ -16,31 +16,29 @@ VCMI_LIB_NAMESPACE_BEGIN
 template<typename Iterator>
 void JsonWriter::writeContainer(Iterator begin, Iterator end)
 {
-	if (begin == end)
+	if(begin == end)
 		return;
 
 	prefix += '\t';
 
 	writeEntry(begin++);
 
-	while (begin != end)
+	while(begin != end)
 	{
 		out << (compactMode ? ", " : ",\n");
 		writeEntry(begin++);
 	}
 
 	out << (compactMode ? "" : "\n");
-	prefix.resize(prefix.size()-1);
+	prefix.resize(prefix.size() - 1);
 }
 
 void JsonWriter::writeEntry(JsonMap::const_iterator entry)
 {
 	if(!compactMode)
 	{
-		if (!entry->second.meta.empty())
-			out << prefix << " // " << entry->second.meta << "\n";
-		if(!entry->second.flags.empty())
-			out << prefix << " // flags: " << boost::algorithm::join(entry->second.flags, ", ") << "\n";
+		if(!entry->second.getModScope().empty())
+			out << prefix << " // " << entry->second.getModScope() << "\n";
 		out << prefix;
 	}
 	writeString(entry->first);
@@ -52,30 +50,25 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry)
 {
 	if(!compactMode)
 	{
-		if (!entry->meta.empty())
-			out << prefix << " // " << entry->meta << "\n";
-		if(!entry->flags.empty())
-			out << prefix << " // flags: " << boost::algorithm::join(entry->flags, ", ") << "\n";
+		if(!entry->getModScope().empty())
+			out << prefix << " // " << entry->getModScope() << "\n";
 		out << prefix;
 	}
 	writeNode(*entry);
 }
 
-void JsonWriter::writeString(const std::string &string)
+void JsonWriter::writeString(const std::string & string)
 {
-	static const std::string escaped = "\"\\\b\f\n\r\t/";
+	static const std::string escaped = "\"\\\b\f\n\r\t";
+	static const std::array escapedCode = {'\"', '\\', 'b', 'f', 'n', 'r', 't'};
 
-	static const std::array<char, 8> escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't', '/'};
-
-	out <<'\"';
+	out << '\"';
 	size_t pos = 0;
 	size_t start = 0;
-	for (; pos<string.size(); pos++)
+	for(; pos < string.size(); pos++)
 	{
 		//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()) )
+		if((string[pos] == '\\') && (pos + 1 < string.size()) && (std::find(escapedCode.begin(), escapedCode.end(), string[pos + 1]) != escapedCode.end()))
 		{
 			pos++; //write unchanged, next simbol also checked
 		}
@@ -83,20 +76,19 @@ void JsonWriter::writeString(const std::string &string)
 		{
 			size_t escapedPos = escaped.find(string[pos]);
 
-			if (escapedPos != std::string::npos)
+			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 << '\\' << escapedCode[escapedPos];
+				start = pos + 1;
 			}
 		}
-
 	}
-	out.write(string.data()+start, pos - start);
-	out <<'\"';
+	out.write(string.data() + start, pos - start);
+	out << '\"';
 }
 
-void JsonWriter::writeNode(const JsonNode &node)
+void JsonWriter::writeNode(const JsonNode & node)
 {
 	bool originalMode = compactMode;
 	if(compact && !compactMode && node.isCompact())
@@ -104,40 +96,48 @@ void JsonWriter::writeNode(const JsonNode &node)
 
 	switch(node.getType())
 	{
-		break; case JsonNode::JsonType::DATA_NULL:
+		case JsonNode::JsonType::DATA_NULL:
 			out << "null";
+			break;
 
-		break; case JsonNode::JsonType::DATA_BOOL:
-			if (node.Bool())
+		case JsonNode::JsonType::DATA_BOOL:
+			if(node.Bool())
 				out << "true";
 			else
 				out << "false";
+			break;
 
-		break; case JsonNode::JsonType::DATA_FLOAT:
+		case JsonNode::JsonType::DATA_FLOAT:
 			out << node.Float();
+			break;
 
-		break; case JsonNode::JsonType::DATA_STRING:
+		case JsonNode::JsonType::DATA_STRING:
 			writeString(node.String());
+			break;
 
-		break; case JsonNode::JsonType::DATA_VECTOR:
+		case JsonNode::JsonType::DATA_VECTOR:
 			out << "[" << (compactMode ? " " : "\n");
 			writeContainer(node.Vector().begin(), node.Vector().end());
 			out << (compactMode ? " " : prefix) << "]";
+			break;
 
-		break; case JsonNode::JsonType::DATA_STRUCT:
+		case JsonNode::JsonType::DATA_STRUCT:
 			out << "{" << (compactMode ? " " : "\n");
 			writeContainer(node.Struct().begin(), node.Struct().end());
 			out << (compactMode ? " " : prefix) << "}";
+			break;
 
-		break; case JsonNode::JsonType::DATA_INTEGER:
+		case JsonNode::JsonType::DATA_INTEGER:
 			out << node.Integer();
+			break;
 	}
 
 	compactMode = originalMode;
 }
 
 JsonWriter::JsonWriter(std::ostream & output, bool compact)
-	: out(output), compact(compact)
+	: out(output)
+	, compact(compact)
 {
 }
 

+ 1 - 0
lib/json/JsonWriter.h

@@ -22,6 +22,7 @@ class JsonWriter
 	bool compact;
 	//tracks whether we are currently using single-line format
 	bool compactMode = false;
+
 public:
 	template<typename Iterator>
 	void writeContainer(Iterator begin, Iterator end);

+ 1 - 1
lib/mapObjectConstructors/CBankInstanceConstructor.cpp

@@ -27,7 +27,7 @@ void CBankInstanceConstructor::initTypeData(const JsonNode & input)
 	if (input.Struct().count("name") == 0)
 		logMod->warn("Bank %s missing name!", getJsonKey());
 
-	VLC->generaltexth->registerString(input.meta, getNameTextID(), input["name"].String());
+	VLC->generaltexth->registerString(input.getModScope(), getNameTextID(), input["name"].String());
 
 	levels = input["levels"].Vector();
 	bankResetDuration = static_cast<si32>(input["resetDuration"].Float());

+ 5 - 5
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -259,21 +259,21 @@ std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::stri
 	{
 		if (!subData.second["index"].isNull())
 		{
-			const std::string & subMeta = subData.second["index"].meta;
+			const std::string & subMeta = subData.second["index"].getModScope();
 
 			if ( subMeta == "core")
 			{
 				size_t subIndex = subData.second["index"].Integer();
-				loadSubObject(subData.second.meta, subData.first, subData.second, obj.get(), subIndex);
+				loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get(), subIndex);
 			}
 			else
 			{
 				logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first );
-				loadSubObject(subData.second.meta, subData.first, subData.second, obj.get());
+				loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
 			}
 		}
 		else
-			loadSubObject(subData.second.meta, subData.first, subData.second, obj.get());
+			loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
 	}
 
 	if (obj->id == MapObjectID::MONOLITH_TWO_WAY)
@@ -306,7 +306,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo
 		objects.at(ID.getNum())->objects.resize(subID.getNum()+1);
 
 	JsonUtils::inherit(config, objects.at(ID.getNum())->base);
-	loadSubObject(config.meta, identifier, config, objects.at(ID.getNum()).get(), subID.getNum());
+	loadSubObject(config.getModScope(), identifier, config, objects.at(ID.getNum()).get(), subID.getNum());
 }
 
 void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)

+ 1 - 1
lib/mapObjectConstructors/CRewardableConstructor.cpp

@@ -22,7 +22,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config)
 	blockVisit = config["blockedVisitable"].Bool();
 
 	if (!config["name"].isNull())
-		VLC->generaltexth->registerString( config.meta, getNameTextID(), config["name"].String());
+		VLC->generaltexth->registerString( config.getModScope(), getNameTextID(), config["name"].String());
 	
 }
 

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů