From 0104c77d618651b039da72ef588f299f224f4d17 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 03:27:59 +0400
Subject: [PATCH 01/35] Interface builder for random map tab

---
 client/CMakeLists.txt            |   2 +
 client/gui/InterfaceBuilder.cpp  | 208 +++++++++++++
 client/gui/InterfaceBuilder.h    |  39 +++
 client/gui/NotificationHandler.h |   2 +-
 client/lobby/RandomMapTab.cpp    | 158 +++-------
 client/lobby/RandomMapTab.h      |  22 +-
 config/windows.json              | 513 +++++++++++++++++++++++++++++++
 7 files changed, 804 insertions(+), 140 deletions(-)
 create mode 100644 client/gui/InterfaceBuilder.cpp
 create mode 100644 client/gui/InterfaceBuilder.h
 create mode 100644 config/windows.json

diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 07fd813ae..18886669b 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -15,6 +15,7 @@ set(client_SRCS
 		gui/Geometries.cpp
 		gui/SDL_Extensions.cpp
 		gui/NotificationHandler.cpp
+		gui/InterfaceBuilder.cpp
 
 		widgets/AdventureMapClasses.cpp
 		widgets/Buttons.cpp
@@ -90,6 +91,7 @@ set(client_HEADERS
 		gui/SDL_Extensions.h
 		gui/SDL_Pixels.h
 		gui/NotificationHandler.h
+		gui/InterfaceBuilder.h
 
 		widgets/AdventureMapClasses.h
 		widgets/Buttons.h
diff --git a/client/gui/InterfaceBuilder.cpp b/client/gui/InterfaceBuilder.cpp
new file mode 100644
index 000000000..db079f099
--- /dev/null
+++ b/client/gui/InterfaceBuilder.cpp
@@ -0,0 +1,208 @@
+/*
+* InterfaceBuilder.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 "InterfaceBuilder.h"
+
+#include "../CGameInfo.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+
+
+InterfaceBuilder::InterfaceBuilder(const JsonNode & config):
+	CIntObject()
+{
+	init(config);
+}
+
+InterfaceBuilder::InterfaceBuilder():
+	CIntObject()
+{
+}
+
+void InterfaceBuilder::addCallback(const std::string & callbackName, std::function<void(int)> callback)
+{
+	callbacks[callbackName] = callback;
+}
+
+const std::shared_ptr<CIntObject> InterfaceBuilder::widget(const std::string & name) const
+{
+	return widgets.at(name);
+}
+
+void InterfaceBuilder::init(const JsonNode &config)
+{
+	OBJ_CONSTRUCTION;
+	int unnamedObjectId = 0;
+	const std::string unnamedObjectPrefix = "__widget_";
+	
+	for(const auto & item : config["items"].Vector())
+	{
+		std::string name = item["name"].isNull()
+						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
+						: item["name"].String();
+		widgets[name] = buildWidget(item);
+	}
+}
+
+std::string InterfaceBuilder::buildText(const JsonNode & config) const
+{
+	if(config.isNull())
+		return "";
+	
+	if(config.isNumber())
+	{
+		return CGI->generaltexth->allTexts[config.Integer()];
+	}
+	return config.String();
+}
+
+std::shared_ptr<CIntObject> InterfaceBuilder::buildWidget(const JsonNode & config)
+{
+	assert(!config.isNull());
+	auto type = config["type"].String();
+	
+	int x = 0, y = 0;
+	if(!config["position"].isNull())
+	{
+		x = config["position"]["x"].Integer();
+		y = config["position"]["y"].Integer();
+	}
+	
+	std::string image, text = buildText(config["text"]);
+	auto alignment = EAlignment::CENTER;
+	auto color = Colors::DEFAULT_KEY_COLOR;
+	auto font = EFonts::FONT_TIMES;
+	
+	if(!config["image"].isNull())
+		image = config["image"].String();
+	if(!config["alignment"].isNull())
+	{
+		if(config["alignment"].String() == "left")
+			alignment = EAlignment::TOPLEFT;
+		if(config["alignment"].String() == "center")
+			alignment = EAlignment::CENTER;
+		if(config["alignment"].String() == "right")
+			alignment = EAlignment::BOTTOMRIGHT;
+	}
+	if(!config["color"].isNull())
+	{
+		if(config["color"].String() == "yellow")
+			color = Colors::YELLOW;
+		if(config["color"].String() == "white")
+			color = Colors::WHITE;
+		if(config["color"].String() == "gold")
+			color = Colors::METALLIC_GOLD;
+		if(config["color"].String() == "green")
+			color = Colors::GREEN;
+		if(config["color"].String() == "orange")
+			color = Colors::ORANGE;
+		if(config["color"].String() == "bright-yellow")
+			color = Colors::BRIGHT_YELLOW;
+	}
+	if(!config["font"].isNull())
+	{
+		if(config["font"].String() == "big")
+			font = EFonts::FONT_BIG;
+		if(config["font"].String() == "medium")
+			font = EFonts::FONT_MEDIUM;
+		if(config["font"].String() == "small")
+			font = EFonts::FONT_SMALL;
+		if(config["font"].String() == "tiny")
+			font = EFonts::FONT_TINY;
+	}
+	
+	
+	if(type == "picture")
+	{
+		return std::make_shared<CPicture>(image, x, y);
+	}
+	if(type == "label")
+	{
+		return std::make_shared<CLabel>(x, y, font, alignment, color, text);
+	}
+	if(type == "toggleGroup")
+	{
+		auto group = std::make_shared<CToggleGroup>(0);
+		group->pos.x += x;
+		group->pos.y += y;
+		if(!config["items"].isNull())
+		{
+			SObjectConstruction obj__i(group.get());
+			int itemIdx = -1;
+			for(const auto & item : config["items"].Vector())
+			{
+				itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
+				group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
+			}
+		}
+		if(!config["selected"].isNull())
+			group->setSelected(config["selected"].Integer());
+		if(!config["callback"].isNull())
+			group->addCallback(callbacks[config["callback"].String()]);
+		return group;
+	}
+	if(type == "toggleButton")
+	{
+		std::pair<std::string, std::string> zelp;
+		if(!config["zelp"].isNull())
+			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
+		auto button = std::make_shared<CToggleButton>(Point(x, y), image, zelp);
+		if(!config["selected"].isNull())
+			button->setSelected(config["selected"].Bool());
+		if(!config["imageOrder"].isNull())
+		{
+			auto imgOrder = config["imageOrder"].Vector();
+			assert(imgOrder.size() >= 4);
+			button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
+		}
+		if(!config["callback"].isNull())
+			button->addCallback(callbacks[config["callback"].String()]);
+		return button;
+	}
+	if(type == "button")
+	{
+		std::pair<std::string, std::string> zelp;
+		if(!config["zelp"].isNull())
+			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
+		auto button = std::make_shared<CButton>(Point(x, y), image, zelp);
+		return button;
+	}
+	if(type == "labelGroup")
+	{
+		auto group = std::make_shared<CLabelGroup>(font, alignment, color);
+		if(!config["items"].isNull())
+		{
+			for(const auto & item : config["items"].Vector())
+			{
+				if(!item["position"].isNull())
+				{
+					x = item["position"]["x"].Integer();
+					y = item["position"]["y"].Integer();
+				}
+				if(!item["text"].isNull())
+					text = buildText(item["text"]);
+				group->add(x, y, text);
+			}
+		}
+		return group;
+	}
+	return std::shared_ptr<CIntObject>(nullptr);
+}
diff --git a/client/gui/InterfaceBuilder.h b/client/gui/InterfaceBuilder.h
new file mode 100644
index 000000000..00740d7ef
--- /dev/null
+++ b/client/gui/InterfaceBuilder.h
@@ -0,0 +1,39 @@
+/*
+* InterfaceBuilder.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 "CIntObject.h"
+
+#include "../../lib/JsonNode.h"
+
+class InterfaceBuilder: public CIntObject
+{
+public:
+	InterfaceBuilder();
+	InterfaceBuilder(const JsonNode & config);
+
+protected:
+	//must be called after adding callbacks
+	void init(const JsonNode & config);
+	
+	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
+	
+	const std::shared_ptr<CIntObject> widget(const std::string &) const;
+	
+private:
+	
+	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
+	std::map<std::string, std::function<void(int)>> callbacks;
+	
+	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config);
+	
+	std::string buildText(const JsonNode & param) const;
+};
diff --git a/client/gui/NotificationHandler.h b/client/gui/NotificationHandler.h
index a1ac262a7..536e0978d 100644
--- a/client/gui/NotificationHandler.h
+++ b/client/gui/NotificationHandler.h
@@ -1,5 +1,5 @@
 /*
-* NotificationHandler.cpp, part of VCMI engine
+* NotificationHandler.h, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index d945388cf..2d5848b99 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -27,127 +27,72 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/rmg/CMapGenOptions.h"
+#include "../../lib/CModHandler.h"
 
-RandomMapTab::RandomMapTab()
+RandomMapTab::RandomMapTab():
+	InterfaceBuilder()
 {
 	recActions = 0;
 	mapGenOptions = std::make_shared<CMapGenOptions>();
-	OBJ_CONSTRUCTION;
-	background = std::make_shared<CPicture>("RANMAPBK", 0, 6);
-
-	labelHeadlineBig = std::make_shared<CLabel>(222, 36, FONT_BIG, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[738]);
-	labelHeadlineSmall = std::make_shared<CLabel>(222, 56, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[739]);
-
-	labelMapSize = std::make_shared<CLabel>(104, 97, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[752]);
-	groupMapSize = std::make_shared<CToggleGroup>(0);
-	groupMapSize->pos.y += 81;
-	groupMapSize->pos.x += 158;
-	const std::vector<std::string> mapSizeBtns = {"RANSIZS", "RANSIZM", "RANSIZL", "RANSIZX"};
-	addButtonsToGroup(groupMapSize.get(), mapSizeBtns, 0, 3, 47, 198);
-	groupMapSize->setSelected(1);
-	groupMapSize->addCallback([&](int btnId)
+	
+	const JsonNode config(ResourceID("config/windows.json"));
+	addCallback("toggleMapSize", [&](int btnId)
 	{
 		auto mapSizeVal = getPossibleMapSizes();
 		mapGenOptions->setWidth(mapSizeVal[btnId]);
 		mapGenOptions->setHeight(mapSizeVal[btnId]);
 		updateMapInfoByHost();
 	});
-
-	buttonTwoLevels = std::make_shared<CToggleButton>(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
-	buttonTwoLevels->setSelected(true);
-	buttonTwoLevels->addCallback([&](bool on)
+	addCallback("toggleTwoLevels", [&](bool on)
 	{
 		mapGenOptions->setHasTwoLevels(on);
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE);
-	// Create number defs list
-	std::vector<std::string> numberDefs;
-	for(int i = 0; i <= 8; ++i)
-	{
-		numberDefs.push_back("RANNUM" + boost::lexical_cast<std::string>(i));
-	}
-
-	const int NUMBERS_WIDTH = 32;
-	const int BTNS_GROUP_LEFT_MARGIN = 67;
-	labelGroupForOptions->add(68, 133, CGI->generaltexth->allTexts[753]);
-	groupMaxPlayers = std::make_shared<CToggleGroup>(0);
-	groupMaxPlayers->pos.y += 153;
-	groupMaxPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(groupMaxPlayers.get(), numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
-	groupMaxPlayers->addCallback([&](int btnId)
+	
+	addCallback("setPlayersCount", [&](int btnId)
 	{
 		mapGenOptions->setPlayerCount(btnId);
-		deactivateButtonsFrom(groupMaxTeams.get(), btnId);
+	
+		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")).get(), btnId);
 
 		// deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		deactivateButtonsFrom(groupCompOnlyPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
+		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")).get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
 
 		validatePlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions->add(68, 199, CGI->generaltexth->allTexts[754]);
-	groupMaxTeams = std::make_shared<CToggleGroup>(0);
-	groupMaxTeams->pos.y += 219;
-	groupMaxTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(groupMaxTeams.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
-	groupMaxTeams->addCallback([&](int btnId)
+	
+	addCallback("setTeamsCount", [&](int btnId)
 	{
 		mapGenOptions->setTeamCount(btnId);
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions->add(68, 265, CGI->generaltexth->allTexts[755]);
-	groupCompOnlyPlayers = std::make_shared<CToggleGroup>(0);
-	groupCompOnlyPlayers->pos.y += 285;
-	groupCompOnlyPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(groupCompOnlyPlayers.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
-	groupCompOnlyPlayers->addCallback([&](int btnId)
+	
+	addCallback("setCompOnlyPlayers", [&](int btnId)
 	{
 		mapGenOptions->setCompOnlyPlayerCount(btnId);
-		
+
 		// deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		deactivateButtonsFrom(groupMaxPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
+		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers")).get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
 		
-		deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId));
+		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")).get(), (btnId == 0 ? 1 : btnId));
 		validateCompOnlyPlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions->add(68, 331, CGI->generaltexth->allTexts[756]);
-	groupCompOnlyTeams = std::make_shared<CToggleGroup>(0);
-	groupCompOnlyTeams->pos.y += 351;
-	groupCompOnlyTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	addButtonsWithRandToGroup(groupCompOnlyTeams.get(), numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
-	deactivateButtonsFrom(groupCompOnlyTeams.get(), 1);
-	groupCompOnlyTeams->addCallback([&](int btnId)
+	
+	addCallback("setCompOnlyTeams", [&](int btnId)
 	{
 		mapGenOptions->setCompOnlyTeamCount(btnId);
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions->add(68, 398, CGI->generaltexth->allTexts[757]);
-	const int WIDE_BTN_WIDTH = 85;
-	groupWaterContent = std::make_shared<CToggleGroup>(0);
-	groupWaterContent->pos.y += 419;
-	groupWaterContent->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	const std::vector<std::string> waterContentBtns = {"RANNONE", "RANNORM", "RANISLD"};
-	addButtonsWithRandToGroup(groupWaterContent.get(), waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
-	groupWaterContent->addCallback([&](int btnId)
+	
+	addCallback("setWaterContent", [&](int btnId)
 	{
 		mapGenOptions->setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions->add(68, 465, CGI->generaltexth->allTexts[758]);
-	groupMonsterStrength = std::make_shared<CToggleGroup>(0);
-	groupMonsterStrength->pos.y += 485;
-	groupMonsterStrength->pos.x += BTNS_GROUP_LEFT_MARGIN;
-	const std::vector<std::string> monsterStrengthBtns = {"RANWEAK", "RANNORM", "RANSTRG"};
-	addButtonsWithRandToGroup(groupMonsterStrength.get(), monsterStrengthBtns, 2, 4, WIDE_BTN_WIDTH, 248, 251, EMonsterStrength::RANDOM, false);
-	groupMonsterStrength->addCallback([&](int btnId)
+	
+	addCallback("setMonsterStrenght", [&](int btnId)
 	{
 		if(btnId < 0)
 			mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
@@ -155,9 +100,9 @@ RandomMapTab::RandomMapTab()
 			mapGenOptions->setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId)); //value 2 to 4
 		updateMapInfoByHost();
 	});
-
-	buttonShowRandomMaps = std::make_shared<CButton>(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
-
+	
+	init(config["randomMapTab"]);
+	
 	updateMapInfoByHost();
 }
 
@@ -216,39 +161,14 @@ void RandomMapTab::updateMapInfoByHost()
 
 void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 {
-	groupMapSize->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
-	buttonTwoLevels->setSelected(opts->getHasTwoLevels());
-	groupMaxPlayers->setSelected(opts->getPlayerCount());
-	groupMaxTeams->setSelected(opts->getTeamCount());
-	groupCompOnlyPlayers->setSelected(opts->getCompOnlyPlayerCount());
-	groupCompOnlyTeams->setSelected(opts->getCompOnlyTeamCount());
-	groupWaterContent->setSelected(opts->getWaterContent());
-	groupMonsterStrength->setSelected(opts->getMonsterStrength());
-}
-
-void RandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex, int randIndex, bool animIdfromBtnId) const
-{
-	addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex, animIdfromBtnId);
-
-	// Buttons are relative to button group, TODO better solution?
-	SObjectConstruction obj__i(group);
-	const std::string RANDOM_DEF = "RANRAND";
-	group->addToggle(randIndex, std::make_shared<CToggleButton>(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
-	group->setSelected(randIndex);
-}
-
-void RandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, bool animIdfromBtnId) const
-{
-	// Buttons are relative to button group, TODO better solution?
-	SObjectConstruction obj__i(group);
-	int cnt = nEnd - nStart + 1;
-	for(int i = 0; i < cnt; ++i)
-	{
-		auto button = std::make_shared<CToggleButton>(Point(i * btnWidth, 0), animIdfromBtnId ? defs[i + nStart] : defs[i], CGI->generaltexth->zelp[helpStartIndex + i]);
-		// For blocked state we should use pressed image actually
-		button->setImageOrder(0, 1, 1, 3);
-		group->addToggle(i + nStart, button);
-	}
+	dynamic_pointer_cast<CToggleGroup>(widget("groupMapSize"))->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
+	dynamic_pointer_cast<CToggleButton>(widget("buttonTwoLevels"))->setSelected(opts->getHasTwoLevels());
+	dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers"))->setSelected(opts->getPlayerCount());
+	dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams"))->setSelected(opts->getTeamCount());
+	dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers"))->setSelected(opts->getCompOnlyPlayerCount());
+	dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams"))->setSelected(opts->getCompOnlyTeamCount());
+	dynamic_pointer_cast<CToggleGroup>(widget("groupWaterContent"))->setSelected(opts->getWaterContent());
+	dynamic_pointer_cast<CToggleGroup>(widget("groupMonsterStrength"))->setSelected(opts->getMonsterStrength());
 }
 
 void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
@@ -280,13 +200,13 @@ void RandomMapTab::validatePlayersCnt(int playersCnt)
 	if(mapGenOptions->getTeamCount() >= playersCnt)
 	{
 		mapGenOptions->setTeamCount(playersCnt - 1);
-		groupMaxTeams->setSelected(mapGenOptions->getTeamCount());
+		dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams"))->setSelected(mapGenOptions->getTeamCount());
 	}
 	// total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3)
 	if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I)
 	{
 		mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
-		groupCompOnlyPlayers->setSelected(mapGenOptions->getCompOnlyPlayerCount());
+		dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers"))->setSelected(mapGenOptions->getCompOnlyPlayerCount());
 	}
 
 	validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
@@ -304,7 +224,7 @@ void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
 		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
 		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
 		updateMapInfoByHost();
-		groupCompOnlyTeams->setSelected(compOnlyTeamCount);
+		dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams"))->setSelected(compOnlyTeamCount);
 	}
 }
 
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index f7e399a17..ac78247f9 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -13,6 +13,7 @@
 
 #include "../../lib/FunctionList.h"
 #include "../../lib/GameConstants.h"
+#include "../gui/InterfaceBuilder.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -24,7 +25,7 @@ class CToggleButton;
 class CLabel;
 class CLabelGroup;
 
-class RandomMapTab : public CIntObject
+class RandomMapTab : public InterfaceBuilder
 {
 public:
 	RandomMapTab();
@@ -35,30 +36,11 @@ public:
 	CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
 
 private:
-	void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex, int randIndex = -1, bool animIdfromBtnId = true) const;
-	void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, bool animIdfromBtnId = true) const;
 	void deactivateButtonsFrom(CToggleGroup * group, int startId);
 	void validatePlayersCnt(int playersCnt);
 	void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
 	std::vector<int> getPossibleMapSizes();
 
-
-	std::shared_ptr<CPicture> background;
-	std::shared_ptr<CLabel> labelHeadlineBig;
-	std::shared_ptr<CLabel> labelHeadlineSmall;
-
-	std::shared_ptr<CLabel> labelMapSize;
-	std::shared_ptr<CToggleGroup> groupMapSize;
-	std::shared_ptr<CToggleButton> buttonTwoLevels;
-
-	std::shared_ptr<CLabelGroup> labelGroupForOptions;
-	std::shared_ptr<CToggleGroup> groupMaxPlayers;
-	std::shared_ptr<CToggleGroup> groupMaxTeams;
-	std::shared_ptr<CToggleGroup> groupCompOnlyPlayers;
-	std::shared_ptr<CToggleGroup> groupCompOnlyTeams;
-	std::shared_ptr<CToggleGroup> groupWaterContent;
-	std::shared_ptr<CToggleGroup> groupMonsterStrength;
-	std::shared_ptr<CButton> buttonShowRandomMaps;
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
 	std::shared_ptr<CMapInfo> mapInfo;
 };
diff --git a/config/windows.json b/config/windows.json
new file mode 100644
index 000000000..06de0c1a6
--- /dev/null
+++ b/config/windows.json
@@ -0,0 +1,513 @@
+{
+	"randomMapTab":
+	{
+		"items":
+		[
+			{
+				"name": "background",
+				"type": "picture",
+				"image": "RANMAPBK",
+				"position": {"x": 0, "y": 6}
+			},
+
+			{
+				"name": "labelHeadlineBig",
+				"type": "label",
+				"font": "big",
+				"alignment": "center",
+				"color": "yellow",
+				"text": 738,
+				"position": {"x": 222, "y": 36}
+			},
+
+			{
+				"name": "labelHeadlineSmall",
+				"type": "label",
+				"font": "small",
+				"alignment": "center",
+				"color": "white",
+				"text": 739,
+				"position": {"x": 222, "y": 56}
+			},
+
+			{
+				"name": "labelMapSize",
+				"type": "label",
+				"font": "small",
+				"alignment": "center",
+				"color": "white",
+				"text": 752,
+				"position": {"x": 104, "y": 97}
+			},
+
+			{
+				"name": "groupMapSize",
+				"type": "toggleGroup",
+				"position": {"x": 158, "y": 81},
+				"items":
+				[
+					{
+						"index": 0,
+						"type": "toggleButton",
+						"image": "RANSIZS",
+						"zelp": 198,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0},
+					},
+
+					{
+						"type": "toggleButton",
+						"image": "RANSIZM",
+						"zelp": 199,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 47, "y": 0},
+					},
+
+					{
+						"type": "toggleButton",
+						"image": "RANSIZL",
+						"zelp": 200,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 94, "y": 0},
+					},
+
+					{
+						"type": "toggleButton",
+						"image": "RANSIZX",
+						"zelp": 201,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 141, "y": 0}
+					}
+				],
+				"selected": 1,
+				"callback": "toggleMapSize"
+			},
+
+			{
+				"name": "buttonTwoLevels",
+				"type": "toggleButton",
+				"image": "RANUNDR",
+				"position": {"x": 346, "y": 81},
+				"selected": true,
+				"callback": "toggleTwoLevels"
+			},
+
+			{
+				"name": "groupMaxPlayers",
+				"type": "toggleGroup",
+				"position": {"x": 67, "y": 153},
+				"items": 
+				[
+					{
+						"index": 1,
+						"type": "toggleButton",
+						"image": "RANNUM1",
+						"zelp": 204,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM2",
+						"zelp": 205,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 32, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM3",
+						"zelp": 206,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 64, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM4",
+						"zelp": 207,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 96, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM5",
+						"zelp": 208,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 128, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM6",
+						"zelp": 209,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 160, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM7",
+						"zelp": 210,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 192, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM8",
+						"zelp": 211,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 224, "y": 0}
+					},
+					{
+						"index": -1,
+						"type": "toggleButton",
+						"image": "RANRAND",
+						"zelp": 212,
+						"position": {"x": 256, "y": 0},
+					}
+				],
+				"selected": 7,
+				"callback": "setPlayersCount"
+			},
+
+			{
+				"name": "groupMaxTeams",
+				"type": "toggleGroup",
+				"position": {"x": 67, "y": 219},
+				"items": 
+				[
+					{
+						"index": 0,
+						"type": "toggleButton",
+						"image": "RANNUM0",
+						"zelp": 214,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM1",
+						"zelp": 215,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 32, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM2",
+						"zelp": 216,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 64, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM3",
+						"zelp": 217,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 96, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM4",
+						"zelp": 218,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 128, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM5",
+						"zelp": 219,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 160, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM6",
+						"zelp": 220,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 192, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM7",
+						"zelp": 221,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 224, "y": 0}
+					},
+					{
+						"index": -1,
+						"type": "toggleButton",
+						"image": "RANRAND",
+						"zelp": 222,
+						"position": {"x": 256, "y": 0},
+					}
+				],
+				"selected": 7,
+				"callback": "setTeamsCount"
+			},
+
+			{
+				"name": "groupCompOnlyPlayers",
+				"type": "toggleGroup",
+				"position": {"x": 67, "y": 285},
+				"items": 
+				[
+					{
+						"index": 0,
+						"type": "toggleButton",
+						"image": "RANNUM0",
+						"zelp": 224,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM1",
+						"zelp": 225,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 32, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM2",
+						"zelp": 226,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 64, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM3",
+						"zelp": 227,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 96, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM4",
+						"zelp": 228,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 128, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM5",
+						"zelp": 229,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 160, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM6",
+						"zelp": 230,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 192, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM7",
+						"zelp": 231,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 224, "y": 0}
+					},
+					{
+						"index": -1,
+						"type": "toggleButton",
+						"image": "RANRAND",
+						"zelp": 232,
+						"position": {"x": 256, "y": 0},
+					}
+				],
+				"selected": 7,
+				"callback": "setCompOnlyPlayers"
+			},
+
+			{
+				"name": "groupCompOnlyTeams",
+				"type": "toggleGroup",
+				"position": {"x": 67, "y": 351},
+				"items": 
+				[
+					{
+						"index": 0,
+						"type": "toggleButton",
+						"image": "RANNUM0",
+						"zelp": 234,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM1",
+						"zelp": 235,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 32, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM2",
+						"zelp": 236,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 64, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM3",
+						"zelp": 237,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 96, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM4",
+						"zelp": 238,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 128, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM5",
+						"zelp": 239,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 160, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNUM6",
+						"zelp": 240,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 192, "y": 0}
+					},
+					{
+						"index": -1,
+						"type": "toggleButton",
+						"image": "RANRAND",
+						"zelp": 241,
+						"position": {"x": 256, "y": 0},
+					}
+				],
+				"selected": 7,
+				"callback": "setCompOnlyTeams"
+			},
+
+			{
+				"name": "groupWaterContent",
+				"type": "toggleGroup",
+				"position": {"x": 67, "y": 419},
+				"items": 
+				[
+					{
+						"index": 0,
+						"type": "toggleButton",
+						"image": "RANNONE",
+						"zelp": 243,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANNORM",
+						"zelp": 244,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 85, "y": 0}
+					},
+					{
+						"type": "toggleButton",
+						"image": "RANISLD",
+						"zelp": 245,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 170, "y": 0}
+					},
+					{
+						"index": -1,
+						"type": "toggleButton",
+						"image": "RANRAND",
+						"zelp": 246,
+						"position": {"x": 256, "y": 0},
+					}
+				],
+				"selected": 3,
+				"callback": "setWaterContent"
+			},
+
+			{
+				"name": "groupMonsterStrength",
+				"type": "toggleGroup",
+				"position": {"x": 67, "y": 485},
+				"items":
+				[
+					{
+						"index": 2,
+						"type": "toggleButton",
+						"image": "RANWEAK",
+						"zelp": 248,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 0, "y": 0}
+					},
+					{
+						"index": 3,
+						"type": "toggleButton",
+						"image": "RANNORM",
+						"zelp": 249,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 85, "y": 0}
+					},
+					{
+						"index": 4,
+						"type": "toggleButton",
+						"image": "RANSTRG",
+						"zelp": 250,
+						"imageOrder": [0, 1, 1, 3],
+						"position": {"x": 170, "y": 0}
+					},
+					{
+						"index": -2,
+						"type": "toggleButton",
+						"image": "RANRAND",
+						"zelp": 251,
+						"position": {"x": 256, "y": 0},
+					}
+				],
+				"selected": 3,
+				"callback": "setMonsterStrenght"
+			},
+
+			{
+				"name": "buttonShowRandomMaps",
+				"type": "button",
+				"position": {"x": 54, "y": 535},
+				"image": "RANSHOW",
+				"zelp": 252
+			},
+
+			{
+				"type": "labelGroup",
+				"font": "small",
+				"alignment": "left",
+				"color": "white",
+				"items":
+				[
+					{
+						"position": {"x": 68, "y": 133},
+						"text": 753
+					},
+					{
+						"position": {"x": 68, "y": 199},
+						"text": 754
+					},
+					{
+						"position": {"x": 68, "y": 265},
+						"text": 755
+					},
+					{
+						"position": {"x": 68, "y": 331},
+						"text": 756
+					},
+					{
+						"position": {"x": 68, "y": 398},
+						"text": 757
+					},
+					{
+						"position": {"x": 68, "y": 465},
+						"text": 758
+					}
+				]
+			}
+		]
+	}
+}

From 3be9969154a5b09c7a2e480dca27c25e3ecd03dc Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 03:58:39 +0400
Subject: [PATCH 02/35] Prevent crashes for custom config

---
 client/gui/InterfaceBuilder.cpp |  5 +++-
 client/lobby/RandomMapTab.cpp   | 45 ++++++++++++++++++++++-----------
 2 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/client/gui/InterfaceBuilder.cpp b/client/gui/InterfaceBuilder.cpp
index db079f099..f8d383039 100644
--- a/client/gui/InterfaceBuilder.cpp
+++ b/client/gui/InterfaceBuilder.cpp
@@ -44,7 +44,10 @@ void InterfaceBuilder::addCallback(const std::string & callbackName, std::functi
 
 const std::shared_ptr<CIntObject> InterfaceBuilder::widget(const std::string & name) const
 {
-	return widgets.at(name);
+	auto iter = widgets.find(name);
+	if(iter == widgets.end())
+		return nullptr;
+	return iter->second;
 }
 
 void InterfaceBuilder::init(const JsonNode &config)
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 2d5848b99..cf02dee30 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -53,10 +53,12 @@ RandomMapTab::RandomMapTab():
 	{
 		mapGenOptions->setPlayerCount(btnId);
 	
-		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")).get(), btnId);
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")))
+			deactivateButtonsFrom(w.get(), btnId);
 
 		// deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")).get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")))
+			deactivateButtonsFrom(w.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
 
 		validatePlayersCnt(btnId);
 		updateMapInfoByHost();
@@ -73,9 +75,11 @@ RandomMapTab::RandomMapTab():
 		mapGenOptions->setCompOnlyPlayerCount(btnId);
 
 		// deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers")).get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers")))
+			deactivateButtonsFrom(w.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
 		
-		deactivateButtonsFrom(dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")).get(), (btnId == 0 ? 1 : btnId));
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")))
+			deactivateButtonsFrom(w.get(), (btnId == 0 ? 1 : btnId));
 		validateCompOnlyPlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
@@ -161,14 +165,22 @@ void RandomMapTab::updateMapInfoByHost()
 
 void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 {
-	dynamic_pointer_cast<CToggleGroup>(widget("groupMapSize"))->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
-	dynamic_pointer_cast<CToggleButton>(widget("buttonTwoLevels"))->setSelected(opts->getHasTwoLevels());
-	dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers"))->setSelected(opts->getPlayerCount());
-	dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams"))->setSelected(opts->getTeamCount());
-	dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers"))->setSelected(opts->getCompOnlyPlayerCount());
-	dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams"))->setSelected(opts->getCompOnlyTeamCount());
-	dynamic_pointer_cast<CToggleGroup>(widget("groupWaterContent"))->setSelected(opts->getWaterContent());
-	dynamic_pointer_cast<CToggleGroup>(widget("groupMonsterStrength"))->setSelected(opts->getMonsterStrength());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMapSize")))
+		w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
+	if(auto w = dynamic_pointer_cast<CToggleButton>(widget("buttonTwoLevels")))
+		w->setSelected(opts->getHasTwoLevels());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers")))
+		w->setSelected(opts->getPlayerCount());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")))
+		w->setSelected(opts->getTeamCount());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")))
+		w->setSelected(opts->getCompOnlyPlayerCount());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")))
+		w->setSelected(opts->getCompOnlyTeamCount());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupWaterContent")))
+		w->setSelected(opts->getWaterContent());
+	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupgroupMonsterStrengthMaxTeams")))
+		w->setSelected(opts->getMonsterStrength());
 }
 
 void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
@@ -200,13 +212,15 @@ void RandomMapTab::validatePlayersCnt(int playersCnt)
 	if(mapGenOptions->getTeamCount() >= playersCnt)
 	{
 		mapGenOptions->setTeamCount(playersCnt - 1);
-		dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams"))->setSelected(mapGenOptions->getTeamCount());
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")))
+			w->setSelected(mapGenOptions->getTeamCount());
 	}
 	// total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3)
 	if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I)
 	{
 		mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
-		dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers"))->setSelected(mapGenOptions->getCompOnlyPlayerCount());
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")))
+			w->setSelected(mapGenOptions->getCompOnlyPlayerCount());
 	}
 
 	validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
@@ -224,7 +238,8 @@ void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
 		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
 		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
 		updateMapInfoByHost();
-		dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams"))->setSelected(compOnlyTeamCount);
+		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")))
+			w->setSelected(compOnlyTeamCount);
 	}
 }
 

From b3d60ec418ece5d01428e5d3f47339d2e3db24a8 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 04:46:42 +0400
Subject: [PATCH 03/35] Add new map sizes

---
 client/lobby/RandomMapTab.cpp |  2 +-
 lib/mapping/CMap.h            |  3 +++
 lib/mapping/CMapInfo.cpp      | 12 ++++++++++++
 3 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index cf02dee30..ec8a3fcc2 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -245,5 +245,5 @@ void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
 
 std::vector<int> RandomMapTab::getPossibleMapSizes()
 {
-	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE};
+	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h
index b8047624b..25c1a464b 100644
--- a/lib/mapping/CMap.h
+++ b/lib/mapping/CMap.h
@@ -284,6 +284,9 @@ public:
 	static const int MAP_SIZE_MIDDLE = 72;
 	static const int MAP_SIZE_LARGE = 108;
 	static const int MAP_SIZE_XLARGE = 144;
+	static const int MAP_SIZE_HUGE = 180;
+	static const int MAP_SIZE_XHUGE = 216;
+	static const int MAP_SIZE_GIANT = 252;
 
 	CMapHeader();
 	virtual ~CMapHeader();
diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp
index 3756ae580..f33f6a02e 100644
--- a/lib/mapping/CMapInfo.cpp
+++ b/lib/mapping/CMapInfo.cpp
@@ -134,6 +134,12 @@ int CMapInfo::getMapSizeIconId() const
 		return 2;
 	case CMapHeader::MAP_SIZE_XLARGE:
 		return 3;
+	case CMapHeader::MAP_SIZE_HUGE:
+		return 4;
+	case CMapHeader::MAP_SIZE_XHUGE:
+		return 5;
+	case CMapHeader::MAP_SIZE_GIANT:
+		return 6;
 	default:
 		return 4;
 	}
@@ -180,6 +186,12 @@ std::string CMapInfo::getMapSizeName() const
 		return "L";
 	case CMapHeader::MAP_SIZE_XLARGE:
 		return "XL";
+	case CMapHeader::MAP_SIZE_HUGE:
+		return "H";
+	case CMapHeader::MAP_SIZE_XHUGE:
+		return "XH";
+	case CMapHeader::MAP_SIZE_GIANT:
+		return "G";
 	default:
 		return "C";
 	}

From 8dbc5c1c1f38ed4ab90ab4c6c6b6a142b05ea154 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 04:52:44 +0400
Subject: [PATCH 04/35] Make each windows coniguration in separate file

---
 client/lobby/RandomMapTab.cpp    |   4 +-
 config/windows.json              | 513 -------------------------------
 config/windows/randomMapTab.json | 510 ++++++++++++++++++++++++++++++
 3 files changed, 512 insertions(+), 515 deletions(-)
 delete mode 100644 config/windows.json
 create mode 100644 config/windows/randomMapTab.json

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index ec8a3fcc2..76543c3e4 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -35,7 +35,7 @@ RandomMapTab::RandomMapTab():
 	recActions = 0;
 	mapGenOptions = std::make_shared<CMapGenOptions>();
 	
-	const JsonNode config(ResourceID("config/windows.json"));
+	const JsonNode config(ResourceID("config/windows/randomMapTab.json"));
 	addCallback("toggleMapSize", [&](int btnId)
 	{
 		auto mapSizeVal = getPossibleMapSizes();
@@ -105,7 +105,7 @@ RandomMapTab::RandomMapTab():
 		updateMapInfoByHost();
 	});
 	
-	init(config["randomMapTab"]);
+	init(config);
 	
 	updateMapInfoByHost();
 }
diff --git a/config/windows.json b/config/windows.json
deleted file mode 100644
index 06de0c1a6..000000000
--- a/config/windows.json
+++ /dev/null
@@ -1,513 +0,0 @@
-{
-	"randomMapTab":
-	{
-		"items":
-		[
-			{
-				"name": "background",
-				"type": "picture",
-				"image": "RANMAPBK",
-				"position": {"x": 0, "y": 6}
-			},
-
-			{
-				"name": "labelHeadlineBig",
-				"type": "label",
-				"font": "big",
-				"alignment": "center",
-				"color": "yellow",
-				"text": 738,
-				"position": {"x": 222, "y": 36}
-			},
-
-			{
-				"name": "labelHeadlineSmall",
-				"type": "label",
-				"font": "small",
-				"alignment": "center",
-				"color": "white",
-				"text": 739,
-				"position": {"x": 222, "y": 56}
-			},
-
-			{
-				"name": "labelMapSize",
-				"type": "label",
-				"font": "small",
-				"alignment": "center",
-				"color": "white",
-				"text": 752,
-				"position": {"x": 104, "y": 97}
-			},
-
-			{
-				"name": "groupMapSize",
-				"type": "toggleGroup",
-				"position": {"x": 158, "y": 81},
-				"items":
-				[
-					{
-						"index": 0,
-						"type": "toggleButton",
-						"image": "RANSIZS",
-						"zelp": 198,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0},
-					},
-
-					{
-						"type": "toggleButton",
-						"image": "RANSIZM",
-						"zelp": 199,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 47, "y": 0},
-					},
-
-					{
-						"type": "toggleButton",
-						"image": "RANSIZL",
-						"zelp": 200,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 94, "y": 0},
-					},
-
-					{
-						"type": "toggleButton",
-						"image": "RANSIZX",
-						"zelp": 201,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 141, "y": 0}
-					}
-				],
-				"selected": 1,
-				"callback": "toggleMapSize"
-			},
-
-			{
-				"name": "buttonTwoLevels",
-				"type": "toggleButton",
-				"image": "RANUNDR",
-				"position": {"x": 346, "y": 81},
-				"selected": true,
-				"callback": "toggleTwoLevels"
-			},
-
-			{
-				"name": "groupMaxPlayers",
-				"type": "toggleGroup",
-				"position": {"x": 67, "y": 153},
-				"items": 
-				[
-					{
-						"index": 1,
-						"type": "toggleButton",
-						"image": "RANNUM1",
-						"zelp": 204,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM2",
-						"zelp": 205,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 32, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM3",
-						"zelp": 206,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 64, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM4",
-						"zelp": 207,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 96, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM5",
-						"zelp": 208,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 128, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM6",
-						"zelp": 209,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 160, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM7",
-						"zelp": 210,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 192, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM8",
-						"zelp": 211,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 224, "y": 0}
-					},
-					{
-						"index": -1,
-						"type": "toggleButton",
-						"image": "RANRAND",
-						"zelp": 212,
-						"position": {"x": 256, "y": 0},
-					}
-				],
-				"selected": 7,
-				"callback": "setPlayersCount"
-			},
-
-			{
-				"name": "groupMaxTeams",
-				"type": "toggleGroup",
-				"position": {"x": 67, "y": 219},
-				"items": 
-				[
-					{
-						"index": 0,
-						"type": "toggleButton",
-						"image": "RANNUM0",
-						"zelp": 214,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM1",
-						"zelp": 215,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 32, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM2",
-						"zelp": 216,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 64, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM3",
-						"zelp": 217,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 96, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM4",
-						"zelp": 218,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 128, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM5",
-						"zelp": 219,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 160, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM6",
-						"zelp": 220,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 192, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM7",
-						"zelp": 221,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 224, "y": 0}
-					},
-					{
-						"index": -1,
-						"type": "toggleButton",
-						"image": "RANRAND",
-						"zelp": 222,
-						"position": {"x": 256, "y": 0},
-					}
-				],
-				"selected": 7,
-				"callback": "setTeamsCount"
-			},
-
-			{
-				"name": "groupCompOnlyPlayers",
-				"type": "toggleGroup",
-				"position": {"x": 67, "y": 285},
-				"items": 
-				[
-					{
-						"index": 0,
-						"type": "toggleButton",
-						"image": "RANNUM0",
-						"zelp": 224,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM1",
-						"zelp": 225,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 32, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM2",
-						"zelp": 226,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 64, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM3",
-						"zelp": 227,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 96, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM4",
-						"zelp": 228,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 128, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM5",
-						"zelp": 229,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 160, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM6",
-						"zelp": 230,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 192, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM7",
-						"zelp": 231,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 224, "y": 0}
-					},
-					{
-						"index": -1,
-						"type": "toggleButton",
-						"image": "RANRAND",
-						"zelp": 232,
-						"position": {"x": 256, "y": 0},
-					}
-				],
-				"selected": 7,
-				"callback": "setCompOnlyPlayers"
-			},
-
-			{
-				"name": "groupCompOnlyTeams",
-				"type": "toggleGroup",
-				"position": {"x": 67, "y": 351},
-				"items": 
-				[
-					{
-						"index": 0,
-						"type": "toggleButton",
-						"image": "RANNUM0",
-						"zelp": 234,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM1",
-						"zelp": 235,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 32, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM2",
-						"zelp": 236,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 64, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM3",
-						"zelp": 237,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 96, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM4",
-						"zelp": 238,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 128, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM5",
-						"zelp": 239,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 160, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNUM6",
-						"zelp": 240,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 192, "y": 0}
-					},
-					{
-						"index": -1,
-						"type": "toggleButton",
-						"image": "RANRAND",
-						"zelp": 241,
-						"position": {"x": 256, "y": 0},
-					}
-				],
-				"selected": 7,
-				"callback": "setCompOnlyTeams"
-			},
-
-			{
-				"name": "groupWaterContent",
-				"type": "toggleGroup",
-				"position": {"x": 67, "y": 419},
-				"items": 
-				[
-					{
-						"index": 0,
-						"type": "toggleButton",
-						"image": "RANNONE",
-						"zelp": 243,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANNORM",
-						"zelp": 244,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 85, "y": 0}
-					},
-					{
-						"type": "toggleButton",
-						"image": "RANISLD",
-						"zelp": 245,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 170, "y": 0}
-					},
-					{
-						"index": -1,
-						"type": "toggleButton",
-						"image": "RANRAND",
-						"zelp": 246,
-						"position": {"x": 256, "y": 0},
-					}
-				],
-				"selected": 3,
-				"callback": "setWaterContent"
-			},
-
-			{
-				"name": "groupMonsterStrength",
-				"type": "toggleGroup",
-				"position": {"x": 67, "y": 485},
-				"items":
-				[
-					{
-						"index": 2,
-						"type": "toggleButton",
-						"image": "RANWEAK",
-						"zelp": 248,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 0, "y": 0}
-					},
-					{
-						"index": 3,
-						"type": "toggleButton",
-						"image": "RANNORM",
-						"zelp": 249,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 85, "y": 0}
-					},
-					{
-						"index": 4,
-						"type": "toggleButton",
-						"image": "RANSTRG",
-						"zelp": 250,
-						"imageOrder": [0, 1, 1, 3],
-						"position": {"x": 170, "y": 0}
-					},
-					{
-						"index": -2,
-						"type": "toggleButton",
-						"image": "RANRAND",
-						"zelp": 251,
-						"position": {"x": 256, "y": 0},
-					}
-				],
-				"selected": 3,
-				"callback": "setMonsterStrenght"
-			},
-
-			{
-				"name": "buttonShowRandomMaps",
-				"type": "button",
-				"position": {"x": 54, "y": 535},
-				"image": "RANSHOW",
-				"zelp": 252
-			},
-
-			{
-				"type": "labelGroup",
-				"font": "small",
-				"alignment": "left",
-				"color": "white",
-				"items":
-				[
-					{
-						"position": {"x": 68, "y": 133},
-						"text": 753
-					},
-					{
-						"position": {"x": 68, "y": 199},
-						"text": 754
-					},
-					{
-						"position": {"x": 68, "y": 265},
-						"text": 755
-					},
-					{
-						"position": {"x": 68, "y": 331},
-						"text": 756
-					},
-					{
-						"position": {"x": 68, "y": 398},
-						"text": 757
-					},
-					{
-						"position": {"x": 68, "y": 465},
-						"text": 758
-					}
-				]
-			}
-		]
-	}
-}
diff --git a/config/windows/randomMapTab.json b/config/windows/randomMapTab.json
new file mode 100644
index 000000000..189537ac9
--- /dev/null
+++ b/config/windows/randomMapTab.json
@@ -0,0 +1,510 @@
+{
+	"items":
+	[
+		{
+			"name": "background",
+			"type": "picture",
+			"image": "RANMAPBK",
+			"position": {"x": 0, "y": 6}
+		},
+
+		{
+			"name": "labelHeadlineBig",
+			"type": "label",
+			"font": "big",
+			"alignment": "center",
+			"color": "yellow",
+			"text": 738,
+			"position": {"x": 222, "y": 36}
+		},
+
+		{
+			"name": "labelHeadlineSmall",
+			"type": "label",
+			"font": "small",
+			"alignment": "center",
+			"color": "white",
+			"text": 739,
+			"position": {"x": 222, "y": 56}
+		},
+
+		{
+			"name": "labelMapSize",
+			"type": "label",
+			"font": "small",
+			"alignment": "center",
+			"color": "white",
+			"text": 752,
+			"position": {"x": 104, "y": 97}
+		},
+
+		{
+			"name": "groupMapSize",
+			"type": "toggleGroup",
+			"position": {"x": 158, "y": 81},
+			"items":
+			[
+				{
+					"index": 0,
+					"type": "toggleButton",
+					"image": "RANSIZS",
+					"zelp": 198,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0},
+				},
+
+				{
+					"type": "toggleButton",
+					"image": "RANSIZM",
+					"zelp": 199,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 47, "y": 0},
+				},
+
+				{
+					"type": "toggleButton",
+					"image": "RANSIZL",
+					"zelp": 200,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 94, "y": 0},
+				},
+
+				{
+					"type": "toggleButton",
+					"image": "RANSIZX",
+					"zelp": 201,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 141, "y": 0}
+				}
+			],
+			"selected": 1,
+			"callback": "toggleMapSize"
+		},
+
+		{
+			"name": "buttonTwoLevels",
+			"type": "toggleButton",
+			"image": "RANUNDR",
+			"position": {"x": 346, "y": 81},
+			"selected": true,
+			"callback": "toggleTwoLevels"
+		},
+
+		{
+			"name": "groupMaxPlayers",
+			"type": "toggleGroup",
+			"position": {"x": 67, "y": 153},
+			"items": 
+			[
+				{
+					"index": 1,
+					"type": "toggleButton",
+					"image": "RANNUM1",
+					"zelp": 204,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM2",
+					"zelp": 205,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 32, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM3",
+					"zelp": 206,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 64, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM4",
+					"zelp": 207,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 96, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM5",
+					"zelp": 208,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 128, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM6",
+					"zelp": 209,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 160, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM7",
+					"zelp": 210,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 192, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM8",
+					"zelp": 211,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 224, "y": 0}
+				},
+				{
+					"index": -1,
+					"type": "toggleButton",
+					"image": "RANRAND",
+					"zelp": 212,
+					"position": {"x": 256, "y": 0},
+				}
+			],
+			"selected": 7,
+			"callback": "setPlayersCount"
+		},
+
+		{
+			"name": "groupMaxTeams",
+			"type": "toggleGroup",
+			"position": {"x": 67, "y": 219},
+			"items": 
+			[
+				{
+					"index": 0,
+					"type": "toggleButton",
+					"image": "RANNUM0",
+					"zelp": 214,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM1",
+					"zelp": 215,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 32, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM2",
+					"zelp": 216,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 64, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM3",
+					"zelp": 217,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 96, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM4",
+					"zelp": 218,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 128, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM5",
+					"zelp": 219,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 160, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM6",
+					"zelp": 220,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 192, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM7",
+					"zelp": 221,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 224, "y": 0}
+				},
+				{
+					"index": -1,
+					"type": "toggleButton",
+					"image": "RANRAND",
+					"zelp": 222,
+					"position": {"x": 256, "y": 0},
+				}
+			],
+			"selected": 7,
+			"callback": "setTeamsCount"
+		},
+
+		{
+			"name": "groupCompOnlyPlayers",
+			"type": "toggleGroup",
+			"position": {"x": 67, "y": 285},
+			"items": 
+			[
+				{
+					"index": 0,
+					"type": "toggleButton",
+					"image": "RANNUM0",
+					"zelp": 224,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM1",
+					"zelp": 225,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 32, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM2",
+					"zelp": 226,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 64, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM3",
+					"zelp": 227,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 96, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM4",
+					"zelp": 228,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 128, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM5",
+					"zelp": 229,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 160, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM6",
+					"zelp": 230,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 192, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM7",
+					"zelp": 231,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 224, "y": 0}
+				},
+				{
+					"index": -1,
+					"type": "toggleButton",
+					"image": "RANRAND",
+					"zelp": 232,
+					"position": {"x": 256, "y": 0},
+				}
+			],
+			"selected": 7,
+			"callback": "setCompOnlyPlayers"
+		},
+
+		{
+			"name": "groupCompOnlyTeams",
+			"type": "toggleGroup",
+			"position": {"x": 67, "y": 351},
+			"items": 
+			[
+				{
+					"index": 0,
+					"type": "toggleButton",
+					"image": "RANNUM0",
+					"zelp": 234,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM1",
+					"zelp": 235,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 32, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM2",
+					"zelp": 236,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 64, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM3",
+					"zelp": 237,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 96, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM4",
+					"zelp": 238,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 128, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM5",
+					"zelp": 239,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 160, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNUM6",
+					"zelp": 240,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 192, "y": 0}
+				},
+				{
+					"index": -1,
+					"type": "toggleButton",
+					"image": "RANRAND",
+					"zelp": 241,
+					"position": {"x": 256, "y": 0},
+				}
+			],
+			"selected": 7,
+			"callback": "setCompOnlyTeams"
+		},
+
+		{
+			"name": "groupWaterContent",
+			"type": "toggleGroup",
+			"position": {"x": 67, "y": 419},
+			"items": 
+			[
+				{
+					"index": 0,
+					"type": "toggleButton",
+					"image": "RANNONE",
+					"zelp": 243,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANNORM",
+					"zelp": 244,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 85, "y": 0}
+				},
+				{
+					"type": "toggleButton",
+					"image": "RANISLD",
+					"zelp": 245,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 170, "y": 0}
+				},
+				{
+					"index": -1,
+					"type": "toggleButton",
+					"image": "RANRAND",
+					"zelp": 246,
+					"position": {"x": 256, "y": 0},
+				}
+			],
+			"selected": 3,
+			"callback": "setWaterContent"
+		},
+
+		{
+			"name": "groupMonsterStrength",
+			"type": "toggleGroup",
+			"position": {"x": 67, "y": 485},
+			"items":
+			[
+				{
+					"index": 2,
+					"type": "toggleButton",
+					"image": "RANWEAK",
+					"zelp": 248,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 0, "y": 0}
+				},
+				{
+					"index": 3,
+					"type": "toggleButton",
+					"image": "RANNORM",
+					"zelp": 249,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 85, "y": 0}
+				},
+				{
+					"index": 4,
+					"type": "toggleButton",
+					"image": "RANSTRG",
+					"zelp": 250,
+					"imageOrder": [0, 1, 1, 3],
+					"position": {"x": 170, "y": 0}
+				},
+				{
+					"index": -2,
+					"type": "toggleButton",
+					"image": "RANRAND",
+					"zelp": 251,
+					"position": {"x": 256, "y": 0},
+				}
+			],
+			"selected": 3,
+			"callback": "setMonsterStrenght"
+		},
+
+		{
+			"name": "buttonShowRandomMaps",
+			"type": "button",
+			"position": {"x": 54, "y": 535},
+			"image": "RANSHOW",
+			"zelp": 252
+		},
+
+		{
+			"type": "labelGroup",
+			"font": "small",
+			"alignment": "left",
+			"color": "white",
+			"items":
+			[
+				{
+					"position": {"x": 68, "y": 133},
+					"text": 753
+				},
+				{
+					"position": {"x": 68, "y": 199},
+					"text": 754
+				},
+				{
+					"position": {"x": 68, "y": 265},
+					"text": 755
+				},
+				{
+					"position": {"x": 68, "y": 331},
+					"text": 756
+				},
+				{
+					"position": {"x": 68, "y": 398},
+					"text": 757
+				},
+				{
+					"position": {"x": 68, "y": 465},
+					"text": 758
+				}
+			]
+		}
+	]
+}

From 5e3504f57847be30dbf985250074b85699fd6326 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 11:38:27 +0400
Subject: [PATCH 05/35] Add improvements

---
 client/gui/InterfaceBuilder.cpp  |  8 --------
 client/gui/InterfaceBuilder.h    |  9 ++++++++-
 client/lobby/RandomMapTab.cpp    | 32 ++++++++++++++++----------------
 config/windows/randomMapTab.json |  2 +-
 4 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/client/gui/InterfaceBuilder.cpp b/client/gui/InterfaceBuilder.cpp
index f8d383039..4beddbc04 100644
--- a/client/gui/InterfaceBuilder.cpp
+++ b/client/gui/InterfaceBuilder.cpp
@@ -42,14 +42,6 @@ void InterfaceBuilder::addCallback(const std::string & callbackName, std::functi
 	callbacks[callbackName] = callback;
 }
 
-const std::shared_ptr<CIntObject> InterfaceBuilder::widget(const std::string & name) const
-{
-	auto iter = widgets.find(name);
-	if(iter == widgets.end())
-		return nullptr;
-	return iter->second;
-}
-
 void InterfaceBuilder::init(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
diff --git a/client/gui/InterfaceBuilder.h b/client/gui/InterfaceBuilder.h
index 00740d7ef..135896ec0 100644
--- a/client/gui/InterfaceBuilder.h
+++ b/client/gui/InterfaceBuilder.h
@@ -26,7 +26,14 @@ protected:
 	
 	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
 	
-	const std::shared_ptr<CIntObject> widget(const std::string &) const;
+	template<class T>
+	const std::shared_ptr<T> widget(const std::string & name) const
+	{
+		auto iter = widgets.find(name);
+		if(iter == widgets.end())
+			return nullptr;
+		return dynamic_pointer_cast<T>(iter->second);
+	}
 	
 private:
 	
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 76543c3e4..e6b522366 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -53,11 +53,11 @@ RandomMapTab::RandomMapTab():
 	{
 		mapGenOptions->setPlayerCount(btnId);
 	
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")))
+		if(auto w = widget<CToggleGroup>("groupMaxTeams"))
 			deactivateButtonsFrom(w.get(), btnId);
 
 		// deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")))
+		if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
 			deactivateButtonsFrom(w.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
 
 		validatePlayersCnt(btnId);
@@ -75,10 +75,10 @@ RandomMapTab::RandomMapTab():
 		mapGenOptions->setCompOnlyPlayerCount(btnId);
 
 		// deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers")))
+		if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
 			deactivateButtonsFrom(w.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
 		
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")))
+		if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
 			deactivateButtonsFrom(w.get(), (btnId == 0 ? 1 : btnId));
 		validateCompOnlyPlayersCnt(btnId);
 		updateMapInfoByHost();
@@ -96,7 +96,7 @@ RandomMapTab::RandomMapTab():
 		updateMapInfoByHost();
 	});
 	
-	addCallback("setMonsterStrenght", [&](int btnId)
+	addCallback("setMonsterStrength", [&](int btnId)
 	{
 		if(btnId < 0)
 			mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
@@ -165,21 +165,21 @@ void RandomMapTab::updateMapInfoByHost()
 
 void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 {
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMapSize")))
+	if(auto w = widget<CToggleGroup>("groupMapSize"))
 		w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
-	if(auto w = dynamic_pointer_cast<CToggleButton>(widget("buttonTwoLevels")))
+	if(auto w = widget<CToggleButton>("buttonTwoLevels"))
 		w->setSelected(opts->getHasTwoLevels());
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxPlayers")))
+	if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
 		w->setSelected(opts->getPlayerCount());
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")))
+	if(auto w = widget<CToggleGroup>("groupMaxTeams"))
 		w->setSelected(opts->getTeamCount());
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")))
+	if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
 		w->setSelected(opts->getCompOnlyPlayerCount());
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")))
+	if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
 		w->setSelected(opts->getCompOnlyTeamCount());
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupWaterContent")))
+	if(auto w = widget<CToggleGroup>("groupWaterContent"))
 		w->setSelected(opts->getWaterContent());
-	if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupgroupMonsterStrengthMaxTeams")))
+	if(auto w = widget<CToggleGroup>("groupMonsterStrength"))
 		w->setSelected(opts->getMonsterStrength());
 }
 
@@ -212,14 +212,14 @@ void RandomMapTab::validatePlayersCnt(int playersCnt)
 	if(mapGenOptions->getTeamCount() >= playersCnt)
 	{
 		mapGenOptions->setTeamCount(playersCnt - 1);
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupMaxTeams")))
+		if(auto w = widget<CToggleGroup>("groupMaxTeams"))
 			w->setSelected(mapGenOptions->getTeamCount());
 	}
 	// total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3)
 	if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I)
 	{
 		mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyPlayers")))
+		if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
 			w->setSelected(mapGenOptions->getCompOnlyPlayerCount());
 	}
 
@@ -238,7 +238,7 @@ void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
 		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
 		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
 		updateMapInfoByHost();
-		if(auto w = dynamic_pointer_cast<CToggleGroup>(widget("groupCompOnlyTeams")))
+		if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
 			w->setSelected(compOnlyTeamCount);
 	}
 }
diff --git a/config/windows/randomMapTab.json b/config/windows/randomMapTab.json
index 189537ac9..a1741c121 100644
--- a/config/windows/randomMapTab.json
+++ b/config/windows/randomMapTab.json
@@ -462,7 +462,7 @@
 				}
 			],
 			"selected": 3,
-			"callback": "setMonsterStrenght"
+			"callback": "setMonsterStrength"
 		},
 
 		{

From 34bf7419364d73a73deb240fe627d7875c79cf14 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 11:43:54 +0400
Subject: [PATCH 06/35] Fix compilation error

---
 client/gui/InterfaceBuilder.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/gui/InterfaceBuilder.h b/client/gui/InterfaceBuilder.h
index 135896ec0..61a27e547 100644
--- a/client/gui/InterfaceBuilder.h
+++ b/client/gui/InterfaceBuilder.h
@@ -32,7 +32,7 @@ protected:
 		auto iter = widgets.find(name);
 		if(iter == widgets.end())
 			return nullptr;
-		return dynamic_pointer_cast<T>(iter->second);
+		return std::dynamic_pointer_cast<T>(iter->second);
 	}
 	
 private:

From cd58e8a860c202308fd35babcf6d386cd31cbafb Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 11:48:39 +0400
Subject: [PATCH 07/35] Rename InterfaceBuilder

---
 client/CMakeLists.txt                      |   4 +-
 client/gui/InterfaceBuilder.cpp            |  14 +-
 client/gui/InterfaceBuilder.h              |   6 +-
 client/gui/InterfaceObjectConfigurable.cpp | 204 +++++++++++++++++++++
 client/gui/InterfaceObjectConfigurable.h   |  46 +++++
 client/lobby/RandomMapTab.cpp              |   2 +-
 client/lobby/RandomMapTab.h                |   4 +-
 7 files changed, 265 insertions(+), 15 deletions(-)
 create mode 100644 client/gui/InterfaceObjectConfigurable.cpp
 create mode 100644 client/gui/InterfaceObjectConfigurable.h

diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 18886669b..d78ea8006 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -15,7 +15,7 @@ set(client_SRCS
 		gui/Geometries.cpp
 		gui/SDL_Extensions.cpp
 		gui/NotificationHandler.cpp
-		gui/InterfaceBuilder.cpp
+		gui/InterfaceObjectConfigurable.cpp
 
 		widgets/AdventureMapClasses.cpp
 		widgets/Buttons.cpp
@@ -91,7 +91,7 @@ set(client_HEADERS
 		gui/SDL_Extensions.h
 		gui/SDL_Pixels.h
 		gui/NotificationHandler.h
-		gui/InterfaceBuilder.h
+		gui/InterfaceObjectConfigurable.h
 
 		widgets/AdventureMapClasses.h
 		widgets/Buttons.h
diff --git a/client/gui/InterfaceBuilder.cpp b/client/gui/InterfaceBuilder.cpp
index 4beddbc04..40be3acc3 100644
--- a/client/gui/InterfaceBuilder.cpp
+++ b/client/gui/InterfaceBuilder.cpp
@@ -10,7 +10,7 @@
 
 #include "StdInc.h"
 
-#include "InterfaceBuilder.h"
+#include "InterfaceObjectConfigurable.h"
 
 #include "../CGameInfo.h"
 #include "../gui/CAnimation.h"
@@ -26,23 +26,23 @@
 #include "../../lib/CGeneralTextHandler.h"
 
 
-InterfaceBuilder::InterfaceBuilder(const JsonNode & config):
+InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config):
 	CIntObject()
 {
 	init(config);
 }
 
-InterfaceBuilder::InterfaceBuilder():
+InterfaceObjectConfigurable::InterfaceObjectConfigurable():
 	CIntObject()
 {
 }
 
-void InterfaceBuilder::addCallback(const std::string & callbackName, std::function<void(int)> callback)
+void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function<void(int)> callback)
 {
 	callbacks[callbackName] = callback;
 }
 
-void InterfaceBuilder::init(const JsonNode &config)
+void InterfaceObjectConfigurable::init(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
 	int unnamedObjectId = 0;
@@ -57,7 +57,7 @@ void InterfaceBuilder::init(const JsonNode &config)
 	}
 }
 
-std::string InterfaceBuilder::buildText(const JsonNode & config) const
+std::string InterfaceObjectConfigurable::buildText(const JsonNode & config) const
 {
 	if(config.isNull())
 		return "";
@@ -69,7 +69,7 @@ std::string InterfaceBuilder::buildText(const JsonNode & config) const
 	return config.String();
 }
 
-std::shared_ptr<CIntObject> InterfaceBuilder::buildWidget(const JsonNode & config)
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config)
 {
 	assert(!config.isNull());
 	auto type = config["type"].String();
diff --git a/client/gui/InterfaceBuilder.h b/client/gui/InterfaceBuilder.h
index 61a27e547..48501d03b 100644
--- a/client/gui/InterfaceBuilder.h
+++ b/client/gui/InterfaceBuilder.h
@@ -14,11 +14,11 @@
 
 #include "../../lib/JsonNode.h"
 
-class InterfaceBuilder: public CIntObject
+class InterfaceObjectConfigurable: public CIntObject
 {
 public:
-	InterfaceBuilder();
-	InterfaceBuilder(const JsonNode & config);
+	InterfaceObjectConfigurable();
+	InterfaceObjectConfigurable(const JsonNode & config);
 
 protected:
 	//must be called after adding callbacks
diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
new file mode 100644
index 000000000..3ec429257
--- /dev/null
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -0,0 +1,204 @@
+/*
+* InterfaceBuilder.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 "InterfaceObjectConfigurable.h"
+
+#include "../CGameInfo.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CGuiHandler.h"
+#include "../widgets/CComponent.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/MiscWidgets.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+#include "../windows/GUIClasses.h"
+#include "../windows/InfoWindows.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+
+
+InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config):
+	CIntObject()
+{
+	init(config);
+}
+
+InterfaceObjectConfigurable::InterfaceObjectConfigurable():
+	CIntObject()
+{
+}
+
+void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function<void(int)> callback)
+{
+	callbacks[callbackName] = callback;
+}
+
+void InterfaceObjectConfigurable::init(const JsonNode &config)
+{
+	OBJ_CONSTRUCTION;
+	int unnamedObjectId = 0;
+	const std::string unnamedObjectPrefix = "__widget_";
+	
+	for(const auto & item : config["items"].Vector())
+	{
+		std::string name = item["name"].isNull()
+						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
+						: item["name"].String();
+		widgets[name] = buildWidget(item);
+	}
+}
+
+std::string InterfaceObjectConfigurable::buildText(const JsonNode & config) const
+{
+	if(config.isNull())
+		return "";
+	
+	if(config.isNumber())
+	{
+		return CGI->generaltexth->allTexts[config.Integer()];
+	}
+	return config.String();
+}
+
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config)
+{
+	assert(!config.isNull());
+	auto type = config["type"].String();
+	
+	int x = 0, y = 0;
+	if(!config["position"].isNull())
+	{
+		x = config["position"]["x"].Integer();
+		y = config["position"]["y"].Integer();
+	}
+	
+	std::string image;
+	std::string text = buildText(config["text"]);
+	auto alignment = EAlignment::CENTER;
+	auto color = Colors::DEFAULT_KEY_COLOR;
+	auto font = EFonts::FONT_TIMES;
+	
+	if(!config["image"].isNull())
+		image = config["image"].String();
+	if(!config["alignment"].isNull())
+	{
+		if(config["alignment"].String() == "left")
+			alignment = EAlignment::TOPLEFT;
+		if(config["alignment"].String() == "center")
+			alignment = EAlignment::CENTER;
+		if(config["alignment"].String() == "right")
+			alignment = EAlignment::BOTTOMRIGHT;
+	}
+	if(!config["color"].isNull())
+	{
+		if(config["color"].String() == "yellow")
+			color = Colors::YELLOW;
+		if(config["color"].String() == "white")
+			color = Colors::WHITE;
+		if(config["color"].String() == "gold")
+			color = Colors::METALLIC_GOLD;
+		if(config["color"].String() == "green")
+			color = Colors::GREEN;
+		if(config["color"].String() == "orange")
+			color = Colors::ORANGE;
+		if(config["color"].String() == "bright-yellow")
+			color = Colors::BRIGHT_YELLOW;
+	}
+	if(!config["font"].isNull())
+	{
+		if(config["font"].String() == "big")
+			font = EFonts::FONT_BIG;
+		if(config["font"].String() == "medium")
+			font = EFonts::FONT_MEDIUM;
+		if(config["font"].String() == "small")
+			font = EFonts::FONT_SMALL;
+		if(config["font"].String() == "tiny")
+			font = EFonts::FONT_TINY;
+	}
+	
+	
+	if(type == "picture")
+	{
+		return std::make_shared<CPicture>(image, x, y);
+	}
+	if(type == "label")
+	{
+		return std::make_shared<CLabel>(x, y, font, alignment, color, text);
+	}
+	if(type == "toggleGroup")
+	{
+		auto group = std::make_shared<CToggleGroup>(0);
+		group->pos.x += x;
+		group->pos.y += y;
+		if(!config["items"].isNull())
+		{
+			SObjectConstruction obj__i(group.get());
+			int itemIdx = -1;
+			for(const auto & item : config["items"].Vector())
+			{
+				itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
+				group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
+			}
+		}
+		if(!config["selected"].isNull())
+			group->setSelected(config["selected"].Integer());
+		if(!config["callback"].isNull())
+			group->addCallback(callbacks[config["callback"].String()]);
+		return group;
+	}
+	if(type == "toggleButton")
+	{
+		std::pair<std::string, std::string> zelp;
+		if(!config["zelp"].isNull())
+			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
+		auto button = std::make_shared<CToggleButton>(Point(x, y), image, zelp);
+		if(!config["selected"].isNull())
+			button->setSelected(config["selected"].Bool());
+		if(!config["imageOrder"].isNull())
+		{
+			auto imgOrder = config["imageOrder"].Vector();
+			assert(imgOrder.size() >= 4);
+			button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
+		}
+		if(!config["callback"].isNull())
+			button->addCallback(callbacks[config["callback"].String()]);
+		return button;
+	}
+	if(type == "button")
+	{
+		std::pair<std::string, std::string> zelp;
+		if(!config["zelp"].isNull())
+			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
+		auto button = std::make_shared<CButton>(Point(x, y), image, zelp);
+		return button;
+	}
+	if(type == "labelGroup")
+	{
+		auto group = std::make_shared<CLabelGroup>(font, alignment, color);
+		if(!config["items"].isNull())
+		{
+			for(const auto & item : config["items"].Vector())
+			{
+				if(!item["position"].isNull())
+				{
+					x = item["position"]["x"].Integer();
+					y = item["position"]["y"].Integer();
+				}
+				if(!item["text"].isNull())
+					text = buildText(item["text"]);
+				group->add(x, y, text);
+			}
+		}
+		return group;
+	}
+	return std::shared_ptr<CIntObject>(nullptr);
+}
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
new file mode 100644
index 000000000..48501d03b
--- /dev/null
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -0,0 +1,46 @@
+/*
+* InterfaceBuilder.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 "CIntObject.h"
+
+#include "../../lib/JsonNode.h"
+
+class InterfaceObjectConfigurable: public CIntObject
+{
+public:
+	InterfaceObjectConfigurable();
+	InterfaceObjectConfigurable(const JsonNode & config);
+
+protected:
+	//must be called after adding callbacks
+	void init(const JsonNode & config);
+	
+	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
+	
+	template<class T>
+	const std::shared_ptr<T> widget(const std::string & name) const
+	{
+		auto iter = widgets.find(name);
+		if(iter == widgets.end())
+			return nullptr;
+		return std::dynamic_pointer_cast<T>(iter->second);
+	}
+	
+private:
+	
+	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
+	std::map<std::string, std::function<void(int)>> callbacks;
+	
+	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config);
+	
+	std::string buildText(const JsonNode & param) const;
+};
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index e6b522366..eaf8dd264 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -30,7 +30,7 @@
 #include "../../lib/CModHandler.h"
 
 RandomMapTab::RandomMapTab():
-	InterfaceBuilder()
+	InterfaceObjectConfigurable()
 {
 	recActions = 0;
 	mapGenOptions = std::make_shared<CMapGenOptions>();
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index ac78247f9..6b82ae3e2 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -13,7 +13,7 @@
 
 #include "../../lib/FunctionList.h"
 #include "../../lib/GameConstants.h"
-#include "../gui/InterfaceBuilder.h"
+#include "../gui/InterfaceObjectConfigurable.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -25,7 +25,7 @@ class CToggleButton;
 class CLabel;
 class CLabelGroup;
 
-class RandomMapTab : public InterfaceBuilder
+class RandomMapTab : public InterfaceObjectConfigurable
 {
 public:
 	RandomMapTab();

From 690ff773f4d3ba30c69f6b65100373c4b508ecc4 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Mon, 12 Dec 2022 11:53:23 +0400
Subject: [PATCH 08/35] Remove files

---
 client/gui/InterfaceBuilder.cpp | 203 --------------------------------
 client/gui/InterfaceBuilder.h   |  46 --------
 2 files changed, 249 deletions(-)
 delete mode 100644 client/gui/InterfaceBuilder.cpp
 delete mode 100644 client/gui/InterfaceBuilder.h

diff --git a/client/gui/InterfaceBuilder.cpp b/client/gui/InterfaceBuilder.cpp
deleted file mode 100644
index 40be3acc3..000000000
--- a/client/gui/InterfaceBuilder.cpp
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
-* InterfaceBuilder.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 "InterfaceObjectConfigurable.h"
-
-#include "../CGameInfo.h"
-#include "../gui/CAnimation.h"
-#include "../gui/CGuiHandler.h"
-#include "../widgets/CComponent.h"
-#include "../widgets/Buttons.h"
-#include "../widgets/MiscWidgets.h"
-#include "../widgets/ObjectLists.h"
-#include "../widgets/TextControls.h"
-#include "../windows/GUIClasses.h"
-#include "../windows/InfoWindows.h"
-
-#include "../../lib/CGeneralTextHandler.h"
-
-
-InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config):
-	CIntObject()
-{
-	init(config);
-}
-
-InterfaceObjectConfigurable::InterfaceObjectConfigurable():
-	CIntObject()
-{
-}
-
-void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function<void(int)> callback)
-{
-	callbacks[callbackName] = callback;
-}
-
-void InterfaceObjectConfigurable::init(const JsonNode &config)
-{
-	OBJ_CONSTRUCTION;
-	int unnamedObjectId = 0;
-	const std::string unnamedObjectPrefix = "__widget_";
-	
-	for(const auto & item : config["items"].Vector())
-	{
-		std::string name = item["name"].isNull()
-						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
-						: item["name"].String();
-		widgets[name] = buildWidget(item);
-	}
-}
-
-std::string InterfaceObjectConfigurable::buildText(const JsonNode & config) const
-{
-	if(config.isNull())
-		return "";
-	
-	if(config.isNumber())
-	{
-		return CGI->generaltexth->allTexts[config.Integer()];
-	}
-	return config.String();
-}
-
-std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config)
-{
-	assert(!config.isNull());
-	auto type = config["type"].String();
-	
-	int x = 0, y = 0;
-	if(!config["position"].isNull())
-	{
-		x = config["position"]["x"].Integer();
-		y = config["position"]["y"].Integer();
-	}
-	
-	std::string image, text = buildText(config["text"]);
-	auto alignment = EAlignment::CENTER;
-	auto color = Colors::DEFAULT_KEY_COLOR;
-	auto font = EFonts::FONT_TIMES;
-	
-	if(!config["image"].isNull())
-		image = config["image"].String();
-	if(!config["alignment"].isNull())
-	{
-		if(config["alignment"].String() == "left")
-			alignment = EAlignment::TOPLEFT;
-		if(config["alignment"].String() == "center")
-			alignment = EAlignment::CENTER;
-		if(config["alignment"].String() == "right")
-			alignment = EAlignment::BOTTOMRIGHT;
-	}
-	if(!config["color"].isNull())
-	{
-		if(config["color"].String() == "yellow")
-			color = Colors::YELLOW;
-		if(config["color"].String() == "white")
-			color = Colors::WHITE;
-		if(config["color"].String() == "gold")
-			color = Colors::METALLIC_GOLD;
-		if(config["color"].String() == "green")
-			color = Colors::GREEN;
-		if(config["color"].String() == "orange")
-			color = Colors::ORANGE;
-		if(config["color"].String() == "bright-yellow")
-			color = Colors::BRIGHT_YELLOW;
-	}
-	if(!config["font"].isNull())
-	{
-		if(config["font"].String() == "big")
-			font = EFonts::FONT_BIG;
-		if(config["font"].String() == "medium")
-			font = EFonts::FONT_MEDIUM;
-		if(config["font"].String() == "small")
-			font = EFonts::FONT_SMALL;
-		if(config["font"].String() == "tiny")
-			font = EFonts::FONT_TINY;
-	}
-	
-	
-	if(type == "picture")
-	{
-		return std::make_shared<CPicture>(image, x, y);
-	}
-	if(type == "label")
-	{
-		return std::make_shared<CLabel>(x, y, font, alignment, color, text);
-	}
-	if(type == "toggleGroup")
-	{
-		auto group = std::make_shared<CToggleGroup>(0);
-		group->pos.x += x;
-		group->pos.y += y;
-		if(!config["items"].isNull())
-		{
-			SObjectConstruction obj__i(group.get());
-			int itemIdx = -1;
-			for(const auto & item : config["items"].Vector())
-			{
-				itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
-				group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
-			}
-		}
-		if(!config["selected"].isNull())
-			group->setSelected(config["selected"].Integer());
-		if(!config["callback"].isNull())
-			group->addCallback(callbacks[config["callback"].String()]);
-		return group;
-	}
-	if(type == "toggleButton")
-	{
-		std::pair<std::string, std::string> zelp;
-		if(!config["zelp"].isNull())
-			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
-		auto button = std::make_shared<CToggleButton>(Point(x, y), image, zelp);
-		if(!config["selected"].isNull())
-			button->setSelected(config["selected"].Bool());
-		if(!config["imageOrder"].isNull())
-		{
-			auto imgOrder = config["imageOrder"].Vector();
-			assert(imgOrder.size() >= 4);
-			button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
-		}
-		if(!config["callback"].isNull())
-			button->addCallback(callbacks[config["callback"].String()]);
-		return button;
-	}
-	if(type == "button")
-	{
-		std::pair<std::string, std::string> zelp;
-		if(!config["zelp"].isNull())
-			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
-		auto button = std::make_shared<CButton>(Point(x, y), image, zelp);
-		return button;
-	}
-	if(type == "labelGroup")
-	{
-		auto group = std::make_shared<CLabelGroup>(font, alignment, color);
-		if(!config["items"].isNull())
-		{
-			for(const auto & item : config["items"].Vector())
-			{
-				if(!item["position"].isNull())
-				{
-					x = item["position"]["x"].Integer();
-					y = item["position"]["y"].Integer();
-				}
-				if(!item["text"].isNull())
-					text = buildText(item["text"]);
-				group->add(x, y, text);
-			}
-		}
-		return group;
-	}
-	return std::shared_ptr<CIntObject>(nullptr);
-}
diff --git a/client/gui/InterfaceBuilder.h b/client/gui/InterfaceBuilder.h
deleted file mode 100644
index 48501d03b..000000000
--- a/client/gui/InterfaceBuilder.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
-* InterfaceBuilder.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 "CIntObject.h"
-
-#include "../../lib/JsonNode.h"
-
-class InterfaceObjectConfigurable: public CIntObject
-{
-public:
-	InterfaceObjectConfigurable();
-	InterfaceObjectConfigurable(const JsonNode & config);
-
-protected:
-	//must be called after adding callbacks
-	void init(const JsonNode & config);
-	
-	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
-	
-	template<class T>
-	const std::shared_ptr<T> widget(const std::string & name) const
-	{
-		auto iter = widgets.find(name);
-		if(iter == widgets.end())
-			return nullptr;
-		return std::dynamic_pointer_cast<T>(iter->second);
-	}
-	
-private:
-	
-	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
-	std::map<std::string, std::function<void(int)>> callbacks;
-	
-	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config);
-	
-	std::string buildText(const JsonNode & param) const;
-};

From f90cb1be9025a886df630e6737ba0b2bd6c03ddb Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Tue, 13 Dec 2022 03:47:29 +0400
Subject: [PATCH 09/35] Template list prototype looks fine

---
 client/gui/InterfaceObjectConfigurable.cpp |  9 +++
 client/lobby/RandomMapTab.cpp              | 67 ++++++++++++++++++++++
 client/lobby/RandomMapTab.h                | 31 ++++++++++
 3 files changed, 107 insertions(+)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 3ec429257..6c8c64f38 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -179,6 +179,15 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonN
 		if(!config["zelp"].isNull())
 			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
 		auto button = std::make_shared<CButton>(Point(x, y), image, zelp);
+		if(!config["items"].isNull())
+		{
+			for(const auto & item : config["items"].Vector())
+			{
+				button->addOverlay(buildWidget(item));
+			}
+		}
+		if(!config["callback"].isNull())
+			button->addCallback(std::bind(callbacks[config["callback"].String()], 0));
 		return button;
 	}
 	if(type == "labelGroup")
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index eaf8dd264..163cc5798 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -105,6 +105,13 @@ RandomMapTab::RandomMapTab():
 		updateMapInfoByHost();
 	});
 	
+	//new callbacks available only from mod
+	addCallback("templateSelection", [&](int)
+	{
+		GH.pushInt(std::shared_ptr<TemplatesDropBox>(new TemplatesDropBox(mapGenOptions)));
+	});
+	
+	
 	init(config);
 	
 	updateMapInfoByHost();
@@ -247,3 +254,63 @@ std::vector<int> RandomMapTab::getPossibleMapSizes()
 {
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
+
+TemplatesDropBox::ListItem::ListItem(Point position, const std::string & text)
+	: CIntObject(LCLICK | HOVER, position)
+{
+	OBJ_CONSTRUCTION;
+	labelName = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, text);
+	labelName->setAutoRedraw(false);
+	
+	hoverImage = std::make_shared<CPicture>("List10Sl", 0, 0);
+	hoverImage->visible = false;
+	
+	pos.w = hoverImage->pos.w;
+	pos.h = hoverImage->pos.h;
+	type |= REDRAW_PARENT;
+}
+
+void TemplatesDropBox::ListItem::hover(bool on)
+{
+	hoverImage->visible = on;
+	redraw();
+}
+
+TemplatesDropBox::TemplatesDropBox(std::shared_ptr<CMapGenOptions> options):
+	CIntObject(LCLICK | HOVER),
+	mapGenOptions(options)
+{
+	OBJ_CONSTRUCTION;
+	background = std::make_shared<CPicture>("List10Bk", 158, 76);
+	
+	int positionsToShow = 10;
+	
+	for(int i = 0; i < positionsToShow; i++)
+		listItems.push_back(std::make_shared<ListItem>(Point(158, 76 + i * 25), "test" + std::to_string(i)));
+	
+	slider = std::make_shared<CSlider>(Point(212 + 158, 76), 252, std::bind(&TemplatesDropBox::sliderMove, this, _1), positionsToShow, 20, 0, false, CSlider::BLUE);
+	
+	pos = background->pos;
+}
+
+void TemplatesDropBox::sliderMove(int slidPos)
+{
+	if(!slider)
+		return; // ignore spurious call when slider is being created
+	//updateListItems();
+	redraw();
+}
+
+void TemplatesDropBox::hover(bool on)
+{
+	hovered = on;
+}
+
+void TemplatesDropBox::clickLeft(tribool down, bool previousState)
+{
+	if(!hovered)
+	{
+		assert(GH.topInt().get() == this);
+		GH.popInt(GH.topInt());
+	}
+}
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index 6b82ae3e2..a9551cea6 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -24,6 +24,7 @@ VCMI_LIB_NAMESPACE_END
 class CToggleButton;
 class CLabel;
 class CLabelGroup;
+class CSlider;
 
 class RandomMapTab : public InterfaceObjectConfigurable
 {
@@ -44,3 +45,33 @@ private:
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
 	std::shared_ptr<CMapInfo> mapInfo;
 };
+
+class TemplatesDropBox : public CIntObject
+{
+	struct ListItem : public CIntObject
+	{
+		std::shared_ptr<CLabel> labelName;
+		std::shared_ptr<CPicture> hoverImage;
+		ListItem(Point position, const std::string & text);
+		void updateItem(int info = 0, bool selected = false);
+		
+		void hover(bool on) override;
+	};
+	
+public:
+	TemplatesDropBox(std::shared_ptr<CMapGenOptions> options);
+	
+	void hover(bool on) override;
+	void clickLeft(tribool down, bool previousState) override;
+	
+private:
+	
+	void sliderMove(int slidPos);
+	
+	std::shared_ptr<CMapGenOptions> mapGenOptions;
+	
+	std::shared_ptr<CPicture> background;
+	std::vector<std::shared_ptr<ListItem>> listItems;
+	std::shared_ptr<CSlider> slider;
+	
+};

From 1e2abae62bc10f46d6a1cce3f0752e5b46a94591 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Tue, 13 Dec 2022 04:38:18 +0400
Subject: [PATCH 10/35] Template selections works

---
 client/lobby/RandomMapTab.cpp | 99 ++++++++++++++++++++++++++++++-----
 client/lobby/RandomMapTab.h   | 19 +++++--
 2 files changed, 101 insertions(+), 17 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 163cc5798..c65b3d6ef 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -28,6 +28,7 @@
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/rmg/CMapGenOptions.h"
 #include "../../lib/CModHandler.h"
+#include "../../lib/rmg/CRmgTemplateStorage.h"
 
 RandomMapTab::RandomMapTab():
 	InterfaceObjectConfigurable()
@@ -108,7 +109,7 @@ RandomMapTab::RandomMapTab():
 	//new callbacks available only from mod
 	addCallback("templateSelection", [&](int)
 	{
-		GH.pushInt(std::shared_ptr<TemplatesDropBox>(new TemplatesDropBox(mapGenOptions)));
+		GH.pushInt(std::make_shared<TemplatesDropBox>(this));
 	});
 	
 	
@@ -190,6 +191,18 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 		w->setSelected(opts->getMonsterStrength());
 }
 
+void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
+{
+	mapGenOptions->setMapTemplate(tmpl);
+	if(auto w = widget<CButton>("templateButton"))
+	{
+		if(tmpl)
+			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
+		else
+			w->addTextOverlay("default", EFonts::FONT_SMALL);
+	}
+}
+
 void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
 {
 	logGlobal->debug("Blocking buttons from %d", startId);
@@ -255,11 +268,12 @@ std::vector<int> RandomMapTab::getPossibleMapSizes()
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
 
-TemplatesDropBox::ListItem::ListItem(Point position, const std::string & text)
-	: CIntObject(LCLICK | HOVER, position)
+TemplatesDropBox::ListItem::ListItem(TemplatesDropBox * _dropBox, Point position)
+	: CIntObject(LCLICK | HOVER, position),
+	dropBox(_dropBox)
 {
 	OBJ_CONSTRUCTION;
-	labelName = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, text);
+	labelName = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE);
 	labelName->setAutoRedraw(false);
 	
 	hoverImage = std::make_shared<CPicture>("List10Sl", 0, 0);
@@ -270,26 +284,63 @@ TemplatesDropBox::ListItem::ListItem(Point position, const std::string & text)
 	type |= REDRAW_PARENT;
 }
 
+void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item)
+{
+	item = _item;
+	if(item)
+	{
+		labelName->setText(item->getName());
+	}
+	else
+	{
+		if(idx)
+			labelName->setText("");
+		else
+			labelName->setText("default");
+	}
+}
+
 void TemplatesDropBox::ListItem::hover(bool on)
 {
-	hoverImage->visible = on;
+	if(labelName->getText().empty())
+	{
+		hovered = false;
+		hoverImage->visible = false;
+	}
+	else
+	{
+		hoverImage->visible = on;
+	}
 	redraw();
 }
 
-TemplatesDropBox::TemplatesDropBox(std::shared_ptr<CMapGenOptions> options):
-	CIntObject(LCLICK | HOVER),
-	mapGenOptions(options)
+void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
 {
+	if(down && hovered)
+	{
+		dropBox->setTemplate(item);
+	}
+}
+
+
+TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab):
+	CIntObject(LCLICK | HOVER),
+	randomMapTab(randomMapTab)
+{
+	curItems = VLC->tplh->getTemplates();
+	curItems.insert(curItems.begin(), nullptr); //default template
+	
 	OBJ_CONSTRUCTION;
 	background = std::make_shared<CPicture>("List10Bk", 158, 76);
 	
 	int positionsToShow = 10;
 	
 	for(int i = 0; i < positionsToShow; i++)
-		listItems.push_back(std::make_shared<ListItem>(Point(158, 76 + i * 25), "test" + std::to_string(i)));
+		listItems.push_back(std::make_shared<ListItem>(this, Point(158, 76 + i * 25)));
 	
-	slider = std::make_shared<CSlider>(Point(212 + 158, 76), 252, std::bind(&TemplatesDropBox::sliderMove, this, _1), positionsToShow, 20, 0, false, CSlider::BLUE);
+	slider = std::make_shared<CSlider>(Point(212 + 158, 76), 252, std::bind(&TemplatesDropBox::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, false, CSlider::BLUE);
 	
+	updateListItems();
 	pos = background->pos;
 }
 
@@ -297,7 +348,7 @@ void TemplatesDropBox::sliderMove(int slidPos)
 {
 	if(!slider)
 		return; // ignore spurious call when slider is being created
-	//updateListItems();
+	updateListItems();
 	redraw();
 }
 
@@ -308,9 +359,33 @@ void TemplatesDropBox::hover(bool on)
 
 void TemplatesDropBox::clickLeft(tribool down, bool previousState)
 {
-	if(!hovered)
+	if(down && !hovered)
 	{
 		assert(GH.topInt().get() == this);
 		GH.popInt(GH.topInt());
 	}
 }
+
+void TemplatesDropBox::updateListItems()
+{
+	int elemIdx = slider->getValue();
+	for(auto item : listItems)
+	{
+		if(elemIdx < curItems.size())
+		{
+			item->updateItem(elemIdx, curItems[elemIdx]);
+			elemIdx++;
+		}
+		else
+		{
+			item->updateItem(elemIdx);
+		}
+	}
+}
+
+void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
+{
+	randomMapTab->setTemplate(tmpl);
+	assert(GH.topInt().get() == this);
+	GH.popInt(GH.topInt());
+}
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index a9551cea6..3728240c8 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -13,6 +13,7 @@
 
 #include "../../lib/FunctionList.h"
 #include "../../lib/GameConstants.h"
+#include "../../lib/rmg/CRmgTemplate.h"
 #include "../gui/InterfaceObjectConfigurable.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -33,6 +34,7 @@ public:
 
 	void updateMapInfoByHost();
 	void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
+	void setTemplate(const CRmgTemplate *);
 
 	CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
 
@@ -52,26 +54,33 @@ class TemplatesDropBox : public CIntObject
 	{
 		std::shared_ptr<CLabel> labelName;
 		std::shared_ptr<CPicture> hoverImage;
-		ListItem(Point position, const std::string & text);
-		void updateItem(int info = 0, bool selected = false);
+		TemplatesDropBox * dropBox;
+		const CRmgTemplate * item = nullptr;
+		
+		ListItem(TemplatesDropBox *, Point position);
+		void updateItem(int index, const CRmgTemplate * item = nullptr);
 		
 		void hover(bool on) override;
+		void clickLeft(tribool down, bool previousState) override;
 	};
 	
 public:
-	TemplatesDropBox(std::shared_ptr<CMapGenOptions> options);
+	TemplatesDropBox(RandomMapTab * randomMapTab);
 	
 	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override;
+	void setTemplate(const CRmgTemplate *);
 	
 private:
 	
 	void sliderMove(int slidPos);
+	void updateListItems();
 	
-	std::shared_ptr<CMapGenOptions> mapGenOptions;
-	
+	RandomMapTab * randomMapTab;
 	std::shared_ptr<CPicture> background;
 	std::vector<std::shared_ptr<ListItem>> listItems;
 	std::shared_ptr<CSlider> slider;
 	
+	std::vector<const CRmgTemplate *> curItems;
+	
 };

From 53b2f68560f059f0cef9a7b8161245f0f43fc85f Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Wed, 14 Dec 2022 04:37:11 +0400
Subject: [PATCH 11/35] Serialize template

---
 client/lobby/RandomMapTab.cpp   | 124 +++++++++++++++++++++++++-------
 client/lobby/RandomMapTab.h     |   6 +-
 lib/rmg/CMapGenOptions.cpp      |  26 ++++++-
 lib/rmg/CMapGenOptions.h        |  40 ++++-------
 lib/rmg/CRmgTemplate.cpp        |  17 ++++-
 lib/rmg/CRmgTemplate.h          |  29 +++++++-
 lib/rmg/CRmgTemplateStorage.cpp |   3 +-
 lib/rmg/CZonePlacer.cpp         |   1 +
 lib/rmg/Functions.h             |   1 +
 9 files changed, 189 insertions(+), 58 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index c65b3d6ef..77459a1b7 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -47,21 +47,15 @@ RandomMapTab::RandomMapTab():
 	addCallback("toggleTwoLevels", [&](bool on)
 	{
 		mapGenOptions->setHasTwoLevels(on);
+		setTemplate(mapGenOptions->getMapTemplate());
 		updateMapInfoByHost();
 	});
 	
 	addCallback("setPlayersCount", [&](int btnId)
 	{
 		mapGenOptions->setPlayerCount(btnId);
-	
-		if(auto w = widget<CToggleGroup>("groupMaxTeams"))
-			deactivateButtonsFrom(w.get(), btnId);
-
-		// deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
-			deactivateButtonsFrom(w.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
-
-		validatePlayersCnt(btnId);
+		setMapGenOptions(mapGenOptions);
+		//validatePlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
 	
@@ -74,14 +68,8 @@ RandomMapTab::RandomMapTab():
 	addCallback("setCompOnlyPlayers", [&](int btnId)
 	{
 		mapGenOptions->setCompOnlyPlayerCount(btnId);
-
-		// deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
-			deactivateButtonsFrom(w.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
-		
-		if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
-			deactivateButtonsFrom(w.get(), (btnId == 0 ? 1 : btnId));
-		validateCompOnlyPlayersCnt(btnId);
+		setMapGenOptions(mapGenOptions);
+		//validateCompOnlyPlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
 	
@@ -173,20 +161,80 @@ void RandomMapTab::updateMapInfoByHost()
 
 void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 {
+	mapGenOptions = opts;
+	
+	//prepare allowed options
+	for(int i = 0; i <= PlayerColor::PLAYER_LIMIT_I; ++i)
+	{
+		playerCountAllowed.insert(i);
+		compCountAllowed.insert(i);
+		playerTeamsAllowed.insert(i);
+		compTeamsAllowed.insert(i);
+	}
+	auto * tmpl = mapGenOptions->getMapTemplate();
+	if(tmpl)
+	{
+		playerCountAllowed = tmpl->getPlayers().getNumbers();
+		compCountAllowed = tmpl->getCpuPlayers().getNumbers();
+	}
+	if(mapGenOptions->getPlayerCount() != CMapGenOptions::RANDOM_SIZE)
+	{
+		vstd::erase_if(compCountAllowed,
+		[opts](int el){
+			return PlayerColor::PLAYER_LIMIT_I - opts->getPlayerCount() < el;
+		});
+		vstd::erase_if(playerTeamsAllowed,
+		[opts](int el){
+			return PlayerColor::PLAYER_LIMIT_I - opts->getPlayerCount() < el + 1;
+		});
+	}
+	if(mapGenOptions->getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE)
+	{
+		vstd::erase_if(playerCountAllowed,
+		[opts](int el){
+			return PlayerColor::PLAYER_LIMIT_I - opts->getCompOnlyPlayerCount() < el;
+		});
+		vstd::erase_if(compTeamsAllowed,
+		[opts](int el){
+			return PlayerColor::PLAYER_LIMIT_I - opts->getCompOnlyPlayerCount() < el + 1;
+		});
+	}
+	
 	if(auto w = widget<CToggleGroup>("groupMapSize"))
 		w->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
 	if(auto w = widget<CToggleButton>("buttonTwoLevels"))
 		w->setSelected(opts->getHasTwoLevels());
 	if(auto w = widget<CToggleGroup>("groupMaxPlayers"))
+	{
 		w->setSelected(opts->getPlayerCount());
+		deactivateButtonsFrom(*w, playerCountAllowed);
+	}
 	if(auto w = widget<CToggleGroup>("groupMaxTeams"))
+	{
 		w->setSelected(opts->getTeamCount());
+		deactivateButtonsFrom(*w, playerCountAllowed);
+	}
 	if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
+	{
 		w->setSelected(opts->getCompOnlyPlayerCount());
+		deactivateButtonsFrom(*w, playerTeamsAllowed);
+	}
 	if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
+	{
 		w->setSelected(opts->getCompOnlyTeamCount());
+		deactivateButtonsFrom(*w, compTeamsAllowed);
+	}
 	if(auto w = widget<CToggleGroup>("groupWaterContent"))
+	{
 		w->setSelected(opts->getWaterContent());
+		if(opts->getMapTemplate())
+		{
+			std::set<int> allowedWater(opts->getMapTemplate()->getWaterContentAllowed().begin(), opts->getMapTemplate()->getWaterContentAllowed().end());
+			deactivateButtonsFrom(*w, allowedWater);
+		}
+		else
+			deactivateButtonsFrom(*w, {-1});
+	}
 	if(auto w = widget<CToggleGroup>("groupMonsterStrength"))
 		w->setSelected(opts->getMonsterStrength());
 }
@@ -194,6 +242,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
 {
 	mapGenOptions->setMapTemplate(tmpl);
+	setMapGenOptions(mapGenOptions);
 	if(auto w = widget<CButton>("templateButton"))
 	{
 		if(tmpl)
@@ -201,16 +250,41 @@ void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
 		else
 			w->addTextOverlay("default", EFonts::FONT_SMALL);
 	}
+	updateMapInfoByHost();
 }
 
-void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
+void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, int startAllowed, int endAllowed)
 {
-	logGlobal->debug("Blocking buttons from %d", startId);
-	for(auto toggle : group->buttons)
+	logGlobal->debug("Blocking all buttons except %d - %d", startAllowed, endAllowed);
+	for(auto toggle : group.buttons)
 	{
 		if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
 		{
-			if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
+			if(toggle.first == CMapGenOptions::RANDOM_SIZE
+			   || (startAllowed == CMapGenOptions::RANDOM_SIZE && endAllowed == CMapGenOptions::RANDOM_SIZE)
+			   || (toggle.first >= startAllowed
+			   && (endAllowed == CMapGenOptions::RANDOM_SIZE || toggle.first <= endAllowed)))
+			{
+				//button->block(false);
+			}
+			else
+			{
+				button->block(true);
+			}
+		}
+	}
+}
+
+void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed)
+{
+	logGlobal->debug("Blocking buttons");
+	for(auto toggle : group.buttons)
+	{
+		if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
+		{
+			if(allowed.count(CMapGenOptions::RANDOM_SIZE)
+			   || allowed.count(toggle.first)
+			   || toggle.first == CMapGenOptions::RANDOM_SIZE)
 			{
 				button->block(false);
 			}
@@ -229,7 +303,7 @@ void RandomMapTab::validatePlayersCnt(int playersCnt)
 		return;
 	}
 
-	if(mapGenOptions->getTeamCount() >= playersCnt)
+	/*if(mapGenOptions->getTeamCount() >= playersCnt)
 	{
 		mapGenOptions->setTeamCount(playersCnt - 1);
 		if(auto w = widget<CToggleGroup>("groupMaxTeams"))
@@ -241,7 +315,7 @@ void RandomMapTab::validatePlayersCnt(int playersCnt)
 		mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
 		if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
 			w->setSelected(mapGenOptions->getCompOnlyPlayerCount());
-	}
+	}*/
 
 	validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
 }
@@ -253,14 +327,14 @@ void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
 		return;
 	}
 
