Browse Source

Merge pull request #1225 from Nordsoft91/interface-builder

Nordsoft91 2 years ago
parent
commit
a7f521cbd9

+ 2 - 0
client/CMakeLists.txt

@@ -25,6 +25,7 @@ set(client_SRCS
 		gui/Geometries.cpp
 		gui/SDL_Extensions.cpp
 		gui/NotificationHandler.cpp
+		gui/InterfaceObjectConfigurable.cpp
 
 		widgets/AdventureMapClasses.cpp
 		widgets/Buttons.cpp
@@ -110,6 +111,7 @@ set(client_HEADERS
 		gui/SDL_Extensions.h
 		gui/SDL_Pixels.h
 		gui/NotificationHandler.h
+		gui/InterfaceObjectConfigurable.h
 
 		widgets/AdventureMapClasses.h
 		widgets/Buttons.h

+ 1 - 0
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)
 

+ 389 - 0
client/gui/InterfaceObjectConfigurable.cpp

@@ -0,0 +1,389 @@
+/*
+* 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, int used, Point offset):
+	InterfaceObjectConfigurable(used, offset)
+{
+	init(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)
+{
+	callbacks[callbackName] = callback;
+}
+
+void InterfaceObjectConfigurable::init(const JsonNode &config)
+{
+	OBJ_CONSTRUCTION;
+	logGlobal->debug("Building configurable interface object");
+	for(auto & item : config["variables"].Struct())
+	{
+		logGlobal->debug("Read variable named %s", item.first);
+		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()
+						? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
+						: item["name"].String();
+		logGlobal->debug("Building widget with name %s", name);
+		widgets[name] = buildWidget(item);
+	}
+}
+
+std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
+{
+	if(config.isNull())
+		return "";
+	
+	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))
+	{
+		translated = translated[s.substr(0, p)];
+		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;
+}
+
+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();
+	p.h = config["h"].Integer();
+	return p;
+}
+
+ETextAlignment InterfaceObjectConfigurable::readTextAlignment(const JsonNode & config) const
+{
+	logGlobal->debug("Reading text alignment");
+	if(!config.isNull())
+	{
+		if(config.String() == "center")
+			return ETextAlignment::CENTER;
+		if(config.String() == "left")
+			return ETextAlignment::TOPLEFT;
+		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")
+			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;
+	}
+	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")
+			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;
+	}
+	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)
+		{
+			result.first = readText(config["hover"]);
+			result.second = readText(config["help"]);
+			return result;
+		}
+		if(config.getType() == JsonNode::JsonType::DATA_STRING)
+		{
+			logGlobal->debug("Reading non-translated hint: %s", config.String());
+			result.first = result.second = config.String();
+		}
+	}
+	return result;
+}
+
+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);
+	if(!config["visible"].isNull())
+		pic->visible = config["visible"].Bool();
+	return pic;
+}
+
+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"]);
+	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
+{
+	logGlobal->debug("Building widget CToggleGroup");
+	auto position = readPosition(config["position"]);
+	auto group = std::make_shared<CToggleGroup>(0);
+	group->pos += position;
+	if(!config["items"].isNull())
+	{
+		OBJ_CONSTRUCTION_TARGETED(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
+{
+	logGlobal->debug("Building widget CToggleButton");
+	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
+{
+	logGlobal->debug("Building widget CButton");
+	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
+{
+	logGlobal->debug("Building widget CLabelGroup");
+	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<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;
+	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<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();
+	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
+{
+	logGlobal->debug("Building widget CFilledTexture");
+	auto image = config["image"].String();
+	auto rect = readRect(config["rect"]);
+	return std::make_shared<CFilledTexture>(image, rect);
+}
+
+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;
+	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(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()];
+	}
+	
+	auto type = config["type"].String();
+	auto buildIterator = builders.find(type);
+	if(buildIterator != builders.end())
+		return (buildIterator->second)(config);
+
+	logGlobal->error("Builder with type %s is not registered", type);
+	return nullptr;
+}

+ 85 - 0
client/gui/InterfaceObjectConfigurable.h

@@ -0,0 +1,85 @@
+/*
+* 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 CPicture;
+class CLabel;
+class CToggleGroup;
+class CToggleButton;
+class CButton;
+class CLabelGroup;
+class CSlider;
+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:
+	InterfaceObjectConfigurable(int used=0, Point offset=Point());
+	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);
+	
+	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
+	{
+		auto iter = widgets.find(name);
+		if(iter == widgets.end())
+			return nullptr;
+		return std::dynamic_pointer_cast<T>(iter->second);
+	}
+		
+	//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;
+	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<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
+	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;
+};

+ 1 - 1
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
 *

+ 400 - 145
client/lobby/RandomMapTab.cpp

@@ -27,127 +27,68 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/mapping/CMapInfo.h"
 #include "../../lib/rmg/CMapGenOptions.h"
+#include "../../lib/CModHandler.h"
+#include "../../lib/rmg/CRmgTemplateStorage.h"
 
-RandomMapTab::RandomMapTab()
+RandomMapTab::RandomMapTab():
+	InterfaceObjectConfigurable()
 {
 	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, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[738]);
-	labelHeadlineSmall = std::make_shared<CLabel>(222, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[739]);
-
-	labelMapSize = std::make_shared<CLabel>(104, 97, FONT_SMALL, ETextAlignment::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/widgets/randomMapTab.json"));
+	addCallback("toggleMapSize", [&](int btnId)
 	{
 		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();
 	});
-
-	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);
+		if(mapGenOptions->getMapTemplate())
+			if(!mapGenOptions->getMapTemplate()->matchesSize(int3{mapGenOptions->getWidth(), mapGenOptions->getHeight(), 1 + mapGenOptions->getHasTwoLevels()}))
+				setTemplate(nullptr);
 		updateMapInfoByHost();
 	});
-
-	labelGroupForOptions = std::make_shared<CLabelGroup>(FONT_SMALL, ETextAlignment::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);
-
-		// deactive some CompOnlyPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
-		deactivateButtonsFrom(groupCompOnlyPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
-
-		validatePlayersCnt(btnId);
+		setMapGenOptions(mapGenOptions);
 		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(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId));
-		validateCompOnlyPlayersCnt(btnId);
+		setMapGenOptions(mapGenOptions);
 		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("setMonsterStrength", [&](int btnId)
 	{
 		if(btnId < 0)
 			mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
@@ -155,9 +96,31 @@ 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]);
-
+	
+	//new callbacks available only from mod
+	addCallback("templateSelection", [&](int)
+	{
+		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.name;
+		addCallback(cbRoadType, [&, road](bool on)
+		{
+			mapGenOptions->setRoadEnabled(road.name, on);
+			updateMapInfoByHost();
+		});
+	}
+	
+	
+	init(config);
+	
 	updateMapInfoByHost();
 }
 
@@ -192,6 +155,7 @@ void RandomMapTab::updateMapInfoByHost()
 
 	mapInfo->mapHeader->howManyTeams = playersToGen;
 
+	std::set<TeamID> occupiedTeams;
 	for(int i = 0; i < playersToGen; ++i)
 	{
 		PlayerInfo player;
@@ -205,60 +169,153 @@ 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);
 }
 
 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);
+	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 opts->getPlayerCount() <= el;
+		});
+		
+		if(!playerTeamsAllowed.count(opts->getTeamCount()))
+		   opts->setTeamCount(CMapGenOptions::RANDOM_SIZE);
+	}
+	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 opts->getCompOnlyPlayerCount() <= el;
+		});
+		
+		if(!compTeamsAllowed.count(opts->getCompOnlyTeamCount()))
+			opts->setCompOnlyTeamCount(CMapGenOptions::RANDOM_SIZE);
+	}
+	
+	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, playerTeamsAllowed);
+	}
+	if(auto w = widget<CToggleGroup>("groupCompOnlyPlayers"))
+	{
+		w->setSelected(opts->getCompOnlyPlayerCount());
+		deactivateButtonsFrom(*w, compCountAllowed);
+	}
+	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());
+	if(auto w = widget<CButton>("templateButton"))
+	{
+		if(tmpl)
+			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
+		else
+			w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
+	}
+	for(auto r : VLC->terrainTypeHandler->roads())
+	{
+		if(auto w = widget<CToggleButton>(r.name))
+		{
+			w->setSelected(opts->isRoadEnabled(r.name));
+		}
+	}
 }
 
-void RandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, bool animIdfromBtnId) const
+void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
 {
-	// 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);
+	mapGenOptions->setMapTemplate(tmpl);
+	setMapGenOptions(mapGenOptions);
+	if(auto w = widget<CButton>("templateButton"))
+	{
+		if(tmpl)
+			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL);
+		else
+			w->addTextOverlay(readText(variables["defaultTemplate"]), EFonts::FONT_SMALL);
 	}
+	updateMapInfoByHost();
 }
 
-void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
+void RandomMapTab::deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed)
 {
-	logGlobal->debug("Blocking buttons from %d", startId);
-	for(auto toggle : group->buttons)
+	logGlobal->debug("Blocking buttons");
+	for(auto toggle : group.buttons)
 	{
 		if(auto button = std::dynamic_pointer_cast<CToggleButton>(toggle.second))
 		{
-			if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
+			if(allowed.count(CMapGenOptions::RANDOM_SIZE)
+			   || allowed.count(toggle.first)
+			   || toggle.first == CMapGenOptions::RANDOM_SIZE)
 			{
 				button->block(false);
 			}
@@ -270,45 +327,243 @@ void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
 	}
 }
 
-void RandomMapTab::validatePlayersCnt(int playersCnt)
+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(const JsonNode & config, TemplatesDropBox & _dropBox, Point position)
+	: InterfaceObjectConfigurable(LCLICK | HOVER, position),
+	dropBox(_dropBox)
+{
+	OBJ_CONSTRUCTION;
+	
+	init(config);
+	
+	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)
+{
+	if(auto w = widget<CLabel>("labelName"))
+	{
+		item = _item;
+		if(item)
+		{
+			w->setText(item->getName());
+		}
+		else
+		{
+			if(idx)
+				w->setText("");
+			else
+				w->setText(readText(dropBox.variables["defaultTemplate"]));
+		}
+	}
+}
+
+void TemplatesDropBox::ListItem::hover(bool on)
 {
-	if(playersCnt == CMapGenOptions::RANDOM_SIZE)
+	auto h = widget<CPicture>("hoverImage");
+	auto w = widget<CLabel>("labelName");
+	if(h && w)
 	{
-		return;
+		if(w->getText().empty())
+		{
+			hovered = false;
+			h->visible = false;
+		}
+		else
+		{
+			h->visible = on;
+		}
 	}
+	redraw();
+}
 
-	if(mapGenOptions->getTeamCount() >= playersCnt)
+void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
+{
+	if(down && hovered)
 	{
-		mapGenOptions->setTeamCount(playersCnt - 1);
-		groupMaxTeams->setSelected(mapGenOptions->getTeamCount());
+		dropBox.setTemplate(item);
 	}
-	// total players should not exceed PlayerColor::PLAYER_LIMIT_I (8 in homm3)
-	if(mapGenOptions->getCompOnlyPlayerCount() + playersCnt > PlayerColor::PLAYER_LIMIT_I)
+}
+
+
+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
+	
+	const JsonNode config(ResourceID("config/widgets/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;
+	
+	init(config);
+	
+	if(auto w = widget<CSlider>("slider"))
 	{
-		mapGenOptions->setCompOnlyPlayerCount(PlayerColor::PLAYER_LIMIT_I - playersCnt);
-		groupCompOnlyPlayers->setSelected(mapGenOptions->getCompOnlyPlayerCount());
+		w->setAmount(curItems.size());
 	}
+	
+	updateListItems();
+}
+
+std::shared_ptr<CIntObject> TemplatesDropBox::buildListItem(const JsonNode & config)
+{
+	auto position = readPosition(config["position"]);
+	listItems.push_back(std::make_shared<ListItem>(config, *this, position));
+	return listItems.back();
+}
 
-	validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
+void TemplatesDropBox::sliderMove(int slidPos)
+{
+	auto w = widget<CSlider>("slider");
+	if(!w)
+		return; // ignore spurious call when slider is being created
+	updateListItems();
+	redraw();
 }
 
-void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
+void TemplatesDropBox::hover(bool on)
 {
-	if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
+	hovered = on;
+}
+
+void TemplatesDropBox::clickLeft(tribool down, bool previousState)
+{
+	if(down && !hovered)
 	{
-		return;
+		assert(GH.topInt().get() == this);
+		GH.popInt(GH.topInt());
 	}
+}
 
-	if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
+void TemplatesDropBox::updateListItems()
+{
+	if(auto w = widget<CSlider>("slider"))
 	{
-		int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
-		mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
-		updateMapInfoByHost();
-		groupCompOnlyTeams->setSelected(compOnlyTeamCount);
+		int elemIdx = w->getValue();
+		for(auto item : listItems)
+		{
+			if(elemIdx < curItems.size())
+			{
+				item->updateItem(elemIdx, curItems[elemIdx]);
+				elemIdx++;
+			}
+			else
+			{
+				item->updateItem(elemIdx);
+			}
+		}
 	}
 }
 
-std::vector<int> RandomMapTab::getPossibleMapSizes()
+void TemplatesDropBox::setTemplate(const CRmgTemplate * tmpl)
+{
+	randomMapTab.setTemplate(tmpl);
+	assert(GH.topInt().get() == this);
+	GH.popInt(GH.topInt());
+}
+
+TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
+	InterfaceObjectConfigurable(),
+	randomMapTab(randomMapTab)
 {
-	return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE};
+	const JsonNode config(ResourceID("config/widgets/randomMapTeamsWidget.json"));
+	variables = config["variables"];
+	
+	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();
+	variables["totalPlayers"].Integer() = totalPlayers;
+	
+	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();
+	
+	addCallback("ok", [&](int)
+	{
+		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());
+	});
+	
+	addCallback("cancel", [&](int)
+	{
+		assert(GH.topInt().get() == this);
+		GH.popInt(GH.topInt());
+	});
+	
+	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;
+			OBJ_CONSTRUCTION_TARGETED(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(nullptr);
+				}
+			}
+		}));
+		
+		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();
+			variables["point"]["y"].Integer() = variables["cellOffset"]["y"].Integer() + teamId * variables["cellMargin"]["y"].Integer();
+			auto button = buildWidget(variables["button"]);
+			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());
+	}
 }

+ 63 - 23
client/lobby/RandomMapTab.h

@@ -13,6 +13,8 @@
 
 #include "../../lib/FunctionList.h"
 #include "../../lib/GameConstants.h"
+#include "../../lib/rmg/CRmgTemplate.h"
+#include "../gui/InterfaceObjectConfigurable.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -23,42 +25,80 @@ VCMI_LIB_NAMESPACE_END
 class CToggleButton;
 class CLabel;
 class CLabelGroup;
+class CSlider;
+class CPicture;
 
-class RandomMapTab : public CIntObject
+class RandomMapTab : public InterfaceObjectConfigurable
 {
 public:
 	RandomMapTab();
 
 	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;
 
 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);
+	void deactivateButtonsFrom(CToggleGroup & group, const std::set<int> & allowed);
 	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;
+	
+	//options allowed - need to store as impact each other
+	std::set<int> playerCountAllowed, playerTeamsAllowed, compCountAllowed, compTeamsAllowed;
+};
+
+class TemplatesDropBox : public InterfaceObjectConfigurable
+{
+	struct ListItem : public InterfaceObjectConfigurable
+	{
+		TemplatesDropBox & dropBox;
+		const CRmgTemplate * item = nullptr;
+		
+		ListItem(const JsonNode &, TemplatesDropBox &, Point position);
+		void updateItem(int index, const CRmgTemplate * item = nullptr);
+		
+		void hover(bool on) override;
+		void clickLeft(tribool down, bool previousState) override;
+	};
+	
+	friend struct ListItem;
+	
+public:
+	TemplatesDropBox(RandomMapTab & randomMapTab, int3 size);
+	
+	void hover(bool on) override;
+	void clickLeft(tribool down, bool previousState) override;
+	void setTemplate(const CRmgTemplate *);
+	
+private:
+	std::shared_ptr<CIntObject> buildListItem(const JsonNode & config);
+	
+	void sliderMove(int slidPos);
+	void updateListItems();
+	
+	RandomMapTab & randomMapTab;
+	std::vector<std::shared_ptr<ListItem>> listItems;
+	
+	std::vector<const CRmgTemplate *> curItems;
+	
+};
+
+class TeamAlignmentsWidget: public InterfaceObjectConfigurable
+{
+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>> players;
+	std::vector<std::shared_ptr<CIntObject>> placeholders;
 };

+ 17 - 4
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();
 }
 
@@ -449,6 +452,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),
@@ -566,16 +574,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);

+ 4 - 2
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
@@ -256,8 +257,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);
 

+ 1 - 1
client/widgets/Images.h

@@ -56,7 +56,7 @@ public:
 };
 
 /// area filled with specific texture
-class CFilledTexture : CIntObject
+class CFilledTexture : public CIntObject
 {
 	SDL_Surface * texture;
 

+ 2 - 2
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" :
   {

+ 10 - 0
config/translate.json

@@ -113,5 +113,15 @@
 			"label" : "Hide complete quests",
 			"help" : "Hide all quests that already completed"
 		}
+	},
+	"randomMapTab":
+	{
+		"widgets":
+		{
+			"defaultTemplate": "default",
+			"templateLabel": "Template",
+			"teamAlignmentsButton": "Setup...",
+			"teamAlignmentsLabel": "Team alignments"
+		}
 	}
 }

+ 510 - 0
config/widgets/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": "setMonsterStrength"
+		},
+
+		{
+			"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
+				}
+			]
+		}
+	]
+}

+ 4 - 2
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;
 	}

+ 10 - 1
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;

+ 3 - 0
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();

+ 12 - 0
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";
 	}

+ 58 - 4
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))
@@ -204,8 +207,50 @@ 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::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)
@@ -391,7 +436,6 @@ PlayerColor CMapGenOptions::getNextPlayerColor() const
 
 bool CMapGenOptions::checkOptions() const
 {
-	assert(countHumanPlayers() > 0);
 	if(mapTemplate)
 	{
 		return true;
@@ -452,7 +496,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)
 {
 
 }
@@ -494,4 +538,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

+ 31 - 27
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
@@ -76,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;
@@ -84,6 +63,7 @@ public:
 		PlayerColor color;
 		si32 startingTown;
 		EPlayerType::EPlayerType playerType;
+		TeamID team;
 
 	public:
 		template <typename Handler>
@@ -92,6 +72,8 @@ public:
 			h & color;
 			h & startingTown;
 			h & playerType;
+			if(version >= 806)
+				h & team;
 		}
 	};
 
@@ -130,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.
@@ -138,11 +123,14 @@ 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.
 	const CRmgTemplate * getMapTemplate() const;
 	void setMapTemplate(const CRmgTemplate * value);
+	void setMapTemplate(const std::string & name);
 
 	std::vector<const CRmgTemplate *> getPossibleTemplates() const;
 
@@ -171,6 +159,8 @@ private:
 	EWaterContent::EWaterContent waterContent;
 	EMonsterStrength::EMonsterStrength monsterStrength;
 	std::map<PlayerColor, CPlayerSettings> players;
+	std::set<std::string> disabledRoads;
+	
 	const CRmgTemplate * mapTemplate;
 
 public:
@@ -187,7 +177,21 @@ 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 >= 806)
+		{
+			h & templateName;
+			if(!h.saving)
+			{
+				setMapTemplate(templateName);
+			}
+			
+			h & disabledRoads;
+		}
 	}
 };
 

+ 21 - 8
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
@@ -183,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;
@@ -238,19 +244,26 @@ 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();
 		}
-		auto itTeam = RandomGeneratorUtil::nextItem(teamNumbers[j], rand);
-		player.team = TeamID(*itTeam);
-		teamNumbers[j].erase(itTeam);
+		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);
+		}
+		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()

+ 16 - 1
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));

+ 28 - 1
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;
 

+ 2 - 1
lib/rmg/CRmgTemplateStorage.cpp

@@ -32,8 +32,9 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 	{
 		JsonDeserializer handler(nullptr, data);
 		auto fullKey = normalizeIdentifier(scope, CModHandler::scopeBuiltin(), 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)

+ 1 - 0
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"

+ 1 - 0
lib/rmg/Functions.h

@@ -19,6 +19,7 @@ class RmgMap;
 class ObjectManager;
 class ObjectTemplate;
 class CMapGenerator;
+class CRandomGenerator;
 
 class rmgException : public std::exception
 {

+ 9 - 2
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,11 +71,15 @@ 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());
-	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);
 }
 

+ 1 - 1
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";