-	if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
+	/*if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
 	{
 		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
 		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
 		updateMapInfoByHost();
 		if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
 			w->setSelected(compOnlyTeamCount);
-	}
+	}*/
 }
 
 std::vector<int> RandomMapTab::getPossibleMapSizes()
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index 3728240c8..ba2723f5a 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -39,13 +39,17 @@ public:
 	CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
 
 private:
-	void deactivateButtonsFrom(CToggleGroup * group, int startId);
+	void deactivateButtonsFrom(CToggleGroup & group, int startAllower, int endAllowed);
+	void deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed);
 	void validatePlayersCnt(int playersCnt);
 	void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
 	std::vector<int> getPossibleMapSizes();
 
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
 	std::shared_ptr<CMapInfo> mapInfo;
+	
+	//options allowed - need to store as impact each other
+	std::set<int> playerCountAllowed, playerTeamsAllowed, compCountAllowed, compTeamsAllowed;
 };
 
 class TemplatesDropBox : public CIntObject
diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp
index 141e61782..bac65c5af 100644
--- a/lib/rmg/CMapGenOptions.cpp
+++ b/lib/rmg/CMapGenOptions.cpp
@@ -204,8 +204,30 @@ const CRmgTemplate * CMapGenOptions::getMapTemplate() const
 void CMapGenOptions::setMapTemplate(const CRmgTemplate * value)
 {
 	mapTemplate = value;
-	//TODO validate & adapt options according to template
-	//assert(0);
+	//validate & adapt options according to template
+	if(mapTemplate)
+	{
+		if(!mapTemplate->matchesSize(int3(getWidth(), getHeight(), 1 + getHasTwoLevels())))
+		{
+			auto sizes = mapTemplate->getMapSizes();
+			setWidth(sizes.first.x);
+			setHeight(sizes.first.y);
+			setHasTwoLevels(sizes.first.z - 1);
+		}
+		
+		if(!mapTemplate->getPlayers().isInRange(getPlayerCount()))
+			setPlayerCount(RANDOM_SIZE);
+		if(!mapTemplate->getCpuPlayers().isInRange(getCompOnlyPlayerCount()))
+			setCompOnlyPlayerCount(RANDOM_SIZE);
+		if(!mapTemplate->getWaterContentAllowed().count(getWaterContent()))
+			setWaterContent(EWaterContent::RANDOM);
+	}
+}
+
+void CMapGenOptions::setMapTemplate(const std::string & name)
+{
+	if(!name.empty())
+		setMapTemplate(VLC->tplh->getTemplate(name));
 }
 
 void CMapGenOptions::finalize(CRandomGenerator & rand)
diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h
index 9017576b1..d8277518c 100644
--- a/lib/rmg/CMapGenOptions.h
+++ b/lib/rmg/CMapGenOptions.h
@@ -11,37 +11,12 @@
 #pragma once
 
 #include "../GameConstants.h"
+#include "CRmgTemplate.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CRmgTemplate;
 class CRandomGenerator;
 
-namespace EWaterContent
-{
-	enum EWaterContent
-	{
-		RANDOM = -1,
-		NONE,
-		NORMAL,
-		ISLANDS
-	};
-}
-
-namespace EMonsterStrength
-{
-	enum EMonsterStrength
-	{
-		RANDOM = -2,
-		ZONE_WEAK = -1,
-		ZONE_NORMAL = 0,
-		ZONE_STRONG = 1,
-		GLOBAL_WEAK = 2,
-		GLOBAL_NORMAL = 3,
-		GLOBAL_STRONG = 4
-	};
-}
-
 namespace EPlayerType
 {
 	enum EPlayerType
@@ -143,6 +118,7 @@ public:
 	/// Default: Not set/random.
 	const CRmgTemplate * getMapTemplate() const;
 	void setMapTemplate(const CRmgTemplate * value);
+	void setMapTemplate(const std::string & name);
 
 	std::vector<const CRmgTemplate *> getPossibleTemplates() const;
 
@@ -187,7 +163,17 @@ public:
 		h & waterContent;
 		h & monsterStrength;
 		h & players;
-		//TODO add name of template to class, enables selection of a template by a user
+		std::string templateName;
+		if(mapTemplate && h.saving)
+		{
+			templateName = mapTemplate->getId();
+		}
+		//if(version > xxx) do not forget to bump version
+		h & templateName;
+		if(!h.saving)
+		{
+			setMapTemplate(templateName);
+		}
 	}
 };
 
diff --git a/lib/rmg/CRmgTemplate.cpp b/lib/rmg/CRmgTemplate.cpp
index 7e1051a5f..4e54e413a 100644
--- a/lib/rmg/CRmgTemplate.cpp
+++ b/lib/rmg/CRmgTemplate.cpp
@@ -501,9 +501,19 @@ void CRmgTemplate::setId(const std::string & value)
 	id = value;
 }
 
+void CRmgTemplate::setName(const std::string & value)
+{
+	name = value;
+}
+
 const std::string & CRmgTemplate::getName() const
 {
-	return name.empty() ? id : name;
+	return name;
+}
+
+const std::string & CRmgTemplate::getId() const
+{
+	return id;
 }
 
 const CRmgTemplate::CPlayerCountRange & CRmgTemplate::getPlayers() const
@@ -531,6 +541,11 @@ void CRmgTemplate::validate() const
 	//TODO add some validation checks, throw on failure
 }
 
+std::pair<int3, int3> CRmgTemplate::getMapSizes() const
+{
+	return {minSize, maxSize};
+}
+
 void CRmgTemplate::CPlayerCountRange::addRange(int lower, int upper)
 {
 	range.push_back(std::make_pair(lower, upper));
diff --git a/lib/rmg/CRmgTemplate.h b/lib/rmg/CRmgTemplate.h
index 2b8ba45e3..50ce3227f 100644
--- a/lib/rmg/CRmgTemplate.h
+++ b/lib/rmg/CRmgTemplate.h
@@ -14,7 +14,6 @@
 #include "../GameConstants.h"
 #include "../ResourceSet.h"
 #include "../Terrain.h"
-#include "CMapGenOptions.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -32,6 +31,31 @@ namespace ETemplateZoneType
 	};
 }
 
+namespace EWaterContent
+{
+	enum EWaterContent
+	{
+		RANDOM = -1,
+		NONE,
+		NORMAL,
+		ISLANDS
+	};
+}
+
+namespace EMonsterStrength
+{
+	enum EMonsterStrength
+	{
+		RANDOM = -2,
+		ZONE_WEAK = -1,
+		ZONE_NORMAL = 0,
+		ZONE_STRONG = 1,
+		GLOBAL_WEAK = 2,
+		GLOBAL_NORMAL = 3,
+		GLOBAL_STRONG = 4
+	};
+}
+
 class DLL_LINKAGE CTreasureInfo
 {
 public:
@@ -194,10 +218,13 @@ public:
 	const std::set<EWaterContent::EWaterContent> & getWaterContentAllowed() const;
 
 	void setId(const std::string & value);
+	void setName(const std::string & value);
+	const std::string & getId() const;
 	const std::string & getName() const;
 
 	const CPlayerCountRange & getPlayers() const;
 	const CPlayerCountRange & getCpuPlayers() const;
+	std::pair<int3, int3> getMapSizes() const;
 	const Zones & getZones() const;
 	const std::vector<rmg::ZoneConnection> & getConnections() const;
 
diff --git a/lib/rmg/CRmgTemplateStorage.cpp b/lib/rmg/CRmgTemplateStorage.cpp
index ee0564488..cf932ece8 100644
--- a/lib/rmg/CRmgTemplateStorage.cpp
+++ b/lib/rmg/CRmgTemplateStorage.cpp
@@ -31,8 +31,9 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 	{
 		JsonDeserializer handler(nullptr, data);
 		auto fullKey = normalizeIdentifier(scope, "core", name); //actually it's not used
-		templates[fullKey].setId(name);
+		templates[fullKey].setId(fullKey);
 		templates[fullKey].serializeJson(handler);
+		templates[fullKey].setName(name);
 		templates[fullKey].validate();
 	}
 	catch(const std::exception & e)
diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp
index 431b43ee2..572fa4fff 100644
--- a/lib/rmg/CZonePlacer.cpp
+++ b/lib/rmg/CZonePlacer.cpp
@@ -13,6 +13,7 @@
 #include "CZonePlacer.h"
 #include "../mapping/CMap.h"
 #include "../mapping/CMapEditManager.h"
+#include "CMapGenOptions.h"
 #include "RmgMap.h"
 #include "Zone.h"
 #include "Functions.h"
diff --git a/lib/rmg/Functions.h b/lib/rmg/Functions.h
index 9e60506cf..326ad8891 100644
--- a/lib/rmg/Functions.h
+++ b/lib/rmg/Functions.h
@@ -19,6 +19,7 @@ class RmgMap;
 class ObjectManager;
 class ObjectTemplate;
 class CMapGenerator;
+class CRandomGenerator;
 
 class rmgException : public std::exception
 {

From fc7f1dbc5aadb1d87da17945d40d6ec175179737 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Wed, 14 Dec 2022 05:23:21 +0400
Subject: [PATCH 12/35] Fix template selection

---
 client/lobby/RandomMapTab.cpp | 81 +++++------------------------------
 client/lobby/RandomMapTab.h   |  5 +--
 lib/rmg/CMapGenOptions.cpp    |  1 -
 3 files changed, 12 insertions(+), 75 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 77459a1b7..ebd84efc7 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -42,12 +42,17 @@ RandomMapTab::RandomMapTab():
 		auto mapSizeVal = getPossibleMapSizes();
 		mapGenOptions->setWidth(mapSizeVal[btnId]);
 		mapGenOptions->setHeight(mapSizeVal[btnId]);
+		if(mapGenOptions->getMapTemplate())
+			if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}))
+				setTemplate(nullptr);
 		updateMapInfoByHost();
 	});
 	addCallback("toggleTwoLevels", [&](bool on)
 	{
 		mapGenOptions->setHasTwoLevels(on);
-		setTemplate(mapGenOptions->getMapTemplate());
+		if(mapGenOptions->getMapTemplate())
+			if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}))
+				setTemplate(nullptr);
 		updateMapInfoByHost();
 	});
 	
@@ -55,7 +60,6 @@ RandomMapTab::RandomMapTab():
 	{
 		mapGenOptions->setPlayerCount(btnId);
 		setMapGenOptions(mapGenOptions);
-		//validatePlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
 	
@@ -69,7 +73,6 @@ RandomMapTab::RandomMapTab():
 	{
 		mapGenOptions->setCompOnlyPlayerCount(btnId);
 		setMapGenOptions(mapGenOptions);
-		//validateCompOnlyPlayersCnt(btnId);
 		updateMapInfoByHost();
 	});
 	
@@ -97,7 +100,7 @@ RandomMapTab::RandomMapTab():
 	//new callbacks available only from mod
 	addCallback("templateSelection", [&](int)
 	{
-		GH.pushInt(std::make_shared<TemplatesDropBox>(this));
+		GH.pushInt(std::make_shared<TemplatesDropBox>(this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}));
 	});
 	
 	
@@ -212,12 +215,12 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	if(auto w = widget<CToggleGroup>("groupMaxTeams"))
 	{
 		w->setSelected(opts->getTeamCount());
-		deactivateButtonsFrom(*w, playerCountAllowed);
+		deactivateButtonsFrom(*w, playerTeamsAllowed);
 	}
 	if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
 	{
 		w->setSelected(opts->getCompOnlyPlayerCount());
-		deactivateButtonsFrom(*w, playerTeamsAllowed);
+		deactivateButtonsFrom(*w, compCountAllowed);
 	}
 	if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
 	{
@@ -253,28 +256,6 @@ void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
 	updateMapInfoByHost();
 }
 
-void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, int startAllowed, int endAllowed)
-{
-	logGlobal->debug("Blocking all buttons except %d - %d", startAllowed, endAllowed);
-	for(auto toggle : group.buttons)
-	{
-		if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
-		{
-			if(toggle.first == CMapGenOptions::RANDOM_SIZE
-			   || (startAllowed == CMapGenOptions::RANDOM_SIZE && endAllowed == CMapGenOptions::RANDOM_SIZE)
-			   || (toggle.first >= startAllowed
-			   && (endAllowed == CMapGenOptions::RANDOM_SIZE || toggle.first <= endAllowed)))
-			{
-				//button->block(false);
-			}
-			else
-			{
-				button->block(true);
-			}
-		}
-	}
-}
-
 void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed)
 {
 	logGlobal->debug("Blocking buttons");
@@ -296,47 +277,6 @@ void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<in
 	}
 }
 
-void RandomMapTab::validatePlayersCnt(int playersCnt)
-{
-	if(playersCnt == CMapGenOptions::RANDOM_SIZE)
-	{
-		return;
-	}
-
-	/*if(mapGenOptions->getTeamCount() >= playersCnt)
-	{
-		mapGenOptions->setTeamCount(playersCnt - 1);
-		if(auto w = widget<CToggleGroup>("groupMaxTeams"))
-			w->setSelected(mapGenOptions->getTeamCount());
-	}
-	// total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3)
-	if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I)
-	{
-		mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
-		if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
-			w->setSelected(mapGenOptions->getCompOnlyPlayerCount());
-	}*/
-
-	validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
-}
-
-void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
-{
-	if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
-	{
-		return;
-	}
-
-	/*if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
-	{
-		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
-		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
-		updateMapInfoByHost();
-		if(auto w = widget<CToggleGroup>("groupCompOnlyTeams"))
-			w->setSelected(compOnlyTeamCount);
-	}*/
-}
-
 std::vector<int> RandomMapTab::getPossibleMapSizes()
 {
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
@@ -397,11 +337,12 @@ void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
 }
 
 
-TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab):
+TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab, int3 size):
 	CIntObject(LCLICK | HOVER),
 	randomMapTab(randomMapTab)
 {
 	curItems = VLC->tplh->getTemplates();
+	vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);});
 	curItems.insert(curItems.begin(), nullptr); //default template
 	
 	OBJ_CONSTRUCTION;
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index ba2723f5a..d30cc16cd 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -39,10 +39,7 @@ public:
 	CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
 
 private:
-	void deactivateButtonsFrom(CToggleGroup & group, int startAllower, int endAllowed);
 	void deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed);
-	void validatePlayersCnt(int playersCnt);
-	void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
 	std::vector<int> getPossibleMapSizes();
 
 	std::shared_ptr<CMapGenOptions> mapGenOptions;
@@ -69,7 +66,7 @@ class TemplatesDropBox : public CIntObject
 	};
 	
 public:
-	TemplatesDropBox(RandomMapTab * randomMapTab);
+	TemplatesDropBox(RandomMapTab * randomMapTab, int3 size);
 	
 	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override;
diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp
index bac65c5af..c3ed14233 100644
--- a/lib/rmg/CMapGenOptions.cpp
+++ b/lib/rmg/CMapGenOptions.cpp
@@ -413,7 +413,6 @@ PlayerColor CMapGenOptions::getNextPlayerColor() const
 
 bool CMapGenOptions::checkOptions() const
 {
-	assert(countHumanPlayers() > 0);
 	if(mapTemplate)
 	{
 		return true;

From 4c3288dd618773e473bfae2ca8457bc29c7f2d6c Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Wed, 14 Dec 2022 05:45:50 +0400
Subject: [PATCH 13/35] Fix teams

---
 client/lobby/RandomMapTab.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index ebd84efc7..3667341cd 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -188,8 +188,11 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 		});
 		vstd::erase_if(playerTeamsAllowed,
 		[opts](int el){
-			return PlayerColor::PLAYER_LIMIT_I - opts->getPlayerCount() < el + 1;
+			return opts->getPlayerCount() <= el;
 		});
+		
+		if(!playerTeamsAllowed.count(opts->getTeamCount()))
+		   opts->setTeamCount(CMapGenOptions::RANDOM_SIZE);
 	}
 	if(mapGenOptions->getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE)
 	{
@@ -199,8 +202,11 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 		});
 		vstd::erase_if(compTeamsAllowed,
 		[opts](int el){
-			return PlayerColor::PLAYER_LIMIT_I - opts->getCompOnlyPlayerCount() < el + 1;
+			return opts->getCompOnlyPlayerCount() <= el;
 		});
+		
+		if(!compTeamsAllowed.count(opts->getCompOnlyTeamCount()))
+			opts->setCompOnlyTeamCount(CMapGenOptions::RANDOM_SIZE);
 	}
 	
 	if(auto w = widget<CToggleGroup>("groupMapSize"))

From 2371e3e9a221cf95fccaf3b6d0a411978c0c4428 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 00:46:36 +0400
Subject: [PATCH 14/35] Interface builder refactored

---
 client/gui/InterfaceObjectConfigurable.cpp | 302 +++++++++++++--------
 client/gui/InterfaceObjectConfigurable.h   |  32 ++-
 client/lobby/RandomMapTab.cpp              |  43 ++-
 client/lobby/RandomMapTab.h                |  15 +
 4 files changed, 265 insertions(+), 127 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 6c8c64f38..ddce4753e 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -57,7 +57,7 @@ void InterfaceObjectConfigurable::init(const JsonNode &config)
 	}
 }
 
-std::string InterfaceObjectConfigurable::buildText(const JsonNode & config) const
+std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
 {
 	if(config.isNull())
 		return "";
@@ -69,145 +69,207 @@ std::string InterfaceObjectConfigurable::buildText(const JsonNode & config) cons
 	return config.String();
 }
 
-std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config)
+Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
+{
+	Point p;
+	p.x = config["x"].Integer();
+	p.y = config["y"].Integer();
+	return p;
+}
+
+ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const
+{
+	if(!config.isNull())
+	{
+		if(config.String() == "center")
+			return ETextAlignment::CENTER;
+		if(config.String() == "left")
+			return ETextAlignment::TOPLEFT;
+		if(config.String() == "right")
+			return ETextAlignment::BOTTOMRIGHT;
+	}
+	return ETextAlignment::CENTER;
+}
+
+SDL_Color InterfaceObjectConfigurable::readColor(const JsonNode & config) const
+{
+	if(!config.isNull())
+	{
+		if(config.String() == "yellow")
+			return Colors::YELLOW;
+		if(config.String() == "white")
+			return Colors::WHITE;
+		if(config.String() == "gold")
+			return Colors::METALLIC_GOLD;
+		if(config.String() == "green")
+			return Colors::GREEN;
+		if(config.String() == "orange")
+			return Colors::ORANGE;
+		if(config.String() == "bright-yellow")
+			return Colors::BRIGHT_YELLOW;
+	}
+	return Colors::DEFAULT_KEY_COLOR;
+	
+}
+EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
+{
+	if(!config.isNull())
+	{
+		if(config.String() == "big")
+			return EFonts::FONT_BIG;
+		if(config.String() == "medium")
+			return EFonts::FONT_MEDIUM;
+		if(config.String() == "small")
+			return EFonts::FONT_SMALL;
+		if(config.String() == "tiny")
+			return EFonts::FONT_TINY;
+	}
+	return EFonts::FONT_TIMES;
+}
+
+std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(const JsonNode & config) const
+{
+	std::pair<std::string, std::string> result;
+	if(!config.isNull())
+	{
+		if(config.isNumber())
+			return CGI->generaltexth->zelp[config.Integer()];
+		
+		if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
+		{
+			result.first = config["hover"].String();
+			result.second = config["help"].String();
+			return result;
+		}
+		if(config.getType() == JsonNode::JsonType::DATA_STRING)
+		{
+			result.first = result.second = config.String();
+		}
+	}
+	return result;
+}
+
+std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
+{
+	auto image = readText(config["image"]);
+	auto position = readPosition(config["position"]);
+	return std::make_shared<CPicture>(image, position.x, position.y);
+}
+
+std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode & config) const
+{
+	auto font = readFont(config["font"]);
+	auto alignment = readTextAlignment(config["alignment"]);
+	auto color = readColor(config["color"]);
+	auto text = readText(config["text"]);
+	auto position = readPosition(config["position"]);
+	return std::make_shared<CLabel>(position.x, position.y, font, alignment, color, text);
+}
+
+std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(const JsonNode & config) const
+{
+	auto position = readPosition(config["position"]);
+	auto group = std::make_shared<CToggleGroup>(0);
+	group->pos += position;
+	if(!config["items"].isNull())
+	{
+		SObjectConstruction obj__i(group.get());
+		int itemIdx = -1;
+		for(const auto & item : config["items"].Vector())
+		{
+			itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
+			group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
+		}
+	}
+	if(!config["selected"].isNull())
+		group->setSelected(config["selected"].Integer());
+	if(!config["callback"].isNull())
+		group->addCallback(callbacks.at(config["callback"].String()));
+	return group;
+}
+
+std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(const JsonNode & config) const
+{
+	auto position = readPosition(config["position"]);
+	auto image = config["image"].String();
+	auto zelp = readHintText(config["zelp"]);
+	auto button = std::make_shared<CToggleButton>(position, image, zelp);
+	if(!config["selected"].isNull())
+		button->setSelected(config["selected"].Bool());
+	if(!config["imageOrder"].isNull())
+	{
+		auto imgOrder = config["imageOrder"].Vector();
+		assert(imgOrder.size() >= 4);
+		button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
+	}
+	if(!config["callback"].isNull())
+		button->addCallback(callbacks.at(config["callback"].String()));
+	return button;
+}
+
+std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode & config) const
+{
+	auto position = readPosition(config["position"]);
+	auto image = config["image"].String();
+	auto zelp = readHintText(config["zelp"]);
+	auto button = std::make_shared<CButton>(position, image, zelp);
+	if(!config["items"].isNull())
+	{
+		for(const auto & item : config["items"].Vector())
+		{
+			button->addOverlay(buildWidget(item));
+		}
+	}
+	if(!config["callback"].isNull())
+		button->addCallback(std::bind(callbacks.at(config["callback"].String()), 0));
+	return button;
+}
+
+std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
+{
+	auto font = readFont(config["font"]);
+	auto alignment = readTextAlignment(config["alignment"]);
+	auto color = readColor(config["color"]);
+	auto group = std::make_shared<CLabelGroup>(font, alignment, color);
+	if(!config["items"].isNull())
+	{
+		for(const auto & item : config["items"].Vector())
+		{
+			auto position = readPosition(item["position"]);
+			auto text = readText(item["text"]);
+			group->add(position.x, position.y, text);
+		}
+	}
+	return group;
+}
+
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config) const
 {
 	assert(!config.isNull());
 	auto type = config["type"].String();
-	
-	int x = 0, y = 0;
-	if(!config["position"].isNull())
-	{
-		x = config["position"]["x"].Integer();
-		y = config["position"]["y"].Integer();
-	}
-	
-	std::string image;
-	std::string text = buildText(config["text"]);
-	auto alignment = EAlignment::CENTER;
-	auto color = Colors::DEFAULT_KEY_COLOR;
-	auto font = EFonts::FONT_TIMES;
-	
-	if(!config["image"].isNull())
-		image = config["image"].String();
-	if(!config["alignment"].isNull())
-	{
-		if(config["alignment"].String() == "left")
-			alignment = EAlignment::TOPLEFT;
-		if(config["alignment"].String() == "center")
-			alignment = EAlignment::CENTER;
-		if(config["alignment"].String() == "right")
-			alignment = EAlignment::BOTTOMRIGHT;
-	}
-	if(!config["color"].isNull())
-	{
-		if(config["color"].String() == "yellow")
-			color = Colors::YELLOW;
-		if(config["color"].String() == "white")
-			color = Colors::WHITE;
-		if(config["color"].String() == "gold")
-			color = Colors::METALLIC_GOLD;
-		if(config["color"].String() == "green")
-			color = Colors::GREEN;
-		if(config["color"].String() == "orange")
-			color = Colors::ORANGE;
-		if(config["color"].String() == "bright-yellow")
-			color = Colors::BRIGHT_YELLOW;
-	}
-	if(!config["font"].isNull())
-	{
-		if(config["font"].String() == "big")
-			font = EFonts::FONT_BIG;
-		if(config["font"].String() == "medium")
-			font = EFonts::FONT_MEDIUM;
-		if(config["font"].String() == "small")
-			font = EFonts::FONT_SMALL;
-		if(config["font"].String() == "tiny")
-			font = EFonts::FONT_TINY;
-	}
-	
-	
 	if(type == "picture")
 	{
-		return std::make_shared<CPicture>(image, x, y);
+		return buildPicture(config);
 	}
 	if(type == "label")
 	{
-		return std::make_shared<CLabel>(x, y, font, alignment, color, text);
+		return buildLabel(config);
 	}
 	if(type == "toggleGroup")
 	{
-		auto group = std::make_shared<CToggleGroup>(0);
-		group->pos.x += x;
-		group->pos.y += y;
-		if(!config["items"].isNull())
-		{
-			SObjectConstruction obj__i(group.get());
-			int itemIdx = -1;
-			for(const auto & item : config["items"].Vector())
-			{
-				itemIdx = item["index"].isNull() ? itemIdx + 1 : item["index"].Integer();
-				group->addToggle(itemIdx, std::dynamic_pointer_cast<CToggleBase>(buildWidget(item)));
-			}
-		}
-		if(!config["selected"].isNull())
-			group->setSelected(config["selected"].Integer());
-		if(!config["callback"].isNull())
-			group->addCallback(callbacks[config["callback"].String()]);
-		return group;
+		return buildToggleGroup(config);
 	}
 	if(type == "toggleButton")
 	{
-		std::pair<std::string, std::string> zelp;
-		if(!config["zelp"].isNull())
-			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
-		auto button = std::make_shared<CToggleButton>(Point(x, y), image, zelp);
-		if(!config["selected"].isNull())
-			button->setSelected(config["selected"].Bool());
-		if(!config["imageOrder"].isNull())
-		{
-			auto imgOrder = config["imageOrder"].Vector();
-			assert(imgOrder.size() >= 4);
-			button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
-		}
-		if(!config["callback"].isNull())
-			button->addCallback(callbacks[config["callback"].String()]);
-		return button;
+		return buildToggleButton(config);
 	}
 	if(type == "button")
 	{
-		std::pair<std::string, std::string> zelp;
-		if(!config["zelp"].isNull())
-			zelp = CGI->generaltexth->zelp[config["zelp"].Integer()];
-		auto button = std::make_shared<CButton>(Point(x, y), image, zelp);
-		if(!config["items"].isNull())
-		{
-			for(const auto & item : config["items"].Vector())
-			{
-				button->addOverlay(buildWidget(item));
-			}
-		}
-		if(!config["callback"].isNull())
-			button->addCallback(std::bind(callbacks[config["callback"].String()], 0));
-		return button;
+		return buildButton(config);
 	}
 	if(type == "labelGroup")
 	{
-		auto group = std::make_shared<CLabelGroup>(font, alignment, color);
-		if(!config["items"].isNull())
-		{
-			for(const auto & item : config["items"].Vector())
-			{
-				if(!item["position"].isNull())
-				{
-					x = item["position"]["x"].Integer();
-					y = item["position"]["y"].Integer();
-				}
-				if(!item["text"].isNull())
-					text = buildText(item["text"]);
-				group->add(x, y, text);
-			}
-		}
-		return group;
+		return buildLabelGroup(config);
 	}
 	return std::shared_ptr<CIntObject>(nullptr);
 }
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
index 48501d03b..51f23d7be 100644
--- a/client/gui/InterfaceObjectConfigurable.h
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -14,6 +14,13 @@
 
 #include "../../lib/JsonNode.h"
 
+class CPicture;
+class CLabel;
+class CToggleGroup;
+class CToggleButton;
+class CButton;
+class CLabelGroup;
+
 class InterfaceObjectConfigurable: public CIntObject
 {
 public:
@@ -35,12 +42,29 @@ protected:
 		return std::dynamic_pointer_cast<T>(iter->second);
 	}
 	
+private: //field deserializers
+	//basic serializers
+	Point readPosition(const JsonNode &) const;
+	ETextAlignment readTextAlignment(const JsonNode &) const;
+	SDL_Color readColor(const JsonNode &) const;
+	EFonts readFont(const JsonNode &) const;
+	std::string readText(const JsonNode &) const;
+	std::pair<std::string, std::string> readHintText(const JsonNode &) const;
+	
+	//basic widgets
+	std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
+	std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;
+	std::shared_ptr<CToggleGroup> buildToggleGroup(const JsonNode &) const;
+	std::shared_ptr<CToggleButton> buildToggleButton(const JsonNode &) const;
+	std::shared_ptr<CButton> buildButton(const JsonNode &) const;
+	std::shared_ptr<CLabelGroup> buildLabelGroup(const JsonNode &) const;
+	
+	
+	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config) const;
+	
+	
 private:
 	
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
 	std::map<std::string, std::function<void(int)>> callbacks;
-	
-	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config);
-	
-	std::string buildText(const JsonNode & param) const;
 };
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 3667341cd..dca4cc604 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -100,9 +100,23 @@ RandomMapTab::RandomMapTab():
 	//new callbacks available only from mod
 	addCallback("templateSelection", [&](int)
 	{
-		GH.pushInt(std::make_shared<TemplatesDropBox>(this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}));
+		GH.pushIntT<TemplatesDropBox>(this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()});
 	});
 	
+	addCallback("teamAlignments", [&](int)
+	{
+		GH.pushIntT<TeamAlignmentsWidget>(this);
+	});
+	
+	for(auto road : VLC->terrainTypeHandler->roads())
+	{
+		std::string cbRoadType = "selectRoad_" + road.fileName;
+		addCallback(cbRoadType, [&](bool on)
+		{
+			//TODO: support road types
+		});
+	}
+	
 	
 	init(config);
 	
@@ -293,7 +307,7 @@ TemplatesDropBox::ListItem::ListItem(TemplatesDropBox * _dropBox, Point position
 	dropBox(_dropBox)
 {
 	OBJ_CONSTRUCTION;
-	labelName = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE);
+	labelName = std::make_shared<CLabel>(0, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
 	labelName->setAutoRedraw(false);
 	
 	hoverImage = std::make_shared<CPicture>("List10Sl", 0, 0);
@@ -352,6 +366,9 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab, int3 size):
 	curItems.insert(curItems.begin(), nullptr); //default template
 	
 	OBJ_CONSTRUCTION;
+	pos = randomMapTab->pos.topLeft();
+	pos.w = randomMapTab->pos.w;
+	pos.h = randomMapTab->pos.h;
 	background = std::make_shared<CPicture>("List10Bk", 158, 76);
 	
 	int positionsToShow = 10;
@@ -362,7 +379,6 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab, int3 size):
 	slider = std::make_shared<CSlider>(Point(212 + 158, 76), 252, std::bind(&TemplatesDropBox::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, false, CSlider::BLUE);
 	
 	updateListItems();
-	pos = background->pos;
 }
 
 void TemplatesDropBox::sliderMove(int slidPos)
@@ -410,3 +426,24 @@ void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
 	assert(GH.topInt().get() == this);
 	GH.popInt(GH.topInt());
 }
+
+TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab * randomMapTab):
+	CIntObject(),
+	randomMapTab(randomMapTab)
+{
+	OBJ_CONSTRUCTION;
+	
+	pos.w = 300;
+	pos.h = 300;
+	background = std::make_shared<CFilledTexture>("Bl3DCvex", pos);
+	center(pos);
+	
+	buttonOk = std::make_shared<CButton>(Point(43, 240), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], [](){});
+	buttonCancel = std::make_shared<CButton>(Point(193, 240), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [&]()
+	{
+		assert(GH.topInt().get() == this);
+		GH.popInt(GH.topInt());
+	}, SDLK_ESCAPE);
+	
+	
+}
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index d30cc16cd..df6530142 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -85,3 +85,18 @@ private:
 	std::vector<const CRmgTemplate *> curItems;
 	
 };
+
+class TeamAlignmentsWidget: public CIntObject
+{
+public:
+	TeamAlignmentsWidget(RandomMapTab * randomMapTab);
+	
+private:
+	
+	RandomMapTab * randomMapTab;
+	
+	std::shared_ptr<CFilledTexture> background;
+	std::shared_ptr<CLabelGroup> labels;
+	std::shared_ptr<CButton> buttonOk, buttonCancel;
+	std::vector<std::shared_ptr<CToggleGroup>> teams;
+};

From 1d4209d97ec09d4958a1588ed079f7acb076b60f Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 00:57:10 +0400
Subject: [PATCH 15/35] Continue refactoring

---
 client/gui/InterfaceObjectConfigurable.cpp |  9 +++++++++
 client/gui/InterfaceObjectConfigurable.h   |  3 ++-
 client/lobby/RandomMapTab.cpp              | 22 +++++++++++-----------
 client/lobby/RandomMapTab.h                | 12 ++++++------
 4 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index ddce4753e..130cfec71 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -271,5 +271,14 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonN
 	{
 		return buildLabelGroup(config);
 	}
+	if(type == "custom")
+	{
+		return buildCustomWidget(config);
+	}
 	return std::shared_ptr<CIntObject>(nullptr);
 }
+
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildCustomWidget(const JsonNode & config) const
+{
+	return nullptr;
+}
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
index 51f23d7be..cae184738 100644
--- a/client/gui/InterfaceObjectConfigurable.h
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -42,6 +42,8 @@ protected:
 		return std::dynamic_pointer_cast<T>(iter->second);
 	}
 	
+	virtual std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config) const;
+	
 private: //field deserializers
 	//basic serializers
 	Point readPosition(const JsonNode &) const;
@@ -62,7 +64,6 @@ private: //field deserializers
 	
 	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config) const;
 	
-	
 private:
 	
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index dca4cc604..37f6a8829 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -100,12 +100,12 @@ RandomMapTab::RandomMapTab():
 	//new callbacks available only from mod
 	addCallback("templateSelection", [&](int)
 	{
-		GH.pushIntT<TemplatesDropBox>(this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()});
+		GH.pushIntT<TemplatesDropBox>(*this, int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()});
 	});
 	
 	addCallback("teamAlignments", [&](int)
 	{
-		GH.pushIntT<TeamAlignmentsWidget>(this);
+		GH.pushIntT<TeamAlignmentsWidget>(*this);
 	});
 	
 	for(auto road : VLC->terrainTypeHandler->roads())
@@ -302,7 +302,7 @@ std::vector<int> RandomMapTab::getPossibleMapSizes()
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
 
-TemplatesDropBox::ListItem::ListItem(TemplatesDropBox * _dropBox, Point position)
+TemplatesDropBox::ListItem::ListItem(TemplatesDropBox & _dropBox, Point position)
 	: CIntObject(LCLICK | HOVER, position),
 	dropBox(_dropBox)
 {
@@ -352,12 +352,12 @@ void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
 {
 	if(down && hovered)
 	{
-		dropBox->setTemplate(item);
+		dropBox.setTemplate(item);
 	}
 }
 
 
-TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab, int3 size):
+TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 	CIntObject(LCLICK | HOVER),
 	randomMapTab(randomMapTab)
 {
@@ -366,15 +366,15 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab * randomMapTab, int3 size):
 	curItems.insert(curItems.begin(), nullptr); //default template
 	
 	OBJ_CONSTRUCTION;
-	pos = randomMapTab->pos.topLeft();
-	pos.w = randomMapTab->pos.w;
-	pos.h = randomMapTab->pos.h;
+	pos = randomMapTab.pos.topLeft();
+	pos.w = randomMapTab.pos.w;
+	pos.h = randomMapTab.pos.h;
 	background = std::make_shared<CPicture>("List10Bk", 158, 76);
 	
 	int positionsToShow = 10;
 	
 	for(int i = 0; i < positionsToShow; i++)
-		listItems.push_back(std::make_shared<ListItem>(this, Point(158, 76 + i * 25)));
+		listItems.push_back(std::make_shared<ListItem>(*this, Point(158, 76 + i * 25)));
 	
 	slider = std::make_shared<CSlider>(Point(212 + 158, 76), 252, std::bind(&TemplatesDropBox::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, false, CSlider::BLUE);
 	
@@ -422,12 +422,12 @@ void TemplatesDropBox::updateListItems()
 
 void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
 {
-	randomMapTab->setTemplate(tmpl);
+	randomMapTab.setTemplate(tmpl);
 	assert(GH.topInt().get() == this);
 	GH.popInt(GH.topInt());
 }
 
-TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab * randomMapTab):
+TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 	CIntObject(),
 	randomMapTab(randomMapTab)
 {
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index df6530142..aee146e1f 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -55,10 +55,10 @@ class TemplatesDropBox : public CIntObject
 	{
 		std::shared_ptr<CLabel> labelName;
 		std::shared_ptr<CPicture> hoverImage;
-		TemplatesDropBox * dropBox;
+		TemplatesDropBox & dropBox;
 		const CRmgTemplate * item = nullptr;
 		
-		ListItem(TemplatesDropBox *, Point position);
+		ListItem(TemplatesDropBox &, Point position);
 		void updateItem(int index, const CRmgTemplate * item = nullptr);
 		
 		void hover(bool on) override;
@@ -66,7 +66,7 @@ class TemplatesDropBox : public CIntObject
 	};
 	
 public:
-	TemplatesDropBox(RandomMapTab * randomMapTab, int3 size);
+	TemplatesDropBox(RandomMapTab & randomMapTab, int3 size);
 	
 	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override;
@@ -77,7 +77,7 @@ private:
 	void sliderMove(int slidPos);
 	void updateListItems();
 	
-	RandomMapTab * randomMapTab;
+	RandomMapTab & randomMapTab;
 	std::shared_ptr<CPicture> background;
 	std::vector<std::shared_ptr<ListItem>> listItems;
 	std::shared_ptr<CSlider> slider;
@@ -89,11 +89,11 @@ private:
 class TeamAlignmentsWidget: public CIntObject
 {
 public:
-	TeamAlignmentsWidget(RandomMapTab * randomMapTab);
+	TeamAlignmentsWidget(RandomMapTab & randomMapTab);
 	
 private:
 	
-	RandomMapTab * randomMapTab;
+	RandomMapTab & randomMapTab;
 	
 	std::shared_ptr<CFilledTexture> background;
 	std::shared_ptr<CLabelGroup> labels;

From d3c3feb0375dc3475cc548dcea192db98bb3e275 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 02:15:53 +0400
Subject: [PATCH 16/35] Configurable template selection

---
 client/gui/InterfaceObjectConfigurable.cpp |  39 ++++++--
 client/gui/InterfaceObjectConfigurable.h   |  13 ++-
 client/lobby/RandomMapTab.cpp              | 104 +++++++++++++--------
 client/lobby/RandomMapTab.h                |  13 ++-
 client/widgets/Buttons.cpp                 |   9 +-
 client/widgets/Buttons.h                   |   5 +-
 6 files changed, 119 insertions(+), 64 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 130cfec71..9714b9663 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -26,14 +26,14 @@
 #include "../../lib/CGeneralTextHandler.h"
 
 
-InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config):
-	CIntObject()
+InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
+	CIntObject(used, offset)
 {
 	init(config);
 }
 
-InterfaceObjectConfigurable::InterfaceObjectConfigurable():
-	CIntObject()
+InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset):
+	CIntObject(used, offset)
 {
 }
 
@@ -55,6 +55,12 @@ void InterfaceObjectConfigurable::init(const JsonNode &config)
 						: item["name"].String();
 		widgets[name] = buildWidget(item);
 	}
+	variables = config["variables"];
+}
+
+const JsonNode & InterfaceObjectConfigurable::variable(const std::string & name) const
+{
+	return variables[name];
 }
 
 std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
@@ -153,7 +159,10 @@ std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNo
 {
 	auto image = readText(config["image"]);
 	auto position = readPosition(config["position"]);
-	return std::make_shared<CPicture>(image, position.x, position.y);
+	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
+	if(!config["visible"].isNull())
+		pic->visible = config["visible"].Bool();
+	return pic;
 }
 
 std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode & config) const
@@ -243,6 +252,18 @@ std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const
 	return group;
 }
 
+std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode & config) const
+{
+	auto position = readPosition(config["position"]);
+	int length = config["size"].Integer();
+	auto style = config["style"].String() == "brown" ? CSlider::BROWN : CSlider::BLUE;
+	auto itemsVisible = config["itemsVisible"].Integer();
+	auto itemsTotal = config["itemsTotal"].Integer();
+	auto value = config["selected"].Integer();
+	bool horizontal = config["orientation"].String() == "horizontal";
+	return std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
+}
+
 std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config) const
 {
 	assert(!config.isNull());
@@ -271,14 +292,18 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonN
 	{
 		return buildLabelGroup(config);
 	}
+	if(type == "slider")
+	{
+		return buildSlider(config);
+	}
 	if(type == "custom")
 	{
-		return buildCustomWidget(config);
+		return const_cast<InterfaceObjectConfigurable*>(this)->buildCustomWidget(config);
 	}
 	return std::shared_ptr<CIntObject>(nullptr);
 }
 
-std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildCustomWidget(const JsonNode & config) const
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildCustomWidget(const JsonNode & config)
 {
 	return nullptr;
 }
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
index cae184738..980f8b817 100644
--- a/client/gui/InterfaceObjectConfigurable.h
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -20,12 +20,13 @@ class CToggleGroup;
 class CToggleButton;
 class CButton;
 class CLabelGroup;
+class CSlider;
 
 class InterfaceObjectConfigurable: public CIntObject
 {
 public:
-	InterfaceObjectConfigurable();
-	InterfaceObjectConfigurable(const JsonNode & config);
+	InterfaceObjectConfigurable(int used=0, Point offset=Point());
+	InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
 
 protected:
 	//must be called after adding callbacks
@@ -42,9 +43,8 @@ protected:
 		return std::dynamic_pointer_cast<T>(iter->second);
 	}
 	
-	virtual std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config) const;
+	const JsonNode & variable(const std::string & name) const;
 	
-private: //field deserializers
 	//basic serializers
 	Point readPosition(const JsonNode &) const;
 	ETextAlignment readTextAlignment(const JsonNode &) const;
@@ -60,12 +60,15 @@ private: //field deserializers
 	std::shared_ptr<CToggleButton> buildToggleButton(const JsonNode &) const;
 	std::shared_ptr<CButton> buildButton(const JsonNode &) const;
 	std::shared_ptr<CLabelGroup> buildLabelGroup(const JsonNode &) const;
+	std::shared_ptr<CSlider> buildSlider(const JsonNode &) const;
 	
-	
+	//composite widgets
+	virtual std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config);
 	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config) const;
 	
 private:
 	
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
 	std::map<std::string, std::function<void(int)>> callbacks;
+	JsonNode variables;
 };
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 37f6a8829..a17c66c8d 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -302,48 +302,56 @@ std::vector<int> RandomMapTab::getPossibleMapSizes()
 	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE, CMapHeader::MAP_SIZE_HUGE, CMapHeader::MAP_SIZE_XHUGE, CMapHeader::MAP_SIZE_GIANT};
 }
 
-TemplatesDropBox::ListItem::ListItem(TemplatesDropBox & _dropBox, Point position)
-	: CIntObject(LCLICK | HOVER, position),
+TemplatesDropBox::ListItem::ListItem(const JsonNode & config, TemplatesDropBox & _dropBox, Point position)
+	: InterfaceObjectConfigurable(LCLICK | HOVER, position),
 	dropBox(_dropBox)
 {
 	OBJ_CONSTRUCTION;
-	labelName = std::make_shared<CLabel>(0, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
-	labelName->setAutoRedraw(false);
 	
-	hoverImage = std::make_shared<CPicture>("List10Sl", 0, 0);
-	hoverImage->visible = false;
+	init(config);
 	
-	pos.w = hoverImage->pos.w;
-	pos.h = hoverImage->pos.h;
+	if(auto w = widget<CPicture>("hoverImage"))
+	{
+		pos.w = w->pos.w;
+		pos.h = w->pos.h;
+	}
 	type |= REDRAW_PARENT;
 }
 
 void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item)
 {
-	item = _item;
-	if(item)
+	if(auto w = widget<CLabel>("labelName"))
 	{
-		labelName->setText(item->getName());
-	}
-	else
-	{
-		if(idx)
-			labelName->setText("");
+		item = _item;
+		if(item)
+		{
+			w->setText(item->getName());
+		}
 		else
-			labelName->setText("default");
+		{
+			if(idx)
+				w->setText("");
+			else
+				w->setText("default");
+		}
 	}
 }
 
 void TemplatesDropBox::ListItem::hover(bool on)
 {
-	if(labelName->getText().empty())
+	auto h = widget<CPicture>("hoverImage");
+	auto w = widget<CLabel>("labelName");
+	if(h && w)
 	{
-		hovered = false;
-		hoverImage->visible = false;
-	}
-	else
-	{
-		hoverImage->visible = on;
+		if(w->getText().empty())
+		{
+			hovered = false;
+			h->visible = false;
+		}
+		else
+		{
+			h->visible = on;
+		}
 	}
 	redraw();
 }
@@ -358,32 +366,43 @@ void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
 
 
 TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
-	CIntObject(LCLICK | HOVER),
+	InterfaceObjectConfigurable(LCLICK | HOVER),
 	randomMapTab(randomMapTab)
 {
 	curItems = VLC->tplh->getTemplates();
 	vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);});
 	curItems.insert(curItems.begin(), nullptr); //default template
 	
+	const JsonNode config(ResourceID("config/windows/randomMapTemplateWidget.json"));
+	
+	addCallback("sliderMove", std::bind(&TemplatesDropBox::sliderMove, this, std::placeholders::_1));
+	
 	OBJ_CONSTRUCTION;
 	pos = randomMapTab.pos.topLeft();
 	pos.w = randomMapTab.pos.w;
 	pos.h = randomMapTab.pos.h;
-	background = std::make_shared<CPicture>("List10Bk", 158, 76);
 	
-	int positionsToShow = 10;
+	init(config);
 	
-	for(int i = 0; i < positionsToShow; i++)
-		listItems.push_back(std::make_shared<ListItem>(*this, Point(158, 76 + i * 25)));
-	
-	slider = std::make_shared<CSlider>(Point(212 + 158, 76), 252, std::bind(&TemplatesDropBox::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, false, CSlider::BLUE);
+	if(auto w = widget<CSlider>("slider"))
+	{
+		w->setAmount(curItems.size());
+	}
 	
 	updateListItems();
 }
 
+std::shared_ptr<CIntObject> TemplatesDropBox::buildCustomWidget(const JsonNode & config)
+{
+	auto position = readPosition(config["position"]);
+	listItems.push_back(std::make_shared<ListItem>(config, *this, position));
+	return listItems.back();
+}
+
 void TemplatesDropBox::sliderMove(int slidPos)
 {
-	if(!slider)
+	auto w = widget<CSlider>("slider");
+	if(!w)
 		return; // ignore spurious call when slider is being created
 	updateListItems();
 	redraw();
@@ -405,17 +424,20 @@ void TemplatesDropBox::clickLeft(tribool down, bool previousState)
 
 void TemplatesDropBox::updateListItems()
 {
-	int elemIdx = slider->getValue();
-	for(auto item : listItems)
+	if(auto w = widget<CSlider>("slider"))
 	{
-		if(elemIdx < curItems.size())
+		int elemIdx = w->getValue();
+		for(auto item : listItems)
 		{
-			item->updateItem(elemIdx, curItems[elemIdx]);
-			elemIdx++;
-		}
-		else
-		{
-			item->updateItem(elemIdx);
+			if(elemIdx < curItems.size())
+			{
+				item->updateItem(elemIdx, curItems[elemIdx]);
+				elemIdx++;
+			}
+			else
+			{
+				item->updateItem(elemIdx);
+			}
 		}
 	}
 }
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index aee146e1f..effe2d66f 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -49,16 +49,14 @@ private:
 	std::set<int> playerCountAllowed, playerTeamsAllowed, compCountAllowed, compTeamsAllowed;
 };
 
-class TemplatesDropBox : public CIntObject
+class TemplatesDropBox : public InterfaceObjectConfigurable
 {
-	struct ListItem : public CIntObject
+	struct ListItem : public InterfaceObjectConfigurable
 	{
-		std::shared_ptr<CLabel> labelName;
-		std::shared_ptr<CPicture> hoverImage;
 		TemplatesDropBox & dropBox;
 		const CRmgTemplate * item = nullptr;
 		
-		ListItem(TemplatesDropBox &, Point position);
+		ListItem(const JsonNode &, TemplatesDropBox &, Point position);
 		void updateItem(int index, const CRmgTemplate * item = nullptr);
 		
 		void hover(bool on) override;
@@ -72,15 +70,16 @@ public:
 	void clickLeft(tribool down, bool previousState) override;
 	void setTemplate(const CRmgTemplate *);
 	
+protected:
+	std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config) override;
+	
 private:
 	
 	void sliderMove(int slidPos);
 	void updateListItems();
 	
 	RandomMapTab & randomMapTab;
-	std::shared_ptr<CPicture> background;
 	std::vector<std::shared_ptr<ListItem>> listItems;
-	std::shared_ptr<CSlider> slider;
 	
 	std::vector<const CRmgTemplate *> curItems;
 	
diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp
index e180181ae..435a89396 100644
--- a/client/widgets/Buttons.cpp
+++ b/client/widgets/Buttons.cpp
@@ -566,16 +566,21 @@ void CSlider::setScrollStep(int to)
 	scrollStep = to;
 }
 
-int CSlider::getAmount()
+int CSlider::getAmount() const
 {
 	return amount;
 }
 
-int CSlider::getValue()
+int CSlider::getValue() const
 {
 	return value;
 }
 
+int CSlider::getCapacity() const
+{
+	return capacity;
+}
+
 void CSlider::moveLeft()
 {
 	moveTo(value-1);
diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h
index d370b515e..f967fa953 100644
--- a/client/widgets/Buttons.h
+++ b/client/widgets/Buttons.h
@@ -256,8 +256,9 @@ public:
 	void setAmount(int to);
 
 	/// Accessors
-	int getAmount();
-	int getValue();
+	int getAmount() const;
+	int getValue() const;
+	int getCapacity() const;
 
 	void addCallback(std::function<void(int)> callback);
 

From 0c41787ca563c839ff4de5f81ee826e6d7427fff Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 02:17:10 +0400
Subject: [PATCH 17/35] Remove uncompleted code

---
 client/lobby/RandomMapTab.cpp | 24 ++----------------------
 client/lobby/RandomMapTab.h   | 15 ---------------
 2 files changed, 2 insertions(+), 37 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index a17c66c8d..679675e2c 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -105,7 +105,8 @@ RandomMapTab::RandomMapTab():
 	
 	addCallback("teamAlignments", [&](int)
 	{
-		GH.pushIntT<TeamAlignmentsWidget>(*this);
+		//TODO: support team alignments
+		//GH.pushIntT<TeamAlignmentsWidget>(*this);
 	});
 	
 	for(auto road : VLC->terrainTypeHandler->roads())
@@ -448,24 +449,3 @@ void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
 	assert(GH.topInt().get() == this);
 	GH.popInt(GH.topInt());
 }
-
-TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
-	CIntObject(),
-	randomMapTab(randomMapTab)
-{
-	OBJ_CONSTRUCTION;
-	
-	pos.w = 300;
-	pos.h = 300;
-	background = std::make_shared<CFilledTexture>("Bl3DCvex", pos);
-	center(pos);
-	
-	buttonOk = std::make_shared<CButton>(Point(43, 240), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], [](){});
-	buttonCancel = std::make_shared<CButton>(Point(193, 240), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [&]()
-	{
-		assert(GH.topInt().get() == this);
-		GH.popInt(GH.topInt());
-	}, SDLK_ESCAPE);
-	
-	
-}
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index effe2d66f..72eab43e3 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -84,18 +84,3 @@ private:
 	std::vector<const CRmgTemplate *> curItems;
 	
 };
-
-class TeamAlignmentsWidget: public CIntObject
-{
-public:
-	TeamAlignmentsWidget(RandomMapTab & randomMapTab);
-	
-private:
-	
-	RandomMapTab & randomMapTab;
-	
-	std::shared_ptr<CFilledTexture> background;
-	std::shared_ptr<CLabelGroup> labels;
-	std::shared_ptr<CButton> buttonOk, buttonCancel;
-	std::vector<std::shared_ptr<CToggleGroup>> teams;
-};

From 8f089b3302146d0285246a5857064e772caf0d4c Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 02:19:23 +0400
Subject: [PATCH 18/35] Rename folder

---
 client/lobby/RandomMapTab.cpp                 | 4 ++--
 config/{windows => widgets}/randomMapTab.json | 0
 2 files changed, 2 insertions(+), 2 deletions(-)
 rename config/{windows => widgets}/randomMapTab.json (100%)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 679675e2c..18c216f6d 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -36,7 +36,7 @@ RandomMapTab::RandomMapTab():
 	recActions = 0;
 	mapGenOptions = std::make_shared<CMapGenOptions>();
 	
-	const JsonNode config(ResourceID("config/windows/randomMapTab.json"));
+	const JsonNode config(ResourceID("config/widgets/randomMapTab.json"));
 	addCallback("toggleMapSize", [&](int btnId)
 	{
 		auto mapSizeVal = getPossibleMapSizes();
@@ -374,7 +374,7 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 	vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);});
 	curItems.insert(curItems.begin(), nullptr); //default template
 	
-	const JsonNode config(ResourceID("config/windows/randomMapTemplateWidget.json"));
+	const JsonNode config(ResourceID("config/widgets/randomMapTemplateWidget.json"));
 	
 	addCallback("sliderMove", std::bind(&TemplatesDropBox::sliderMove, this, std::placeholders::_1));
 	
diff --git a/config/windows/randomMapTab.json b/config/widgets/randomMapTab.json
similarity index 100%
rename from config/windows/randomMapTab.json
rename to config/widgets/randomMapTab.json

From 1d59dfecef55d45e6901a0af4116989b28de14e7 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 02:48:07 +0400
Subject: [PATCH 19/35] Support translations

---
 client/gui/InterfaceObjectConfigurable.cpp | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 9714b9663..7fa80aa0e 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -72,7 +72,18 @@ std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
 	{
 		return CGI->generaltexth->allTexts[config.Integer()];
 	}
-	return config.String();
+	
+	const std::string delimiter = "/";
+	std::string s = config.String();
+	JsonNode translated = CGI->generaltexth->localizedTexts;
+	for(size_t p = s.find(delimiter); p != std::string::npos; p = s.find(delimiter))
+	{
+		translated = translated[s.substr(0, p)];
+		s.erase(0, p + delimiter.length());
+	}
+	if(s == config.String())
+		return s;
+	return translated[s].String();
 }
 
 Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
@@ -157,7 +168,7 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
 
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 {
-	auto image = readText(config["image"]);
+	auto image = config["image"].String();
 	auto position = readPosition(config["position"]);
 	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
 	if(!config["visible"].isNull())

From 268b87052b476648da9344e95f42ea124ecbbefb Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Fri, 16 Dec 2022 02:49:52 +0400
Subject: [PATCH 20/35] Fix for hint

---
 client/gui/InterfaceObjectConfigurable.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 7fa80aa0e..b4d80edb6 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -154,8 +154,8 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
 		
 		if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
 		{
-			result.first = config["hover"].String();
-			result.second = config["help"].String();
+			result.first = readText(config["hover"]);
+			result.second = readText(config["help"]);
 			return result;
 		}
 		if(config.getType() == JsonNode::JsonType::DATA_STRING)

From 4508487afa234601937880ce8bb43de57a739aad Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 02:28:42 +0400
Subject: [PATCH 21/35] Fix cmake for macOS build

---
 CMakeLists.txt | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d7b251ba0..1f56b7f76 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -61,6 +61,7 @@ option(ENABLE_GITVERSION "Enable Version.cpp with Git commit hash" ON)
 option(ENABLE_DEBUG_CONSOLE "Enable debug console for Windows builds" ON)
 option(ENABLE_MULTI_PROCESS_BUILDS "Enable /MP flag for MSVS solution" ON)
 option(ENABLE_SINGLE_APP_BUILD "Builds client and server as single executable" OFF)
+option(COPY_CONFIG_ON_BUILD "Copies config folder into output directory at building phase" ON)
 
 # Used for Snap packages and also useful for debugging
 if(NOT APPLE_IOS)
@@ -80,8 +81,8 @@ if(ENABLE_ERM AND NOT ENABLE_LUA)
 	set(ENABLE_LUA ON)
 endif()
 
-# We want to deploy assets into build directory for easier debugging without install
-if(NOT APPLE_IOS AND NOT COPY_CONFIG_ON_BUILD)
+# We don't want to deploy assets into build directory for iOS build
+if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
 	set(COPY_CONFIG_ON_BUILD OFF)
 endif()
 

From f27a40dd3494ebe0f63906ef748fb68cce7acdd8 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 03:52:40 +0400
Subject: [PATCH 22/35] Support roads and teams customization in engine

---
 lib/rmg/CMapGenOptions.cpp   | 32 +++++++++++++++++++++++++++++++-
 lib/rmg/CMapGenOptions.h     | 26 ++++++++++++++++++++++----
 lib/rmg/CMapGenerator.cpp    | 24 ++++++++++++++++++------
 lib/rmg/RoadPlacer.cpp       |  7 +++++++
 lib/serializer/CSerializer.h |  2 +-
 5 files changed, 79 insertions(+), 12 deletions(-)

diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp
index c3ed14233..d653029f6 100644
--- a/lib/rmg/CMapGenOptions.cpp
+++ b/lib/rmg/CMapGenOptions.cpp
@@ -230,6 +230,26 @@ void CMapGenOptions::setMapTemplate(const std::string & name)
 		setMapTemplate(VLC->tplh->getTemplate(name));
 }
 
+void CMapGenOptions::setRoadEnabled(const std::string & roadName, bool enable)
+{
+	if(enable)
+		disabledRoads.erase(roadName);
+	else
+		disabledRoads.insert(roadName);
+}
+
+bool CMapGenOptions::isRoadEnabled(const std::string & roadName) const
+{
+	return !disabledRoads.count(roadName);
+}
+
+void CMapGenOptions::setPlayerTeam(PlayerColor color, TeamID team)
+{
+	auto it = players.find(color);
+	if(it == players.end()) assert(0);
+	it->second.setTeam(team);
+}
+
 void CMapGenOptions::finalize(CRandomGenerator & rand)
 {
 	logGlobal->info("RMG map: %dx%d, %s underground", getWidth(), getHeight(), getHasTwoLevels() ? "WITH" : "NO");
@@ -473,7 +493,7 @@ const CRmgTemplate * CMapGenOptions::getPossibleTemplate(CRandomGenerator & rand
 	return *RandomGeneratorUtil::nextItem(templates, rand);
 }
 
-CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI)
+CMapGenOptions::CPlayerSettings::CPlayerSettings() : color(0), startingTown(RANDOM_TOWN), playerType(EPlayerType::AI), team(TeamID::NO_TEAM)
 {
 
 }
@@ -515,4 +535,14 @@ void CMapGenOptions::CPlayerSettings::setPlayerType(EPlayerType::EPlayerType val
 	playerType = value;
 }
 
+TeamID CMapGenOptions::CPlayerSettings::getTeam() const
+{
+	return team;
+}
+
+void CMapGenOptions::CPlayerSettings::setTeam(TeamID value)
+{
+	team = value;
+}
+
 VCMI_LIB_NAMESPACE_END
diff --git a/lib/rmg/CMapGenOptions.h b/lib/rmg/CMapGenOptions.h
index d8277518c..39d947d05 100644
--- a/lib/rmg/CMapGenOptions.h
+++ b/lib/rmg/CMapGenOptions.h
@@ -51,6 +51,10 @@ public:
 		/// The default value is EPlayerType::AI.
 		EPlayerType::EPlayerType getPlayerType() const;
 		void setPlayerType(EPlayerType::EPlayerType value);
+		
+		/// Team id for this player. TeamID::NO_TEAM by default - team will be randomly assigned
+		TeamID getTeam() const;
+		void setTeam(TeamID value);
 
 		/// Constant for a random town selection.
 		static const si32 RANDOM_TOWN = -1;
@@ -59,6 +63,7 @@ public:
 		PlayerColor color;
 		si32 startingTown;
 		EPlayerType::EPlayerType playerType;
+		TeamID team;
 
 	public:
 		template <typename Handler>
@@ -67,6 +72,8 @@ public:
 			h & color;
 			h & startingTown;
 			h & playerType;
+			if(version >= 806)
+				h & team;
 		}
 	};
 
@@ -105,6 +112,9 @@ public:
 
 	EMonsterStrength::EMonsterStrength getMonsterStrength() const;
 	void setMonsterStrength(EMonsterStrength::EMonsterStrength value);
+	
+	bool isRoadEnabled(const std::string & roadName) const;
+	void setRoadEnabled(const std::string & roadName, bool enable);
 
 	/// The first player colors belong to standard players and the last player colors belong to comp only players.
 	/// All standard players are by default of type EPlayerType::AI.
@@ -113,6 +123,8 @@ public:
 	/// Sets a player type for a standard player. A standard player is the opposite of a computer only player. The
 	/// values which can be chosen for the player type are EPlayerType::AI or EPlayerType::HUMAN.
 	void setPlayerTypeForStandardPlayer(PlayerColor color, EPlayerType::EPlayerType playerType);
+	
+	void setPlayerTeam(PlayerColor color, TeamID team = TeamID::NO_TEAM);
 
 	/// The random map template to generate the map with or empty/not set if the template should be chosen randomly.
 	/// Default: Not set/random.
@@ -147,6 +159,8 @@ private:
 	EWaterContent::EWaterContent waterContent;
 	EMonsterStrength::EMonsterStrength monsterStrength;
 	std::map<PlayerColor, CPlayerSettings> players;
+	std::set<std::string> disabledRoads;
+	
 	const CRmgTemplate * mapTemplate;
 
 public:
@@ -168,11 +182,15 @@ public:
 		{
 			templateName = mapTemplate->getId();
 		}
-		//if(version > xxx) do not forget to bump version
-		h & templateName;
-		if(!h.saving)
+		if(version >= 806)
 		{
-			setMapTemplate(templateName);
+			h & templateName;
+			if(!h.saving)
+			{
+				setMapTemplate(templateName);
+			}
+			
+			h & disabledRoads;
 		}
 	}
 };
diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp
index ffe2a691f..21e8ccc1a 100644
--- a/lib/rmg/CMapGenerator.cpp
+++ b/lib/rmg/CMapGenerator.cpp
@@ -76,6 +76,11 @@ void CMapGenerator::loadConfig()
 	config.pandoraMultiplierSpells = randomMapJson["pandoras"]["valueMultiplierSpells"].Integer();
 	config.pandoraSpellSchool = randomMapJson["pandoras"]["valueSpellSchool"].Integer();
 	config.pandoraSpell60 = randomMapJson["pandoras"]["valueSpell60"].Integer();
+	//override config with game options
+	if(!mapGenOptions.isRoadEnabled(config.secondaryRoadType))
+		config.secondaryRoadType = "";
+	if(!mapGenOptions.isRoadEnabled(config.defaultRoadType))
+		config.defaultRoadType = config.secondaryRoadType;
 }
 
 const CMapGenerator::Config & CMapGenerator::getConfig() const
@@ -238,14 +243,21 @@ void CMapGenerator::addPlayerInfo()
 			player.canHumanPlay = true;
 		}
 
-		if (teamNumbers[j].empty())
+		if(pSettings.getTeam() != TeamID::NO_TEAM)
 		{
-			logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human"));
-			assert (teamNumbers[j].size());
+			player.team = pSettings.getTeam();
+		}
+		else
+		{
+			if (teamNumbers[j].empty())
+			{
+				logGlobal->error("Not enough places in team for %s player", ((j == CPUONLY) ? "CPU" : "CPU or human"));
+				assert (teamNumbers[j].size());
+			}
+			auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
+			player.team = TeamID(*itTeam);
+			teamNumbers[j].erase(itTeam);
 		}
-		auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
-		player.team = TeamID(*itTeam);
-		teamNumbers[j].erase(itTeam);
 		map->map().players[pSettings.getColor().getNum()] = player;
 	}
 
diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp
index a435395af..ff108375b 100644
--- a/lib/rmg/RoadPlacer.cpp
+++ b/lib/rmg/RoadPlacer.cpp
@@ -22,6 +22,9 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 void RoadPlacer::process()
 {
+	if(generator.getConfig().defaultRoadType.empty() && generator.getConfig().secondaryRoadType.empty())
+		return; //do not generate roads at all
+	
 	connectRoads();
 }
 
@@ -68,6 +71,10 @@ bool RoadPlacer::createRoad(const int3 & dst)
 
 void RoadPlacer::drawRoads(bool secondary)
 {
+	if((secondary && generator.getConfig().secondaryRoadType.empty())
+	   || (!secondary && generator.getConfig().defaultRoadType.empty()))
+		return;
+	
 	zone.areaPossible().subtract(roads);
 	zone.freePaths().unite(roads);
 	map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector());
diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h
index 9b0d14e47..bcfc6fed9 100644
--- a/lib/serializer/CSerializer.h
+++ b/lib/serializer/CSerializer.h
@@ -14,7 +14,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-const ui32 SERIALIZATION_VERSION = 805;
+const ui32 SERIALIZATION_VERSION = 806;
 const ui32 MINIMAL_SERIALIZATION_VERSION = 805;
 const std::string SAVEGAME_MAGIC = "VCMISVG";
 

From 42281f51e8361910ec1e422740efa309e897dd8d Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 03:53:26 +0400
Subject: [PATCH 23/35] Revert "Remove uncompleted code"

This reverts commit 0c41787ca563c839ff4de5f81ee826e6d7427fff.
---
 client/lobby/RandomMapTab.cpp | 24 ++++++++++++++++++++++--
 client/lobby/RandomMapTab.h   | 15 +++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 18c216f6d..213628fa9 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -105,8 +105,7 @@ RandomMapTab::RandomMapTab():
 	
 	addCallback("teamAlignments", [&](int)
 	{
-		//TODO: support team alignments
-		//GH.pushIntT<TeamAlignmentsWidget>(*this);
+		GH.pushIntT<TeamAlignmentsWidget>(*this);
 	});
 	
 	for(auto road : VLC->terrainTypeHandler->roads())
@@ -449,3 +448,24 @@ void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
 	assert(GH.topInt().get() == this);
 	GH.popInt(GH.topInt());
 }
+
+TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
+	CIntObject(),
+	randomMapTab(randomMapTab)
+{
+	OBJ_CONSTRUCTION;
+	
+	pos.w = 300;
+	pos.h = 300;
+	background = std::make_shared<CFilledTexture>("Bl3DCvex", pos);
+	center(pos);
+	
+	buttonOk = std::make_shared<CButton>(Point(43, 240), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], [](){});
+	buttonCancel = std::make_shared<CButton>(Point(193, 240), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [&]()
+	{
+		assert(GH.topInt().get() == this);
+		GH.popInt(GH.topInt());
+	}, SDLK_ESCAPE);
+	
+	
+}
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index 72eab43e3..effe2d66f 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -84,3 +84,18 @@ private:
 	std::vector<const CRmgTemplate *> curItems;
 	
 };
+
+class TeamAlignmentsWidget: public CIntObject
+{
+public:
+	TeamAlignmentsWidget(RandomMapTab & randomMapTab);
+	
+private:
+	
+	RandomMapTab & randomMapTab;
+	
+	std::shared_ptr<CFilledTexture> background;
+	std::shared_ptr<CLabelGroup> labels;
+	std::shared_ptr<CButton> buttonOk, buttonCancel;
+	std::vector<std::shared_ptr<CToggleGroup>> teams;
+};

From d93e844609c227db50ba2b9f2273499f3ea05ff9 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 04:54:01 +0400
Subject: [PATCH 24/35] Use roads names instead of their filenames

---
 config/randomMap.json  |  4 ++--
 lib/Terrain.cpp        |  6 ++++--
 lib/Terrain.h          | 11 ++++++++++-
 lib/rmg/RoadPlacer.cpp |  4 ++--
 4 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/config/randomMap.json b/config/randomMap.json
index 926214ea2..fa54f121f 100644
--- a/config/randomMap.json
+++ b/config/randomMap.json
@@ -16,8 +16,8 @@
     "extraResourcesLimit" : 3
   },
   "minGuardStrength" : 2000,
-  "defaultRoadType" : "pc", //pd - dirt, pg - gravel, pc - cobblestone
-  "secondaryRoadType": "pd",
+  "defaultRoadType" : "cobblestoneRoad",
+  "secondaryRoadType": "dirtRoad",
   "treasureValueLimit" : 20000, //generate pandora with gold for treasure above this limit
   "prisons" :
   {
diff --git a/lib/Terrain.cpp b/lib/Terrain.cpp
index fff98de34..7b6635080 100644
--- a/lib/Terrain.cpp
+++ b/lib/Terrain.cpp
@@ -220,6 +220,7 @@ void TerrainTypeHandler::initRivers(const std::vector<std::string> & allConfigs)
 		{
 			RiverType info;
 
+			info.name = river.first;
 			info.fileName = river.second["animation"].String();
 			info.code = river.second["code"].String();
 			info.deltaName = river.second["delta"].String();
@@ -255,6 +256,7 @@ void TerrainTypeHandler::initRoads(const std::vector<std::string> & allConfigs)
 		{
 			RoadType info;
 
+			info.name = road.first;
 			info.fileName = road.second["animation"].String();
 			info.code = road.second["code"].String();
 			info.movementCost = static_cast<ui8>(road.second["moveCost"].Float());
@@ -295,7 +297,7 @@ void TerrainTypeHandler::recreateRiverMaps()
 	{
 		const auto * riverInfo = &riverTypes[i];
 
-		riverInfoByName[riverInfo->fileName] = riverInfo;
+		riverInfoByName[riverInfo->name] = riverInfo;
 		riverInfoByCode[riverInfo->code] = riverInfo;
 		riverInfoById[riverInfo->id] = riverInfo;
 	}
@@ -307,7 +309,7 @@ void TerrainTypeHandler::recreateRoadMaps()
 	{
 		const auto * roadInfo = &roadTypes[i];
 
-		roadInfoByName[roadInfo->fileName] = roadInfo;
+		roadInfoByName[roadInfo->name] = roadInfo;
 		roadInfoByCode[roadInfo->code] = roadInfo;
 		roadInfoById[roadInfo->id] = roadInfo;
 	}
diff --git a/lib/Terrain.h b/lib/Terrain.h
index 6e23a9ca2..5d6fca1e7 100644
--- a/lib/Terrain.h
+++ b/lib/Terrain.h
@@ -89,7 +89,7 @@ public:
 class DLL_LINKAGE RiverType
 {
 public:
-
+	std::string name;
 	std::string fileName;
 	std::string code;
 	std::string deltaName;
@@ -99,6 +99,10 @@ public:
 
 	template <typename Handler> void serialize(Handler& h, const int version)
 	{
+		if(version >= 806)
+		{
+			h & name;
+		}
 		h & fileName;
 		h & code;
 		h & deltaName;
@@ -109,6 +113,7 @@ public:
 class DLL_LINKAGE RoadType
 {
 public:
+	std::string name;
 	std::string fileName;
 	std::string code;
 	RoadId id;
@@ -118,6 +123,10 @@ public:
 
 	template <typename Handler> void serialize(Handler& h, const int version)
 	{
+		if(version >= 806)
+		{
+			h & name;
+		}
 		h & fileName;
 		h & code;
 		h & id;
diff --git a/lib/rmg/RoadPlacer.cpp b/lib/rmg/RoadPlacer.cpp
index ff108375b..b8a037135 100644
--- a/lib/rmg/RoadPlacer.cpp
+++ b/lib/rmg/RoadPlacer.cpp
@@ -78,8 +78,8 @@ void RoadPlacer::drawRoads(bool secondary)
 	zone.areaPossible().subtract(roads);
 	zone.freePaths().unite(roads);
 	map.getEditManager()->getTerrainSelection().setSelection(roads.getTilesVector());
-	std::string roadCode = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
-	RoadId roadType = VLC->terrainTypeHandler->getRoadByCode(roadCode)->id;
+	std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
+	RoadId roadType = VLC->terrainTypeHandler->getRoadByName(roadName)->id;
 	map.getEditManager()->drawRoad(roadType, &generator.rand);
 }
 

From 4f5fab702e628b22a10b199dda3c1ee74c25fc2c Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 04:54:37 +0400
Subject: [PATCH 25/35] Support roads selection in random map tab

---
 client/lobby/RandomMapTab.cpp |  7 ++++---
 config/translate.json         | 13 +++++++++++++
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 213628fa9..755fe5ded 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -110,10 +110,11 @@ RandomMapTab::RandomMapTab():
 	
 	for(auto road : VLC->terrainTypeHandler->roads())
 	{
-		std::string cbRoadType = "selectRoad_" + road.fileName;
-		addCallback(cbRoadType, [&](bool on)
+		std::string cbRoadType = "selectRoad_" + road.name;
+		addCallback(cbRoadType, [&, road](bool on)
 		{
-			//TODO: support road types
+			mapGenOptions->setRoadEnabled(road.name, on);
+			updateMapInfoByHost();
 		});
 	}
 	
diff --git a/config/translate.json b/config/translate.json
index 3352fedd3..5322a50fb 100644
--- a/config/translate.json
+++ b/config/translate.json
@@ -113,5 +113,18 @@
 			"label" : "Hide complete quests",
 			"help" : "Hide all quests that already completed"
 		}
+	},
+	"randomMapTab":
+	{
+		"widgets":
+		{
+			"defaultTemplate": "default",
+			"templateLabel": "Template",
+			"teamAlignmentsButton": "Setup...",
+			"teamAlignmentsLabel": "Team alignments",
+			"dirtRoad": "Dirt",
+			"gravelRoad": "Gravel",
+			"cobblestoneRoad": "Stone"
+		}
 	}
 }

From b1f2c7aed400ed7a4aa6b57360f28f963c9d6fae Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 08:19:16 +0400
Subject: [PATCH 26/35] Team alignments implemented

---
 client/gui/InterfaceObjectConfigurable.cpp | 62 ++++++++++++++++-
 client/gui/InterfaceObjectConfigurable.h   |  8 +++
 client/lobby/RandomMapTab.cpp              | 79 ++++++++++++++++++++--
 client/lobby/RandomMapTab.h                |  5 +-
 client/widgets/Buttons.cpp                 |  5 ++
 client/widgets/Buttons.h                   |  1 +
 client/widgets/Images.h                    |  2 +-
 config/translate.json                      |  5 +-
 lib/rmg/CMapGenOptions.cpp                 |  3 +
 lib/rmg/CMapGenerator.cpp                  |  5 +-
 10 files changed, 161 insertions(+), 14 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index b4d80edb6..31b43c2af 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -55,7 +55,6 @@ void InterfaceObjectConfigurable::init(const JsonNode &config)
 						: item["name"].String();
 		widgets[name] = buildWidget(item);
 	}
-	variables = config["variables"];
 }
 
 const JsonNode & InterfaceObjectConfigurable::variable(const std::string & name) const
@@ -94,6 +93,16 @@ Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
 	return p;
 }
 
+Rect InterfaceObjectConfigurable::readRect(const JsonNode & config) const
+{
+	Rect p;
+	p.x = config["x"].Integer();
+	p.y = config["y"].Integer();
+	p.w = config["w"].Integer();
+	p.h = config["h"].Integer();
+	return p;
+}
+
 ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const
 {
 	if(!config.isNull())
@@ -275,6 +284,45 @@ std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode
 	return std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
 }
 
+std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNode & config) const
+{
+	auto position = readPosition(config["position"]);
+	auto image = config["image"].String();
+	int group = config["group"].isNull() ? 0 : config["group"].Integer();
+	int frame = config["frame"].isNull() ? 0 : config["frame"].Integer();
+	return std::make_shared<CAnimImage>(image, frame, group, position.x, position.y);
+}
+
+std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
+{
+	auto image = config["image"].String();
+	auto rect = readRect(config);
+	return std::make_shared<CFilledTexture>(image, rect);
+}
+
+std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const
+{
+	auto position = readPosition(config["position"]);
+	auto image = config["image"].String();
+	ui8 flags = 0;
+	if(!config["repeat"].Bool())
+		flags |= CShowableAnim::EFlags::PLAY_ONCE;
+	
+	int group = config["group"].isNull() ? 0 : config["group"].Integer();
+	auto anim = std::make_shared<CShowableAnim>(position.x, position.y, image, flags, 4, group);
+	if(!config["alpha"].isNull())
+		anim->setAlpha(config["alpha"].Integer());
+	if(!config["callback"].isNull())
+		anim->callback = std::bind(callbacks.at(config["callback"].String()), 0);
+	if(!config["frames"].isNull())
+	{
+		auto b = config["frames"]["start"].Integer();
+		auto e = config["frames"]["end"].Integer();
+		anim->set(group, b, e);
+	}
+	return anim;
+}
+
 std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config) const
 {
 	assert(!config.isNull());
@@ -283,6 +331,18 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonN
 	{
 		return buildPicture(config);
 	}
+	if(type == "image")
+	{
+		return buildImage(config);
+	}
+	if(type == "texture")
+	{
+		return buildTexture(config);
+	}
+	if(type == "animation")
+	{
+		return buildAnimation(config);
+	}
 	if(type == "label")
 	{
 		return buildLabel(config);
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
index 980f8b817..4bb40e974 100644
--- a/client/gui/InterfaceObjectConfigurable.h
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -21,6 +21,9 @@ class CToggleButton;
 class CButton;
 class CLabelGroup;
 class CSlider;
+class CAnimImage;
+class CShowableAnim;
+class CFilledTexture;
 
 class InterfaceObjectConfigurable: public CIntObject
 {
@@ -47,6 +50,7 @@ protected:
 	
 	//basic serializers
 	Point readPosition(const JsonNode &) const;
+	Rect readRect(const JsonNode &) const;
 	ETextAlignment readTextAlignment(const JsonNode &) const;
 	SDL_Color readColor(const JsonNode &) const;
 	EFonts readFont(const JsonNode &) const;
@@ -61,6 +65,10 @@ protected:
 	std::shared_ptr<CButton> buildButton(const JsonNode &) const;
 	std::shared_ptr<CLabelGroup> buildLabelGroup(const JsonNode &) const;
 	std::shared_ptr<CSlider> buildSlider(const JsonNode &) const;
+	std::shared_ptr<CAnimImage> buildImage(const JsonNode &) const;
+	std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
+	std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
+	
 	
 	//composite widgets
 	virtual std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config);
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 755fe5ded..c931d6f72 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -155,6 +155,7 @@ void RandomMapTab::updateMapInfoByHost()
 
 	mapInfo->mapHeader->howManyTeams = playersToGen;
 
+	std::set<TeamID> occupiedTeams;
 	for(int i = 0; i < playersToGen; ++i)
 	{
 		PlayerInfo player;
@@ -168,11 +169,25 @@ void RandomMapTab::updateMapInfoByHost()
 		{
 			player.canHumanPlay = true;
 		}
-		player.team = TeamID(i);
+		auto team = mapGenOptions->getPlayersSettings().at(PlayerColor(i)).getTeam();
+		player.team = team;
+		occupiedTeams.insert(team);
 		player.hasMainTown = true;
 		player.generateHeroAtMainTown = true;
 		mapInfo->mapHeader->players.push_back(player);
 	}
+	for(auto & player : mapInfo->mapHeader->players)
+	{
+		for(int i = 0; player.team == TeamID::NO_TEAM; ++i)
+		{
+			TeamID team(i);
+			if(!occupiedTeams.count(team))
+			{
+				player.team = team;
+				occupiedTeams.insert(team);
+			}
+		}
+	}
 
 	mapInfoChanged(mapInfo, mapGenOptions);
 }
@@ -456,13 +471,67 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 {
 	OBJ_CONSTRUCTION;
 	
-	pos.w = 300;
-	pos.h = 300;
+	int humanPlayers = randomMapTab.obtainMapGenOptions().getPlayerCount();
+	int cpuPlayers = randomMapTab.obtainMapGenOptions().getCompOnlyPlayerCount();
+	int totalPlayers = humanPlayers == CMapGenOptions::RANDOM_SIZE || cpuPlayers == CMapGenOptions::RANDOM_SIZE
+	? PlayerColor::PLAYER_LIMIT_I : humanPlayers + cpuPlayers;
+	assert(totalPlayers <= PlayerColor::PLAYER_LIMIT_I);
+	auto settings = randomMapTab.obtainMapGenOptions().getPlayersSettings();
+	
+	pos.w = 80 + totalPlayers * 32;
+	pos.h = 80 + totalPlayers * 32;
 	background = std::make_shared<CFilledTexture>("Bl3DCvex", pos);
 	center(pos);
 	
-	buttonOk = std::make_shared<CButton>(Point(43, 240), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], [](){});
-	buttonCancel = std::make_shared<CButton>(Point(193, 240), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [&]()
+	for(int plId = 0; plId < totalPlayers; ++plId)
+	{
+		players.push_back(std::make_shared<CToggleGroup>([&, totalPlayers, plId](int sel)
+		{
+			SObjectConstruction obj__i(players[plId].get());
+			for(int teamId = 0; teamId < totalPlayers; ++teamId)
+			{
+				auto button = std::dynamic_pointer_cast<CToggleButton>(players[plId]->buttons[teamId]);
+				assert(button);
+				if(sel == teamId)
+				{
+					button->addOverlay(std::make_shared<CAnimImage>("ITGFLAGS", plId, 0, 8, 8));
+				}
+				else
+				{
+					button->addOverlay(std::make_shared<CPicture>("TeamPlSl"));
+				}
+			}
+		}));
+		
+		SObjectConstruction obj__i(players.back().get());
+		for(int teamId = 0; teamId < totalPlayers; ++teamId)
+		{
+			Point p(40 + plId * 32, 20 + teamId * 32);
+			placeholders.push_back(std::make_shared<CPicture>("TeamPlSl", p.x, p.y));
+			auto button = std::make_shared<CToggleButton>(p, "TeamPlSl", std::pair<std::string, std::string>{"", ""});
+			button->pos.w = 32;
+			button->pos.h = 32;
+			players.back()->addToggle(teamId, button);
+		}
+		
+		auto team = settings.at(PlayerColor(plId)).getTeam();
+		if(team == TeamID::NO_TEAM)
+			players.back()->setSelected(plId);
+		else
+			players.back()->setSelected(team.getNum());
+	}
+	
+	buttonOk = std::make_shared<CButton>(Point(40, 40 + totalPlayers * 32), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], [&]()
+	{
+		for(int plId = 0; plId < players.size(); ++plId)
+		{
+			randomMapTab.obtainMapGenOptions().setPlayerTeam(PlayerColor(plId), TeamID(players[plId]->getSelected()));
+		}
+		randomMapTab.updateMapInfoByHost();
+		assert(GH.topInt().get() == this);
+		GH.popInt(GH.topInt());
+	});
+	buttonCancel = std::make_shared<CButton>(Point(120, 40 + totalPlayers * 32), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [&]()
 	{
 		assert(GH.topInt().get() == this);
 		GH.popInt(GH.topInt());
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index effe2d66f..6b307169b 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -26,6 +26,7 @@ class CToggleButton;
 class CLabel;
 class CLabelGroup;
 class CSlider;
+class CPicture;
 
 class RandomMapTab : public InterfaceObjectConfigurable
 {
@@ -35,6 +36,7 @@ public:
 	void updateMapInfoByHost();
 	void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
 	void setTemplate(const CRmgTemplate *);
+	CMapGenOptions & obtainMapGenOptions() {return *mapGenOptions;}
 
 	CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
 
@@ -97,5 +99,6 @@ private:
 	std::shared_ptr<CFilledTexture> background;
 	std::shared_ptr<CLabelGroup> labels;
 	std::shared_ptr<CButton> buttonOk, buttonCancel;
-	std::vector<std::shared_ptr<CToggleGroup>> teams;
+	std::vector<std::shared_ptr<CToggleGroup>> players;
+	std::vector<std::shared_ptr<CPicture>> placeholders;
 };
diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp
index 435a89396..592eecdc0 100644
--- a/client/widgets/Buttons.cpp
+++ b/client/widgets/Buttons.cpp
@@ -449,6 +449,11 @@ void CToggleGroup::selectionChanged(int to)
 		parent->redraw();
 }
 
+int CToggleGroup::getSelected() const
+{
+	return selectedID;
+}
+
 CVolumeSlider::CVolumeSlider(const Point & position, const std::string & defName, const int value, const std::pair<std::string, std::string> * const help)
 	: CIntObject(LCLICK | RCLICK | WHEEL),
 	value(value),
diff --git a/client/widgets/Buttons.h b/client/widgets/Buttons.h
index f967fa953..872cc448e 100644
--- a/client/widgets/Buttons.h
+++ b/client/widgets/Buttons.h
@@ -185,6 +185,7 @@ public:
 	/// in some cases, e.g. LoadGame difficulty selection, after refreshing the UI, the ToggleGroup should 
 	/// reset all of it's child buttons to BLOCK state, then make selection again
 	void setSelectedOnly(int id);
+	int getSelected() const;
 };
 
 /// A typical slider for volume with an animated indicator
diff --git a/client/widgets/Images.h b/client/widgets/Images.h
index 0ac142125..84f069f38 100644
--- a/client/widgets/Images.h
+++ b/client/widgets/Images.h
@@ -56,7 +56,7 @@ public:
 };
 
 /// area filled with specific texture
-class CFilledTexture : CIntObject
+class CFilledTexture : public CIntObject
 {
 	SDL_Surface * texture;
 
diff --git a/config/translate.json b/config/translate.json
index 5322a50fb..35d36f18d 100644
--- a/config/translate.json
+++ b/config/translate.json
@@ -121,10 +121,7 @@
 			"defaultTemplate": "default",
 			"templateLabel": "Template",
 			"teamAlignmentsButton": "Setup...",
-			"teamAlignmentsLabel": "Team alignments",
-			"dirtRoad": "Dirt",
-			"gravelRoad": "Gravel",
-			"cobblestoneRoad": "Stone"
+			"teamAlignmentsLabel": "Team alignments"
 		}
 	}
 }
diff --git a/lib/rmg/CMapGenOptions.cpp b/lib/rmg/CMapGenOptions.cpp
index d653029f6..098511bfe 100644
--- a/lib/rmg/CMapGenOptions.cpp
+++ b/lib/rmg/CMapGenOptions.cpp
@@ -136,12 +136,14 @@ void CMapGenOptions::resetPlayersMap()
 {
 
 	std::map<PlayerColor, TFaction> rememberTownTypes;
+	std::map<PlayerColor, TeamID> rememberTeam;
 
 	for (auto p : players)
 	{
 		auto town = p.second.getStartingTown();
 		if (town != RANDOM_SIZE)
 			rememberTownTypes[p.first] = town;
+		rememberTeam[p.first] = p.second.getTeam();
 	}
 
 
@@ -169,6 +171,7 @@ void CMapGenOptions::resetPlayersMap()
 			playerType = EPlayerType::COMP_ONLY;
 		}
 		player.setPlayerType(playerType);
+		player.setTeam(rememberTeam[pc]);
 		players[pc] = player;
 
 		if (vstd::contains(rememberTownTypes, pc))
diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp
index 21e8ccc1a..9a4b1f916 100644
--- a/lib/rmg/CMapGenerator.cpp
+++ b/lib/rmg/CMapGenerator.cpp
@@ -188,6 +188,7 @@ void CMapGenerator::addPlayerInfo()
 
 	enum ETeams {CPHUMAN = 0, CPUONLY = 1, AFTER_LAST = 2};
 	std::array<std::list<int>, 2> teamNumbers;
+	std::set<int> teamsTotal;
 
 	int teamOffset = 0;
 	int playerCount = 0;
@@ -258,11 +259,11 @@ void CMapGenerator::addPlayerInfo()
 			player.team = TeamID(*itTeam);
 			teamNumbers[j].erase(itTeam);
 		}
+		teamsTotal.insert(player.team.getNum());
 		map->map().players[pSettings.getColor().getNum()] = player;
 	}
 
-	map->map().howManyTeams = (mapGenOptions.getTeamCount() == 0 ? mapGenOptions.getPlayerCount() : mapGenOptions.getTeamCount())
-			+ (mapGenOptions.getCompOnlyTeamCount() == 0 ? mapGenOptions.getCompOnlyPlayerCount() : mapGenOptions.getCompOnlyTeamCount());
+	map->map().howManyTeams = teamsTotal.size();
 }
 
 void CMapGenerator::genZones()

From b590e4a0e4b9572622c342797820295d2208e546 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 08:38:33 +0400
Subject: [PATCH 27/35] Fixes for multiplyer

---
 client/lobby/RandomMapTab.cpp | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index c931d6f72..a2270e5b3 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -276,6 +276,20 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	}
 	if(auto w = widget<CToggleGroup>("groupMonsterStrength"))
 		w->setSelected(opts->getMonsterStrength());
+	if(auto w = widget<CButton>("templateButton"))
+	{
+		if(tmpl)
+			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
+		else
+			w->addTextOverlay("default", EFonts::FONT_SMALL);
+	}
+	for(auto r : VLC->terrainTypeHandler->roads())
+	{
+		if(auto w = widget<CToggleButton>(r.name))
+		{
+			w->setSelected(opts->isRoadEnabled(r.name));
+		}
+	}
 }
 
 void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)

From 9b76a8000fa14e0f75974a91792c045d66b0a8d1 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 20:38:16 +0400
Subject: [PATCH 28/35] Making team alignments widget customizable

---
 client/gui/InterfaceObjectConfigurable.cpp |  22 +++--
 client/gui/InterfaceObjectConfigurable.h   |   8 +-
 client/lobby/RandomMapTab.cpp              | 110 ++++++++++++---------
 client/lobby/RandomMapTab.h                |   4 +-
 4 files changed, 82 insertions(+), 62 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 31b43c2af..f6a2f141c 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -45,9 +45,14 @@ void InterfaceObjectConfigurable::addCallback(const std::string & callbackName,
 void InterfaceObjectConfigurable::init(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
+	
+	for(auto & item : config["variables"].Struct())
+	{
+		variables[item.first] = item.second;
+	}
+	
 	int unnamedObjectId = 0;
 	const std::string unnamedObjectPrefix = "__widget_";
-	
 	for(const auto & item : config["items"].Vector())
 	{
 		std::string name = item["name"].isNull()
@@ -57,11 +62,6 @@ void InterfaceObjectConfigurable::init(const JsonNode &config)
 	}
 }
 
-const JsonNode & InterfaceObjectConfigurable::variable(const std::string & name) const
-{
-	return variables[name];
-}
-
 std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
 {
 	if(config.isNull())
@@ -296,7 +296,7 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
 {
 	auto image = config["image"].String();
-	auto rect = readRect(config);
+	auto rect = readRect(config["rect"]);
 	return std::make_shared<CFilledTexture>(image, rect);
 }
 
@@ -323,9 +323,15 @@ std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const
 	return anim;
 }
 
-std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(const JsonNode & config) const
+std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode config) const
 {
 	assert(!config.isNull());
+	//overrides from variables
+	for(auto & item : config["overrides"].Struct())
+	{
+		config[item.first] = variables[item.second.String()];
+	}
+	
 	auto type = config["type"].String();
 	if(type == "picture")
 	{
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
index 4bb40e974..50ba9f30c 100644
--- a/client/gui/InterfaceObjectConfigurable.h
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -36,6 +36,7 @@ protected:
 	void init(const JsonNode & config);
 	
 	void addCallback(const std::string & callbackName, std::function<void(int)> callback);
+	JsonNode variables;
 	
 	template<class T>
 	const std::shared_ptr<T> widget(const std::string & name) const
@@ -45,9 +46,7 @@ protected:
 			return nullptr;
 		return std::dynamic_pointer_cast<T>(iter->second);
 	}
-	
-	const JsonNode & variable(const std::string & name) const;
-	
+		
 	//basic serializers
 	Point readPosition(const JsonNode &) const;
 	Rect readRect(const JsonNode &) const;
@@ -72,11 +71,10 @@ protected:
 	
 	//composite widgets
 	virtual std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config);
-	std::shared_ptr<CIntObject> buildWidget(const JsonNode & config) const;
+	std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
 	
 private:
 	
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
 	std::map<std::string, std::function<void(int)>> callbacks;
-	JsonNode variables;
 };
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index a2270e5b3..de7e29006 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -480,10 +480,11 @@ void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
 }
 
 TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
-	CIntObject(),
+	InterfaceObjectConfigurable(),
 	randomMapTab(randomMapTab)
 {
-	OBJ_CONSTRUCTION;
+	const JsonNode config(ResourceID("config/widgets/randomMapTeamsWidget.json"));
+	variables = config["variables"];
 	
 	int humanPlayers = randomMapTab.obtainMapGenOptions().getPlayerCount();
 	int cpuPlayers = randomMapTab.obtainMapGenOptions().getCompOnlyPlayerCount();
@@ -491,51 +492,20 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 	? PlayerColor::PLAYER_LIMIT_I : humanPlayers + cpuPlayers;
 	assert(totalPlayers <= PlayerColor::PLAYER_LIMIT_I);
 	auto settings = randomMapTab.obtainMapGenOptions().getPlayersSettings();
+	variables["totalPlayers"].Integer() = totalPlayers;
 	
-	pos.w = 80 + totalPlayers * 32;
-	pos.h = 80 + totalPlayers * 32;
-	background = std::make_shared<CFilledTexture>("Bl3DCvex", pos);
-	center(pos);
+	pos.w = variables["windowSize"]["x"].Integer() + totalPlayers * variables["cellMargin"]["x"].Integer();
+	pos.h = variables["windowSize"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
+	variables["backgroundRect"]["x"].Integer() = pos.x;
+	variables["backgroundRect"]["y"].Integer() = pos.y;
+	variables["backgroundRect"]["w"].Integer() = pos.w;
+	variables["backgroundRect"]["h"].Integer() = pos.h;
+	variables["okButtonPosition"]["x"].Integer() = variables["buttonsOffset"]["ok"]["x"].Integer();
+	variables["okButtonPosition"]["y"].Integer() = variables["buttonsOffset"]["ok"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
+	variables["cancelButtonPosition"]["x"].Integer() = variables["buttonsOffset"]["cancel"]["x"].Integer();
+	variables["cancelButtonPosition"]["y"].Integer() = variables["buttonsOffset"]["cancel"]["y"].Integer() + totalPlayers * variables["cellMargin"]["y"].Integer();
 	
-	for(int plId = 0; plId < totalPlayers; ++plId)
-	{
-		players.push_back(std::make_shared<CToggleGroup>([&, totalPlayers, plId](int sel)
-		{
-			SObjectConstruction obj__i(players[plId].get());
-			for(int teamId = 0; teamId < totalPlayers; ++teamId)
-			{
-				auto button = std::dynamic_pointer_cast<CToggleButton>(players[plId]->buttons[teamId]);
-				assert(button);
-				if(sel == teamId)
-				{
-					button->addOverlay(std::make_shared<CAnimImage>("ITGFLAGS", plId, 0, 8, 8));
-				}
-				else
-				{
-					button->addOverlay(std::make_shared<CPicture>("TeamPlSl"));
-				}
-			}
-		}));
-		
-		SObjectConstruction obj__i(players.back().get());
-		for(int teamId = 0; teamId < totalPlayers; ++teamId)
-		{
-			Point p(40 + plId * 32, 20 + teamId * 32);
-			placeholders.push_back(std::make_shared<CPicture>("TeamPlSl", p.x, p.y));
-			auto button = std::make_shared<CToggleButton>(p, "TeamPlSl", std::pair<std::string, std::string>{"", ""});
-			button->pos.w = 32;
-			button->pos.h = 32;
-			players.back()->addToggle(teamId, button);
-		}
-		
-		auto team = settings.at(PlayerColor(plId)).getTeam();
-		if(team == TeamID::NO_TEAM)
-			players.back()->setSelected(plId);
-		else
-			players.back()->setSelected(team.getNum());
-	}
-	
-	buttonOk = std::make_shared<CButton>(Point(40, 40 + totalPlayers * 32), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], [&]()
+	addCallback("ok", [&](int)
 	{
 		for(int plId = 0; plId < players.size(); ++plId)
 		{
@@ -545,11 +515,57 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 		assert(GH.topInt().get() == this);
 		GH.popInt(GH.topInt());
 	});
-	buttonCancel = std::make_shared<CButton>(Point(120, 40 + totalPlayers * 32), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [&]()
+	
+	addCallback("cancel", [&](int)
 	{
 		assert(GH.topInt().get() == this);
 		GH.popInt(GH.topInt());
-	}, SDLK_ESCAPE);
+	});
 	
+	init(config);
 	
+	center(pos);
+	
+	OBJ_CONSTRUCTION;
+	
+	for(int plId = 0; plId < totalPlayers; ++plId)
+	{
+		players.push_back(std::make_shared<CToggleGroup>([&, totalPlayers, plId](int sel)
+		{
+			variables["player_id"].Integer() = plId;
+			SObjectConstruction obj__i(players[plId].get());
+			for(int teamId = 0; teamId < totalPlayers; ++teamId)
+			{
+				auto button = std::dynamic_pointer_cast<CToggleButton>(players[plId]->buttons[teamId]);
+				assert(button);
+				if(sel == teamId)
+				{
+					button->addOverlay(buildWidget(variables["flagsAnimation"]));
+				}
+				else
+				{
+					button->addOverlay(buildWidget(variables["unchecked"]));
+				}
+			}
+		}));
+		
+		SObjectConstruction obj__i(players.back().get());
+		for(int teamId = 0; teamId < totalPlayers; ++teamId)
+		{
+			variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer();
+			variables["point"]["y"].Integer() = variables["cellOffset"]["y"].Integer() + teamId * variables["cellMargin"]["y"].Integer();
+			//Point p(40 + plId * 32, 20 + teamId * 32);
+			placeholders.push_back(buildWidget(variables["placeholder"]));
+			auto button = buildWidget(variables["button"]);
+			button->pos.w = variables["cellMargin"]["x"].Integer();
+			button->pos.h = variables["cellMargin"]["y"].Integer();
+			players.back()->addToggle(teamId, std::dynamic_pointer_cast<CToggleBase>(button));
+		}
+		
+		auto team = settings.at(PlayerColor(plId)).getTeam();
+		if(team == TeamID::NO_TEAM)
+			players.back()->setSelected(plId);
+		else
+			players.back()->setSelected(team.getNum());
+	}
 }
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index 6b307169b..8909f5f1a 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -87,7 +87,7 @@ private:
 	
 };
 
-class TeamAlignmentsWidget: public CIntObject
+class TeamAlignmentsWidget: public InterfaceObjectConfigurable
 {
 public:
 	TeamAlignmentsWidget(RandomMapTab & randomMapTab);
@@ -100,5 +100,5 @@ private:
 	std::shared_ptr<CLabelGroup> labels;
 	std::shared_ptr<CButton> buttonOk, buttonCancel;
 	std::vector<std::shared_ptr<CToggleGroup>> players;
-	std::vector<std::shared_ptr<CPicture>> placeholders;
+	std::vector<std::shared_ptr<CIntObject>> placeholders;
 };

From d7b0770b7162fec4a33c7c0b1dd63fff7ecbbe59 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Sat, 17 Dec 2022 20:50:33 +0400
Subject: [PATCH 29/35] Remove excessive code

---
 client/lobby/RandomMapTab.cpp | 6 +-----
 client/widgets/Buttons.cpp    | 7 +++++--
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index de7e29006..9bf461bcb 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -544,7 +544,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 				}
 				else
 				{
-					button->addOverlay(buildWidget(variables["unchecked"]));
+					button->addOverlay(nullptr);
 				}
 			}
 		}));
@@ -554,11 +554,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 		{
 			variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer();
 			variables["point"]["y"].Integer() = variables["cellOffset"]["y"].Integer() + teamId * variables["cellMargin"]["y"].Integer();
-			//Point p(40 + plId * 32, 20 + teamId * 32);
-			placeholders.push_back(buildWidget(variables["placeholder"]));
 			auto button = buildWidget(variables["button"]);
-			button->pos.w = variables["cellMargin"]["x"].Integer();
-			button->pos.h = variables["cellMargin"]["y"].Integer();
 			players.back()->addToggle(teamId, std::dynamic_pointer_cast<CToggleBase>(button));
 		}
 		
diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp
index 592eecdc0..98bb638d7 100644
--- a/client/widgets/Buttons.cpp
+++ b/client/widgets/Buttons.cpp
@@ -84,8 +84,11 @@ void CButton::addTextOverlay(const std::string & Text, EFonts font, SDL_Color co
 void CButton::addOverlay(std::shared_ptr<CIntObject> newOverlay)
 {
 	overlay = newOverlay;
-	addChild(newOverlay.get());
-	overlay->moveTo(overlay->pos.centerIn(pos).topLeft());
+	if(overlay)
+	{
+		addChild(newOverlay.get());
+		overlay->moveTo(overlay->pos.centerIn(pos).topLeft());
+	}
 	update();
 }
 

From 521328addd1f02955e442f6a01b967611a22f0ed Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Thu, 22 Dec 2022 00:54:06 +0400
Subject: [PATCH 30/35] Adding macros for targeted object binding

---
 client/gui/CGuiHandler.h                   | 1 +
 client/gui/InterfaceObjectConfigurable.cpp | 4 ++--
 client/lobby/RandomMapTab.cpp              | 4 ++--
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h
index 86526d5d6..0c4df679d 100644
--- a/client/gui/CGuiHandler.h
+++ b/client/gui/CGuiHandler.h
@@ -165,6 +165,7 @@ struct SSetCaptureState
 };
 
 #define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
+#define OBJ_CONSTRUCTION_TARGETED(obj) SObjectConstruction obj__i(obj)
 #define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
 #define OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(actions) SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
 
diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index f6a2f141c..92c0ad701 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -27,7 +27,7 @@
 
 
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
-	CIntObject(used, offset)
+	InterfaceObjectConfigurable(used, offset)
 {
 	init(config);
 }
@@ -202,7 +202,7 @@ std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(cons
 	group->pos += position;
 	if(!config["items"].isNull())
 	{
-		SObjectConstruction obj__i(group.get());
+		OBJ_CONSTRUCTION_TARGETED(group.get());
 		int itemIdx = -1;
 		for(const auto & item : config["items"].Vector())
 		{
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 9bf461bcb..9b5cd68da 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -533,7 +533,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 		players.push_back(std::make_shared<CToggleGroup>([&, totalPlayers, plId](int sel)
 		{
 			variables["player_id"].Integer() = plId;
-			SObjectConstruction obj__i(players[plId].get());
+			OBJ_CONSTRUCTION_TARGETED(players[plId].get());
 			for(int teamId = 0; teamId < totalPlayers; ++teamId)
 			{
 				auto button = std::dynamic_pointer_cast<CToggleButton>(players[plId]->buttons[teamId]);
@@ -549,7 +549,7 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 			}
 		}));
 		
-		SObjectConstruction obj__i(players.back().get());
+		OBJ_CONSTRUCTION_TARGETED(players.back().get());
 		for(int teamId = 0; teamId < totalPlayers; ++teamId)
 		{
 			variables["point"]["x"].Integer() = variables["cellOffset"]["x"].Integer() + plId * variables["cellMargin"]["x"].Integer();

From c7f430f051e6152e9c5c5ee43b2e94dc3da3b109 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Thu, 22 Dec 2022 01:13:32 +0400
Subject: [PATCH 31/35] Added debug lines

---
 client/gui/InterfaceObjectConfigurable.cpp | 37 +++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 92c0ad701..b66370f26 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -45,9 +45,10 @@ void InterfaceObjectConfigurable::addCallback(const std::string & callbackName,
 void InterfaceObjectConfigurable::init(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
-	
+	logGlobal->info("Building configurable interface object");
 	for(auto & item : config["variables"].Struct())
 	{
+		logGlobal->debug("Read variable named %s", item.first);
 		variables[item.first] = item.second;
 	}
 	
@@ -58,6 +59,7 @@ void InterfaceObjectConfigurable::init(const JsonNode &config)
 		std::string name = item["name"].isNull()
 						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
 						: item["name"].String();
+		logGlobal->debug("Building widget with name %s", name);
 		widgets[name] = buildWidget(item);
 	}
 }
@@ -69,11 +71,13 @@ std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
 	
 	if(config.isNumber())
 	{
+		logGlobal->debug("Reading text from generaltext handler id:%d", config.Integer());
 		return CGI->generaltexth->allTexts[config.Integer()];
 	}
 	
 	const std::string delimiter = "/";
 	std::string s = config.String();
+	logGlobal->debug("Reading text from translations by key: %s", s);
 	JsonNode translated = CGI->generaltexth->localizedTexts;
 	for(size_t p = s.find(delimiter); p != std::string::npos; p = s.find(delimiter))
 	{
@@ -81,13 +85,17 @@ std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
 		s.erase(0, p + delimiter.length());
 	}
 	if(s == config.String())
+	{
+		logGlobal->warn("Reading non-translated text: %s", s);
 		return s;
+	}
 	return translated[s].String();
 }
 
 Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
 {
 	Point p;
+	logGlobal->debug("Reading point");
 	p.x = config["x"].Integer();
 	p.y = config["y"].Integer();
 	return p;
@@ -96,6 +104,7 @@ Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
 Rect InterfaceObjectConfigurable::readRect(const JsonNode & config) const
 {
 	Rect p;
+	logGlobal->debug("Reading rect");
 	p.x = config["x"].Integer();
 	p.y = config["y"].Integer();
 	p.w = config["w"].Integer();
@@ -105,6 +114,7 @@ Rect InterfaceObjectConfigurable::readRect(const JsonNode & config) const
 
 ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const
 {
+	logGlobal->debug("Reading text alignment");
 	if(!config.isNull())
 	{
 		if(config.String() == "center")
@@ -114,11 +124,13 @@ ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & c
 		if(config.String() == "right")
 			return ETextAlignment::BOTTOMRIGHT;
 	}
+	logGlobal->debug("Uknown text alignment attribute");
 	return ETextAlignment::CENTER;
 }
 
 SDL_Color InterfaceObjectConfigurable::readColor(const JsonNode & config) const
 {
+	logGlobal->debug("Reading color");
 	if(!config.isNull())
 	{
 		if(config.String() == "yellow")
@@ -134,11 +146,13 @@ SDL_Color InterfaceObjectConfigurable::readColor(const JsonNode & config) const
 		if(config.String() == "bright-yellow")
 			return Colors::BRIGHT_YELLOW;
 	}
+	logGlobal->debug("Uknown color attribute");
 	return Colors::DEFAULT_KEY_COLOR;
 	
 }
 EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
 {
+	logGlobal->debug("Reading font");
 	if(!config.isNull())
 	{
 		if(config.String() == "big")
@@ -150,16 +164,21 @@ EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
 		if(config.String() == "tiny")
 			return EFonts::FONT_TINY;
 	}
+	logGlobal->debug("Uknown font attribute");
 	return EFonts::FONT_TIMES;
 }
 
 std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(const JsonNode & config) const
 {
+	logGlobal->debug("Reading hint text");
 	std::pair<std::string, std::string> result;
 	if(!config.isNull())
 	{
 		if(config.isNumber())
+		{
+			logGlobal->debug("Reading hint text (zelp) from generaltext handler id:%d", config.Integer());
 			return CGI->generaltexth->zelp[config.Integer()];
+		}
 		
 		if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
 		{
@@ -169,6 +188,7 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
 		}
 		if(config.getType() == JsonNode::JsonType::DATA_STRING)
 		{
+			logGlobal->debug("Reading non-translated hint: %s", config.String());
 			result.first = result.second = config.String();
 		}
 	}
@@ -177,6 +197,7 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
 
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CPicture");
 	auto image = config["image"].String();
 	auto position = readPosition(config["position"]);
 	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
@@ -187,6 +208,7 @@ std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNo
 
 std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CLabel");
 	auto font = readFont(config["font"]);
 	auto alignment = readTextAlignment(config["alignment"]);
 	auto color = readColor(config["color"]);
@@ -197,6 +219,7 @@ std::shared_ptr<CLabel> InterfaceObjectConfigurable::buildLabel(const JsonNode &
 
 std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CToggleGroup");
 	auto position = readPosition(config["position"]);
 	auto group = std::make_shared<CToggleGroup>(0);
 	group->pos += position;
@@ -219,6 +242,7 @@ std::shared_ptr<CToggleGroup> InterfaceObjectConfigurable::buildToggleGroup(cons
 
 std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CToggleButton");
 	auto position = readPosition(config["position"]);
 	auto image = config["image"].String();
 	auto zelp = readHintText(config["zelp"]);
@@ -238,6 +262,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
 
 std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CButton");
 	auto position = readPosition(config["position"]);
 	auto image = config["image"].String();
 	auto zelp = readHintText(config["zelp"]);
@@ -256,6 +281,7 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 
 std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CLabelGroup");
 	auto font = readFont(config["font"]);
 	auto alignment = readTextAlignment(config["alignment"]);
 	auto color = readColor(config["color"]);
@@ -274,6 +300,7 @@ std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const
 
 std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CSlider");
 	auto position = readPosition(config["position"]);
 	int length = config["size"].Integer();
 	auto style = config["style"].String() == "brown" ? CSlider::BROWN : CSlider::BLUE;
@@ -286,6 +313,7 @@ std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode
 
 std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CAnimImage");
 	auto position = readPosition(config["position"]);
 	auto image = config["image"].String();
 	int group = config["group"].isNull() ? 0 : config["group"].Integer();
@@ -295,6 +323,7 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 
 std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CFilledTexture");
 	auto image = config["image"].String();
 	auto rect = readRect(config["rect"]);
 	return std::make_shared<CFilledTexture>(image, rect);
@@ -302,6 +331,7 @@ std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const
 
 std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const JsonNode & config) const
 {
+	logGlobal->debug("Building widget CShowableAnim");
 	auto position = readPosition(config["position"]);
 	auto image = config["image"].String();
 	ui8 flags = 0;
@@ -326,9 +356,11 @@ std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const
 std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode config) const
 {
 	assert(!config.isNull());
+	logGlobal->debug("Building widget from config");
 	//overrides from variables
 	for(auto & item : config["overrides"].Struct())
 	{
+		logGlobal->debug("Config attribute %s was overriden by variable %s", item.first, item.second.String());
 		config[item.first] = variables[item.second.String()];
 	}
 	
@@ -375,12 +407,15 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode co
 	}
 	if(type == "custom")
 	{
+		logGlobal->debug("Calling custom widget building function");
 		return const_cast<InterfaceObjectConfigurable*>(this)->buildCustomWidget(config);
 	}
+	logGlobal->error("Unknown type, nullptr will be returned");
 	return std::shared_ptr<CIntObject>(nullptr);
 }
 
 std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildCustomWidget(const JsonNode & config)
 {
+	logGlobal->error("Default custom widget builder called");
 	return nullptr;
 }

From 8f7025328f379173b85e2a79bf5e2266ae188d17 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Thu, 22 Dec 2022 01:37:33 +0400
Subject: [PATCH 32/35] Read default template from config, named custom types

---
 client/gui/InterfaceObjectConfigurable.cpp | 10 +++-------
 client/lobby/RandomMapTab.cpp              | 17 +++++++++++------
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index b66370f26..604ea5f0d 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -405,13 +405,9 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode co
 	{
 		return buildSlider(config);
 	}
-	if(type == "custom")
-	{
-		logGlobal->debug("Calling custom widget building function");
-		return const_cast<InterfaceObjectConfigurable*>(this)->buildCustomWidget(config);
-	}
-	logGlobal->error("Unknown type, nullptr will be returned");
-	return std::shared_ptr<CIntObject>(nullptr);
+
+	logGlobal->debug("Calling custom widget building function");
+	return const_cast<InterfaceObjectConfigurable*>(this)->buildCustomWidget(config);
 }
 
 std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildCustomWidget(const JsonNode & config)
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index 9b5cd68da..b65107728 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -281,7 +281,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 		if(tmpl)
 			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
 		else
-			w->addTextOverlay("default", EFonts::FONT_SMALL);
+			w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
 	}
 	for(auto r : VLC->terrainTypeHandler->roads())
 	{
@@ -301,7 +301,7 @@ void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
 		if(tmpl)
 			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
 		else
-			w->addTextOverlay("default", EFonts::FONT_SMALL);
+			w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
 	}
 	updateMapInfoByHost();
 }
@@ -362,7 +362,7 @@ void TemplatesDropBox::ListItem::updateItem(int idx, const CRmgTemplate * _item)
 			if(idx)
 				w->setText("");
 			else
-				w->setText("default");
+				w->setText(readText(dropBox.variables["defaultTemplate"]));
 		}
 	}
 }
@@ -424,9 +424,14 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 
 std::shared_ptr<CIntObject> TemplatesDropBox::buildCustomWidget(const JsonNode & config)
 {
-	auto position = readPosition(config["position"]);
-	listItems.push_back(std::make_shared<ListItem>(config, *this, position));
-	return listItems.back();
+	if(config["type"].String() == "templateListItem")
+	{
+		auto position = readPosition(config["position"]);
+		listItems.push_back(std::make_shared<ListItem>(config, *this, position));
+		return listItems.back();
+	}
+	
+	return InterfaceObjectConfigurable::buildCustomWidget(config);
 }
 
 void TemplatesDropBox::sliderMove(int slidPos)

From e245dbaf9dc4e69796053fc5d9c742d25ee4ffe5 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Thu, 22 Dec 2022 02:05:29 +0400
Subject: [PATCH 33/35] Use kind-of-factory approach for widget builders

---
 client/gui/InterfaceObjectConfigurable.cpp | 66 +++++++---------------
 client/gui/InterfaceObjectConfigurable.h   | 11 +++-
 client/lobby/RandomMapTab.cpp              | 15 ++---
 client/lobby/RandomMapTab.h                |  4 +-
 4 files changed, 34 insertions(+), 62 deletions(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index 604ea5f0d..ef6575efe 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -35,6 +35,21 @@ InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(int used, Point offset):
 	CIntObject(used, offset)
 {
+	REGISTER_BUILDER("picture", &InterfaceObjectConfigurable::buildPicture);
+	REGISTER_BUILDER("image", &InterfaceObjectConfigurable::buildImage);
+	REGISTER_BUILDER("texture", &InterfaceObjectConfigurable::buildTexture);
+	REGISTER_BUILDER("animation", &InterfaceObjectConfigurable::buildAnimation);
+	REGISTER_BUILDER("label", &InterfaceObjectConfigurable::buildLabel);
+	REGISTER_BUILDER("toggleGroup", &InterfaceObjectConfigurable::buildToggleGroup);
+	REGISTER_BUILDER("toggleButton", &InterfaceObjectConfigurable::buildToggleButton);
+	REGISTER_BUILDER("button", &InterfaceObjectConfigurable::buildButton);
+	REGISTER_BUILDER("labelGroup", &InterfaceObjectConfigurable::buildLabelGroup);
+	REGISTER_BUILDER("slider", &InterfaceObjectConfigurable::buildSlider);
+}
+
+void InterfaceObjectConfigurable::registerBuilder(const std::string & type, BuilderFunction f)
+{
+	builders[type] = f;
 }
 
 void InterfaceObjectConfigurable::addCallback(const std::string & callbackName, std::function<void(int)> callback)
@@ -365,53 +380,10 @@ std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildWidget(JsonNode co
 	}
 	
 	auto type = config["type"].String();
-	if(type == "picture")
-	{
-		return buildPicture(config);
-	}
-	if(type == "image")
-	{
-		return buildImage(config);
-	}
-	if(type == "texture")
-	{
-		return buildTexture(config);
-	}
-	if(type == "animation")
-	{
-		return buildAnimation(config);
-	}
-	if(type == "label")
-	{
-		return buildLabel(config);
-	}
-	if(type == "toggleGroup")
-	{
-		return buildToggleGroup(config);
-	}
-	if(type == "toggleButton")
-	{
-		return buildToggleButton(config);
-	}
-	if(type == "button")
-	{
-		return buildButton(config);
-	}
-	if(type == "labelGroup")
-	{
-		return buildLabelGroup(config);
-	}
-	if(type == "slider")
-	{
-		return buildSlider(config);
-	}
+	auto buildIterator = builders.find(type);
+	if(buildIterator != builders.end())
+		return (buildIterator->second)(config);
 
-	logGlobal->debug("Calling custom widget building function");
-	return const_cast<InterfaceObjectConfigurable*>(this)->buildCustomWidget(config);
-}
-
-std::shared_ptr<CIntObject> InterfaceObjectConfigurable::buildCustomWidget(const JsonNode & config)
-{
-	logGlobal->error("Default custom widget builder called");
+	logGlobal->error("Builder with type %s is not registered", type);
 	return nullptr;
 }
diff --git a/client/gui/InterfaceObjectConfigurable.h b/client/gui/InterfaceObjectConfigurable.h
index 50ba9f30c..3525a8d09 100644
--- a/client/gui/InterfaceObjectConfigurable.h
+++ b/client/gui/InterfaceObjectConfigurable.h
@@ -25,6 +25,8 @@ class CAnimImage;
 class CShowableAnim;
 class CFilledTexture;
 
+#define REGISTER_BUILDER(type, method) registerBuilder(type, std::bind(method, this, std::placeholders::_1))
+
 class InterfaceObjectConfigurable: public CIntObject
 {
 public:
@@ -32,6 +34,10 @@ public:
 	InterfaceObjectConfigurable(const JsonNode & config, int used=0, Point offset=Point());
 
 protected:
+	
+	using BuilderFunction = std::function<std::shared_ptr<CIntObject>(const JsonNode &)>;
+	void registerBuilder(const std::string &, BuilderFunction);
+	
 	//must be called after adding callbacks
 	void init(const JsonNode & config);
 	
@@ -67,14 +73,13 @@ protected:
 	std::shared_ptr<CAnimImage> buildImage(const JsonNode &) const;
 	std::shared_ptr<CShowableAnim> buildAnimation(const JsonNode &) const;
 	std::shared_ptr<CFilledTexture> buildTexture(const JsonNode &) const;
-	
-	
+		
 	//composite widgets
-	virtual std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config);
 	std::shared_ptr<CIntObject> buildWidget(JsonNode config) const;
 	
 private:
 	
+	std::map<std::string, BuilderFunction> builders;
 	std::map<std::string, std::shared_ptr<CIntObject>> widgets;
 	std::map<std::string, std::function<void(int)>> callbacks;
 };
diff --git a/client/lobby/RandomMapTab.cpp b/client/lobby/RandomMapTab.cpp
index b65107728..8f89ffaa1 100644
--- a/client/lobby/RandomMapTab.cpp
+++ b/client/lobby/RandomMapTab.cpp
@@ -399,6 +399,8 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 	InterfaceObjectConfigurable(LCLICK | HOVER),
 	randomMapTab(randomMapTab)
 {
+	REGISTER_BUILDER("templateListItem", &TemplatesDropBox::buildListItem);
+	
 	curItems = VLC->tplh->getTemplates();
 	vstd::erase_if(curItems, [size](const CRmgTemplate * t){return !t->matchesSize(size);});
 	curItems.insert(curItems.begin(), nullptr); //default template
@@ -422,16 +424,11 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
 	updateListItems();
 }
 
-std::shared_ptr<CIntObject> TemplatesDropBox::buildCustomWidget(const JsonNode & config)
+std::shared_ptr<CIntObject> TemplatesDropBox::buildListItem(const JsonNode & config)
 {
-	if(config["type"].String() == "templateListItem")
-	{
-		auto position = readPosition(config["position"]);
-		listItems.push_back(std::make_shared<ListItem>(config, *this, position));
-		return listItems.back();
-	}
-	
-	return InterfaceObjectConfigurable::buildCustomWidget(config);
+	auto position = readPosition(config["position"]);
+	listItems.push_back(std::make_shared<ListItem>(config, *this, position));
+	return listItems.back();
 }
 
 void TemplatesDropBox::sliderMove(int slidPos)
diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index 8909f5f1a..babd6ff41 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -72,10 +72,8 @@ public:
 	void clickLeft(tribool down, bool previousState) override;
 	void setTemplate(const CRmgTemplate *);
 	
-protected:
-	std::shared_ptr<CIntObject> buildCustomWidget(const JsonNode & config) override;
-	
 private:
+	std::shared_ptr<CIntObject> buildListItem(const JsonNode & config);
 	
 	void sliderMove(int slidPos);
 	void updateListItems();

From cb76cc54ac2f54459693d7baca35368aa9b95d00 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Thu, 22 Dec 2022 02:08:26 +0400
Subject: [PATCH 34/35] Move logging to debug level

---
 client/gui/InterfaceObjectConfigurable.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp
index ef6575efe..99639b0f1 100644
--- a/client/gui/InterfaceObjectConfigurable.cpp
+++ b/client/gui/InterfaceObjectConfigurable.cpp
@@ -60,7 +60,7 @@ void InterfaceObjectConfigurable::addCallback(const std::string & callbackName,
 void InterfaceObjectConfigurable::init(const JsonNode &config)
 {
 	OBJ_CONSTRUCTION;
-	logGlobal->info("Building configurable interface object");
+	logGlobal->debug("Building configurable interface object");
 	for(auto & item : config["variables"].Struct())
 	{
 		logGlobal->debug("Read variable named %s", item.first);

From ad5bf2ac4fdf2a9900cc0a02a49151f9634e72e6 Mon Sep 17 00:00:00 2001
From: nordsoft <nordsoft@rambler.ru>
Date: Thu, 22 Dec 2022 02:10:56 +0400
Subject: [PATCH 35/35] Fix msvc build

---
 client/lobby/RandomMapTab.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/client/lobby/RandomMapTab.h b/client/lobby/RandomMapTab.h
index babd6ff41..454f8fcb2 100644
--- a/client/lobby/RandomMapTab.h
+++ b/client/lobby/RandomMapTab.h
@@ -65,6 +65,8 @@ class TemplatesDropBox : public InterfaceObjectConfigurable
 		void clickLeft(tribool down, bool previousState) override;
 	};
 	
+	friend struct ListItem;
+	
 public:
 	TemplatesDropBox(RandomMapTab & randomMapTab, int3 size);