浏览代码

Merge pull request #2570 from Laserlicht/town_selection_window

Town selection window
Nordsoft91 2 年之前
父节点
当前提交
ef4fddd0c4

二进制
Mods/vcmi/Data/lobby/townBorderBig.png


二进制
Mods/vcmi/Data/lobby/townBorderBigActivated.png


二进制
Mods/vcmi/Data/lobby/townBorderSmallActivated.png


+ 2 - 2
client/CServerHandler.cpp

@@ -459,11 +459,11 @@ void CServerHandler::setPlayer(PlayerColor color) const
 	sendLobbyPack(lsp);
 	sendLobbyPack(lsp);
 }
 }
 
 
-void CServerHandler::setPlayerOption(ui8 what, si8 dir, PlayerColor player) const
+void CServerHandler::setPlayerOption(ui8 what, int32_t value, PlayerColor player) const
 {
 {
 	LobbyChangePlayerOption lcpo;
 	LobbyChangePlayerOption lcpo;
 	lcpo.what = what;
 	lcpo.what = what;
-	lcpo.direction = dir;
+	lcpo.value = value;
 	lcpo.color = player;
 	lcpo.color = player;
 	sendLobbyPack(lcpo);
 	sendLobbyPack(lcpo);
 }
 }

+ 2 - 2
client/CServerHandler.h

@@ -62,7 +62,7 @@ public:
 	virtual void setCampaignBonus(int bonusId) const = 0;
 	virtual void setCampaignBonus(int bonusId) const = 0;
 	virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
 	virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
 	virtual void setPlayer(PlayerColor color) const = 0;
 	virtual void setPlayer(PlayerColor color) const = 0;
-	virtual void setPlayerOption(ui8 what, si8 dir, PlayerColor player) const = 0;
+	virtual void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const = 0;
 	virtual void setDifficulty(int to) const = 0;
 	virtual void setDifficulty(int to) const = 0;
 	virtual void setTurnLength(int npos) const = 0;
 	virtual void setTurnLength(int npos) const = 0;
 	virtual void sendMessage(const std::string & txt) const = 0;
 	virtual void sendMessage(const std::string & txt) const = 0;
@@ -144,7 +144,7 @@ public:
 	void setCampaignBonus(int bonusId) const override;
 	void setCampaignBonus(int bonusId) const override;
 	void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
 	void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
 	void setPlayer(PlayerColor color) const override;
 	void setPlayer(PlayerColor color) const override;
-	void setPlayerOption(ui8 what, si8 dir, PlayerColor player) const override;
+	void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const override;
 	void setDifficulty(int to) const override;
 	void setDifficulty(int to) const override;
 	void setTurnLength(int npos) const override;
 	void setTurnLength(int npos) const override;
 	void sendMessage(const std::string & txt) const override;
 	void sendMessage(const std::string & txt) const override;

+ 375 - 9
client/lobby/OptionsTab.cpp

@@ -14,18 +14,22 @@
 
 
 #include "../CGameInfo.h"
 #include "../CGameInfo.h"
 #include "../CServerHandler.h"
 #include "../CServerHandler.h"
+#include "../CMusicHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../render/Graphics.h"
 #include "../render/Graphics.h"
 #include "../render/IFont.h"
 #include "../render/IFont.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Buttons.h"
+#include "../widgets/Images.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/ObjectLists.h"
 #include "../widgets/Slider.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/TextControls.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
 #include "../windows/InfoWindows.h"
+#include "../eventsSDL/InputHandler.h"
 
 
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -79,7 +83,7 @@ void OptionsTab::recreate()
 	}
 	}
 }
 }
 
 
-size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
+size_t OptionsTab::CPlayerSettingsHelper::getImageIndex(bool big)
 {
 {
 	enum EBonusSelection //frames of bonuses file
 	enum EBonusSelection //frames of bonuses file
 	{
 	{
@@ -103,7 +107,7 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 		case PlayerSettings::RANDOM:
 		case PlayerSettings::RANDOM:
 			return TOWN_RANDOM;
 			return TOWN_RANDOM;
 		default:
 		default:
-			return (*CGI->townh)[factionIndex]->town->clientInfo.icons[true][false] + 2;
+			return (*CGI->townh)[factionIndex]->town->clientInfo.icons[true][false] + (big ? 0 : 2);
 		}
 		}
 	case HERO:
 	case HERO:
 		switch(settings.hero)
 		switch(settings.hero)
@@ -160,14 +164,14 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
 	return 0;
 	return 0;
 }
 }
 
 
-std::string OptionsTab::CPlayerSettingsHelper::getImageName()
+std::string OptionsTab::CPlayerSettingsHelper::getImageName(bool big)
 {
 {
 	switch(type)
 	switch(type)
 	{
 	{
 	case OptionsTab::TOWN:
 	case OptionsTab::TOWN:
-		return "ITPA";
+		return big ? "ITPt": "ITPA";
 	case OptionsTab::HERO:
 	case OptionsTab::HERO:
-		return "PortraitsSmall";
+		return big ? "PortraitsLarge": "PortraitsSmall";
 	case OptionsTab::BONUS:
 	case OptionsTab::BONUS:
 		return "SCNRSTAR";
 		return "SCNRSTAR";
 	}
 	}
@@ -414,8 +418,352 @@ void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
 	textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 	textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 }
 }
 
 
+OptionsTab::SelectionWindow::SelectionWindow(PlayerColor _color, SelType _type)
+	: CWindowObject(BORDERED)
+{
+	addUsedEvents(LCLICK | SHOW_POPUP);
+
+	color = _color;
+	type = _type;
+
+	initialFaction = SEL->getStartInfo()->playerInfos.find(color)->second.castle;
+	initialHero = SEL->getStartInfo()->playerInfos.find(color)->second.hero;
+	initialBonus = SEL->getStartInfo()->playerInfos.find(color)->second.bonus;
+	selectedFaction = initialFaction;
+	selectedHero = initialHero;
+	selectedBonus = initialBonus;
+	allowedFactions = SEL->getPlayerInfo(color.getNum()).allowedFactions;
+	std::vector<bool> allowedHeroesFlag = SEL->getMapInfo()->mapHeader->allowedHeroes;
+	for(int i = 0; i < allowedHeroesFlag.size(); i++)
+		if(allowedHeroesFlag[i])
+			allowedHeroes.insert(HeroTypeID(i));
+
+	allowedBonus.push_back(-1); // random
+	if(initialHero >= -1)
+		allowedBonus.push_back(0); // artifact
+	allowedBonus.push_back(1); // gold
+	if(initialFaction >= 0)
+		allowedBonus.push_back(2); // resource
+
+	recreate();
+}
+
+int OptionsTab::SelectionWindow::calcLines(FactionID faction)
+{
+	double additionalItems = 1; // random
+
+	if(faction < 0)
+		return std::ceil(((double)allowedFactions.size() + additionalItems) / elementsPerLine);
+
+	int count = 0;
+	for(auto & elemh : allowedHeroes)
+	{
+		CHero * type = VLC->heroh->objects[elemh];
+		if(type->heroClass->faction == faction)
+			count++;
+	}
+
+	return std::ceil(std::max((double)count + additionalItems, (double)allowedFactions.size() + additionalItems) / (double)elementsPerLine);
+}
+
+void OptionsTab::SelectionWindow::apply()
+{
+	if(GH.windows().isTopWindow(this))
+	{
+		GH.input().hapticFeedback();
+		CCS->soundh->playSound(soundBase::button);
+
+		close();
+
+		setSelection();
+	}
+}
+
+void OptionsTab::SelectionWindow::setSelection()
+{
+	if(selectedFaction != initialFaction)
+		CSH->setPlayerOption(LobbyChangePlayerOption::TOWN_ID, selectedFaction, color);
+
+	if(selectedHero != initialHero)
+		CSH->setPlayerOption(LobbyChangePlayerOption::HERO_ID, selectedHero, color);
+
+	if(selectedBonus != initialBonus)
+		CSH->setPlayerOption(LobbyChangePlayerOption::BONUS_ID, selectedBonus, color);
+}
+
+void OptionsTab::SelectionWindow::recreate()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	int amountLines = 1;
+	if(type == SelType::BONUS)
+		elementsPerLine = allowedBonus.size();
+	else
+	{
+		// try to make squarish
+		if(type == SelType::TOWN)
+			elementsPerLine = floor(sqrt(allowedFactions.size()));
+		if(type == SelType::HERO)
+		{
+			int count = 0;
+			for(auto & elem : allowedHeroes)
+			{
+				CHero * type = VLC->heroh->objects[elem];
+				if(type->heroClass->faction == selectedFaction)
+				{
+					count++;
+				}
+			}
+			elementsPerLine = floor(sqrt(count));
+		}
+
+		amountLines = calcLines((type > SelType::TOWN) ? selectedFaction : static_cast<FactionID>(PlayerSettings::RANDOM));
+	}
+
+	int x = (elementsPerLine) * (ICON_BIG_WIDTH-1);
+	int y = (amountLines) * (ICON_BIG_HEIGHT-1);
+
+	pos = Rect(0, 0, x, y);
+
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>("DiBoxBck", pos);
+	backgroundTexture->playerColored(PlayerColor(1));
+	updateShadow();
+
+	if(type == SelType::TOWN)
+		genContentFactions();
+	if(type == SelType::HERO)
+		genContentHeroes();
+	if(type == SelType::BONUS)
+		genContentBonus();
+	genContentGrid(amountLines);
+
+	center();
+}
+
+void OptionsTab::SelectionWindow::drawOutlinedText(int x, int y, ColorRGBA color, std::string text)
+{
+	components.push_back(std::make_shared<CLabel>(x-1, y, FONT_TINY, ETextAlignment::CENTER, Colors::BLACK, text));
+	components.push_back(std::make_shared<CLabel>(x+1, y, FONT_TINY, ETextAlignment::CENTER, Colors::BLACK, text));
+	components.push_back(std::make_shared<CLabel>(x, y-1, FONT_TINY, ETextAlignment::CENTER, Colors::BLACK, text));
+	components.push_back(std::make_shared<CLabel>(x, y+1, FONT_TINY, ETextAlignment::CENTER, Colors::BLACK, text));
+	components.push_back(std::make_shared<CLabel>(x, y, FONT_TINY, ETextAlignment::CENTER, color, text));
+}
+
+void OptionsTab::SelectionWindow::genContentGrid(int lines)
+{
+	for(int y = 0; y < lines; y++)
+	{
+		for(int x = 0; x < elementsPerLine; x++)
+		{
+			components.push_back(std::make_shared<CPicture>("lobby/townBorderBig", x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+		}
+	}
+}
+
+void OptionsTab::SelectionWindow::genContentFactions()
+{
+	int i = 1;
+
+	// random
+	PlayerSettings set = PlayerSettings();
+	set.castle = PlayerSettings::RANDOM;
+	CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
+	components.push_back(std::make_shared<CAnimImage>(helper.getImageName(), helper.getImageIndex(), 0, 6, (ICON_SMALL_HEIGHT/2)));
+	drawOutlinedText(TEXT_POS_X, TEXT_POS_Y, (selectedFaction == PlayerSettings::RANDOM) ? Colors::YELLOW : Colors::WHITE, helper.getName());
+	if(selectedFaction == PlayerSettings::RANDOM)
+		components.push_back(std::make_shared<CPicture>("lobby/townBorderSmallActivated", 6, (ICON_SMALL_HEIGHT/2)));
+
+	for(auto & elem : allowedFactions)
+	{
+		int x = i % elementsPerLine;
+		int y = i / elementsPerLine;
+
+		PlayerSettings set = PlayerSettings();
+		set.castle = elem;
+
+		CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
+
+		components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+		components.push_back(std::make_shared<CPicture>(selectedFaction == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig", x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedFaction == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
+		factions.push_back(elem);
+
+		i++;
+	}
+}
+
+void OptionsTab::SelectionWindow::genContentHeroes()
+{
+	int i = 1;
+
+	// random
+	PlayerSettings set = PlayerSettings();
+	set.hero = PlayerSettings::RANDOM;
+	CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
+	components.push_back(std::make_shared<CAnimImage>(helper.getImageName(), helper.getImageIndex(), 0, 6, (ICON_SMALL_HEIGHT/2)));
+	drawOutlinedText(TEXT_POS_X, TEXT_POS_Y, (selectedHero == PlayerSettings::RANDOM) ? Colors::YELLOW : Colors::WHITE, helper.getName());
+	if(selectedHero == PlayerSettings::RANDOM)
+		components.push_back(std::make_shared<CPicture>("lobby/townBorderSmallActivated", 6, (ICON_SMALL_HEIGHT/2)));
+
+	for(auto & elem : allowedHeroes)
+	{
+		CHero * type = VLC->heroh->objects[elem];
+
+		if(type->heroClass->faction == selectedFaction)
+		{
+
+			int x = i % elementsPerLine;
+			int y = i / elementsPerLine;
+
+			PlayerSettings set = PlayerSettings();
+			set.hero = elem;
+
+			CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
+
+			components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+			components.push_back(std::make_shared<CPicture>(selectedHero == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig", x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+			drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedHero == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
+			heroes.push_back(elem);
+
+			i++;
+		}
+	}
+}
+
+void OptionsTab::SelectionWindow::genContentBonus()
+{
+	PlayerSettings set = PlayerSettings();
+
+	int i = 0;
+	for(auto elem : allowedBonus)
+	{
+		int x = i;
+		int y = 0;
+
+		set.bonus = static_cast<PlayerSettings::Ebonus>(elem);
+		CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::BONUS);
+		components.push_back(std::make_shared<CAnimImage>(helper.getImageName(), helper.getImageIndex(), 0, x * (ICON_BIG_WIDTH-1) + 6, y * (ICON_BIG_HEIGHT-1) + (ICON_SMALL_HEIGHT/2)));
+		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, Colors::WHITE , helper.getName());
+		if(selectedBonus == elem)
+		{
+			components.push_back(std::make_shared<CPicture>("lobby/townBorderSmallActivated", x * (ICON_BIG_WIDTH-1) + 6, y * (ICON_BIG_HEIGHT-1) + (ICON_SMALL_HEIGHT/2)));
+			drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, Colors::YELLOW , helper.getName());
+		}
+
+		i++;
+	}
+}
+
+int OptionsTab::SelectionWindow::getElement(const Point & cursorPosition)
+{
+	int x = (cursorPosition.x - pos.x) / (ICON_BIG_WIDTH-1);
+	int y = (cursorPosition.y - pos.y) / (ICON_BIG_HEIGHT-1);
+
+	return x + y * elementsPerLine;
+}
+
+void OptionsTab::SelectionWindow::setElement(int elem, bool doApply)
+{
+	PlayerSettings set = PlayerSettings();
+	if(type == SelType::TOWN)
+	{
+		if(elem > 0)
+		{
+			elem--;
+			if(elem >= factions.size())
+				return;
+			set.castle = factions[elem];
+		}
+		else
+		{
+			set.castle = PlayerSettings::RANDOM;
+		}
+		if(set.castle != PlayerSettings::NONE)
+		{
+			if(!doApply)
+			{
+				CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
+				GH.windows().createAndPushWindow<CPlayerOptionTooltipBox>(helper);
+			}
+			else
+				selectedFaction = set.castle;
+		}
+	}
+	if(type == SelType::HERO)
+	{
+		if(elem > 0)
+		{
+			elem--;
+			if(elem >= heroes.size())
+				return;
+			set.hero = heroes[elem];
+		}
+		else
+		{
+			set.hero = PlayerSettings::RANDOM;
+		}
+		if(set.hero != PlayerSettings::NONE)
+		{
+			if(!doApply)
+			{
+				CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::HERO);
+				GH.windows().createAndPushWindow<CPlayerOptionTooltipBox>(helper);
+			}
+			else
+				selectedHero = set.hero;
+		}
+	}
+	if(type == SelType::BONUS)
+	{
+		if(elem >= 4)
+			return;
+		set.bonus = static_cast<PlayerSettings::Ebonus>(elem-1);
+		if(set.bonus != PlayerSettings::NONE)
+		{
+			if(!doApply)
+			{
+				CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::BONUS);
+				GH.windows().createAndPushWindow<CPlayerOptionTooltipBox>(helper);
+			}
+			else
+				selectedBonus = set.bonus;
+		}
+	}
+
+	if(doApply)
+		apply();
+}
+
+bool OptionsTab::SelectionWindow::receiveEvent(const Point & position, int eventType) const
+{
+	return true;  // capture click also outside of window
+}
+
+void OptionsTab::SelectionWindow::clickReleased(const Point & cursorPosition)
+{
+	if(!pos.isInside(cursorPosition))
+	{
+		close();
+		return;
+	}
+
+	int elem = getElement(cursorPosition);
+
+	setElement(elem, true);
+}
+
+void OptionsTab::SelectionWindow::showPopupWindow(const Point & cursorPosition)
+{
+	if(!pos.isInside(cursorPosition))
+		return;
+
+	int elem = getElement(cursorPosition);
+
+	setElement(elem, false);
+}
+
 OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
 OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
-	: Scrollable(SHOW_POPUP, position, Orientation::HORIZONTAL)
+	: Scrollable(LCLICK | SHOW_POPUP, position, Orientation::HORIZONTAL)
 	, CPlayerSettingsHelper(settings, type)
 	, CPlayerSettingsHelper(settings, type)
 {
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@@ -437,14 +785,32 @@ void OptionsTab::SelectedBox::update()
 void OptionsTab::SelectedBox::showPopupWindow(const Point & cursorPosition)
 void OptionsTab::SelectedBox::showPopupWindow(const Point & cursorPosition)
 {
 {
 	// cases when we do not need to display a message
 	// cases when we do not need to display a message
-	if(settings.castle == -2 && CPlayerSettingsHelper::type == TOWN)
+	if(settings.castle == PlayerSettings::NONE && CPlayerSettingsHelper::type == TOWN)
 		return;
 		return;
-	if(settings.hero == -2 && !SEL->getPlayerInfo(settings.color.getNum()).hasCustomMainHero() && CPlayerSettingsHelper::type == HERO)
+	if(settings.hero == PlayerSettings::NONE && !SEL->getPlayerInfo(settings.color.getNum()).hasCustomMainHero() && CPlayerSettingsHelper::type == HERO)
 		return;
 		return;
 
 
 	GH.windows().createAndPushWindow<CPlayerOptionTooltipBox>(*this);
 	GH.windows().createAndPushWindow<CPlayerOptionTooltipBox>(*this);
 }
 }
 
 
+void OptionsTab::SelectedBox::clickReleased(const Point & cursorPosition)
+{
+	PlayerInfo pi = SEL->getPlayerInfo(settings.color.getNum());
+	const bool foreignPlayer = CSH->isGuest() && !CSH->isMyColor(settings.color);
+
+	if(type == SelType::TOWN && ((pi.allowedFactions.size() < 2 && !pi.isFactionRandom) || foreignPlayer))
+		return;
+
+	if(type == SelType::HERO && ((pi.defaultHero() != -1 || settings.castle < 0) || foreignPlayer))
+		return;
+
+	if(type == SelType::BONUS && foreignPlayer)
+		return;
+
+	GH.input().hapticFeedback();
+	GH.windows().createAndPushWindow<SelectionWindow>(settings.color, type);
+}
+
 void OptionsTab::SelectedBox::scrollBy(int distance)
 void OptionsTab::SelectedBox::scrollBy(int distance)
 {
 {
 	// FIXME: currently options tab is completely recreacted from scratch whenever we receive any information from server
 	// FIXME: currently options tab is completely recreacted from scratch whenever we receive any information from server
@@ -589,4 +955,4 @@ void OptionsTab::PlayerOptionsEntry::hideUnavailableButtons()
 		buttonBonusLeft->enable();
 		buttonBonusLeft->enable();
 		buttonBonusRight->enable();
 		buttonBonusRight->enable();
 	}
 	}
-}
+}

+ 57 - 2
client/lobby/OptionsTab.h

@@ -27,6 +27,8 @@ class CComponentBox;
 class CTextBox;
 class CTextBox;
 class CButton;
 class CButton;
 
 
+class FilledTexturePlayerColored;
+
 /// The options tab which is shown at the map selection phase.
 /// The options tab which is shown at the map selection phase.
 class OptionsTab : public CIntObject
 class OptionsTab : public CIntObject
 {
 {
@@ -60,8 +62,8 @@ public:
 		{}
 		{}
 
 
 		/// visible image settings
 		/// visible image settings
-		size_t getImageIndex();
-		std::string getImageName();
+		size_t getImageIndex(bool big = false);
+		std::string getImageName(bool big = false);
 
 
 		std::string getName(); /// name visible in options dialog
 		std::string getName(); /// name visible in options dialog
 		std::string getTitle(); /// title in popup box
 		std::string getTitle(); /// title in popup box
@@ -94,6 +96,58 @@ public:
 		CPlayerOptionTooltipBox(CPlayerSettingsHelper & helper);
 		CPlayerOptionTooltipBox(CPlayerSettingsHelper & helper);
 	};
 	};
 
 
+	class SelectionWindow : public CWindowObject
+	{
+		//const int ICON_SMALL_WIDTH = 48;
+		const int ICON_SMALL_HEIGHT = 32;
+		const int ICON_BIG_WIDTH = 58;
+		const int ICON_BIG_HEIGHT = 64;
+		const int TEXT_POS_X = 29;
+		const int TEXT_POS_Y = 56;
+
+		int elementsPerLine;
+
+		PlayerColor color;
+		SelType type;
+
+		std::shared_ptr<FilledTexturePlayerColored> backgroundTexture;
+		std::vector<std::shared_ptr<CIntObject>> components;
+
+		std::vector<FactionID> factions;
+		std::vector<HeroTypeID> heroes;
+
+		FactionID initialFaction;
+		HeroTypeID initialHero;
+		int initialBonus;
+		FactionID selectedFaction;
+		HeroTypeID selectedHero;
+		int selectedBonus;
+
+		std::set<FactionID> allowedFactions;
+		std::set<HeroTypeID> allowedHeroes;
+		std::vector<int> allowedBonus;
+
+		void genContentGrid(int lines);
+		void genContentFactions();
+		void genContentHeroes();
+		void genContentBonus();
+
+		void drawOutlinedText(int x, int y, ColorRGBA color, std::string text);
+		int calcLines(FactionID faction);
+		void apply();
+		void recreate();
+		void setSelection();
+		int getElement(const Point & cursorPosition);
+		void setElement(int element, bool doApply);
+
+		bool receiveEvent(const Point & position, int eventType) const override;
+		void clickReleased(const Point & cursorPosition) override;
+		void showPopupWindow(const Point & cursorPosition) override;
+
+	public:
+		SelectionWindow(PlayerColor _color, SelType _type);
+	};
+
 	/// Image with current town/hero/bonus
 	/// Image with current town/hero/bonus
 	struct SelectedBox : public Scrollable, public CPlayerSettingsHelper
 	struct SelectedBox : public Scrollable, public CPlayerSettingsHelper
 	{
 	{
@@ -102,6 +156,7 @@ public:
 
 
 		SelectedBox(Point position, PlayerSettings & settings, SelType type);
 		SelectedBox(Point position, PlayerSettings & settings, SelType type);
 		void showPopupWindow(const Point & cursorPosition) override;
 		void showPopupWindow(const Point & cursorPosition) override;
+		void clickReleased(const Point & cursorPosition) override;
 		void scrollBy(int distance) override;
 		void scrollBy(int distance) override;
 
 
 		void update();
 		void update();

+ 3 - 3
lib/NetPacksLobby.h

@@ -211,9 +211,9 @@ struct DLL_LINKAGE LobbySetCampaignBonus : public CLobbyPackToServer
 
 
 struct DLL_LINKAGE LobbyChangePlayerOption : public CLobbyPackToServer
 struct DLL_LINKAGE LobbyChangePlayerOption : public CLobbyPackToServer
 {
 {
-	enum EWhat : ui8 {UNKNOWN, TOWN, HERO, BONUS};
+	enum EWhat : ui8 {UNKNOWN, TOWN, HERO, BONUS, TOWN_ID, HERO_ID, BONUS_ID};
 	ui8 what = UNKNOWN;
 	ui8 what = UNKNOWN;
-	si8 direction = 0; //-1 or +1
+	int32_t value = 0;
 	PlayerColor color = PlayerColor::CANNOT_DETERMINE;
 	PlayerColor color = PlayerColor::CANNOT_DETERMINE;
 
 
 	virtual void visitTyped(ICPackVisitor & visitor) override;
 	virtual void visitTyped(ICPackVisitor & visitor) override;
@@ -221,7 +221,7 @@ struct DLL_LINKAGE LobbyChangePlayerOption : public CLobbyPackToServer
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
 		h & what;
 		h & what;
-		h & direction;
+		h & value;
 		h & color;
 		h & color;
 	}
 	}
 };
 };

+ 56 - 0
server/CVCMIServer.cpp

@@ -843,6 +843,28 @@ void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
 		s.bonus = PlayerSettings::RANDOM;
 		s.bonus = PlayerSettings::RANDOM;
 }
 }
 
 
+void CVCMIServer::optionSetCastle(PlayerColor player, int id)
+{
+	PlayerSettings & s = si->playerInfos[player];
+	FactionID & cur = s.castle;
+	auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;
+
+	if(cur == PlayerSettings::NONE) //no change
+		return;
+
+	if(allowed.find(static_cast<FactionID>(id)) == allowed.end() && id != PlayerSettings::RANDOM) // valid id
+		return;
+
+	cur = static_cast<FactionID>(id);
+
+	if(s.hero >= 0 && !getPlayerInfo(player.getNum()).hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
+	{
+		s.hero = PlayerSettings::RANDOM;
+	}
+	if(cur < 0 && s.bonus == PlayerSettings::RESOURCE)
+		s.bonus = PlayerSettings::RANDOM;
+}
+
 void CVCMIServer::setCampaignMap(CampaignScenarioID mapId)
 void CVCMIServer::setCampaignMap(CampaignScenarioID mapId)
 {
 {
 	campaignMap = mapId;
 	campaignMap = mapId;
@@ -890,6 +912,20 @@ void CVCMIServer::optionNextHero(PlayerColor player, int dir)
 	}
 	}
 }
 }
 
 
+void CVCMIServer::optionSetHero(PlayerColor player, int id)
+{
+	PlayerSettings & s = si->playerInfos[player];
+	if(s.castle < 0 || s.hero == PlayerSettings::NONE)
+		return;
+
+	if(id == PlayerSettings::RANDOM)
+	{
+		s.hero = PlayerSettings::RANDOM;
+	}
+	if(canUseThisHero(player, id))
+		s.hero = static_cast<HeroTypeID>(id);
+}
+
 int CVCMIServer::nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir)
 int CVCMIServer::nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir)
 {
 {
 	if(dir > 0)
 	if(dir > 0)
@@ -936,6 +972,26 @@ void CVCMIServer::optionNextBonus(PlayerColor player, int dir)
 	}
 	}
 }
 }
 
 
+void CVCMIServer::optionSetBonus(PlayerColor player, int id)
+{
+	PlayerSettings & s = si->playerInfos[player];
+
+	if(s.hero == PlayerSettings::NONE &&
+		!getPlayerInfo(player.getNum()).heroesNames.size() &&
+		id == PlayerSettings::ARTIFACT) //no hero - can't be artifact
+			return;
+
+	if(id > PlayerSettings::RESOURCE)
+		return;
+	if(id < PlayerSettings::RANDOM)
+		return;
+
+	if(s.castle == PlayerSettings::RANDOM && id == PlayerSettings::RESOURCE) //random castle - can't be resource
+		return;
+
+	s.bonus = static_cast<PlayerSettings::Ebonus>(static_cast<int>(id));
+}
+
 bool CVCMIServer::canUseThisHero(PlayerColor player, int ID)
 bool CVCMIServer::canUseThisHero(PlayerColor player, int ID)
 {
 {
 	return VLC->heroh->size() > ID
 	return VLC->heroh->size() > ID

+ 3 - 0
server/CVCMIServer.h

@@ -104,11 +104,14 @@ public:
 	// Work with LobbyInfo
 	// Work with LobbyInfo
 	void setPlayer(PlayerColor clickedColor);
 	void setPlayer(PlayerColor clickedColor);
 	void optionNextHero(PlayerColor player, int dir); //dir == -1 or +1
 	void optionNextHero(PlayerColor player, int dir); //dir == -1 or +1
+	void optionSetHero(PlayerColor player, int id);
 	int nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir);
 	int nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir);
 	bool canUseThisHero(PlayerColor player, int ID);
 	bool canUseThisHero(PlayerColor player, int ID);
 	std::vector<int> getUsedHeroes();
 	std::vector<int> getUsedHeroes();
 	void optionNextBonus(PlayerColor player, int dir); //dir == -1 or +1
 	void optionNextBonus(PlayerColor player, int dir); //dir == -1 or +1
+	void optionSetBonus(PlayerColor player, int id);
 	void optionNextCastle(PlayerColor player, int dir); //dir == -1 or +
 	void optionNextCastle(PlayerColor player, int dir); //dir == -1 or +
+	void optionSetCastle(PlayerColor player, int id);
 
 
 	// Campaigns
 	// Campaigns
 	void setCampaignMap(CampaignScenarioID mapId);
 	void setCampaignMap(CampaignScenarioID mapId);

+ 12 - 3
server/NetPacksLobbyServer.cpp

@@ -360,14 +360,23 @@ void ApplyOnServerNetPackVisitor::visitLobbyChangePlayerOption(LobbyChangePlayer
 {
 {
 	switch(pack.what)
 	switch(pack.what)
 	{
 	{
+	case LobbyChangePlayerOption::TOWN_ID:
+		srv.optionSetCastle(pack.color, pack.value);
+		break;
 	case LobbyChangePlayerOption::TOWN:
 	case LobbyChangePlayerOption::TOWN:
-		srv.optionNextCastle(pack.color, pack.direction);
+		srv.optionNextCastle(pack.color, pack.value);
+		break;
+	case LobbyChangePlayerOption::HERO_ID:
+		srv.optionSetHero(pack.color, pack.value);
 		break;
 		break;
 	case LobbyChangePlayerOption::HERO:
 	case LobbyChangePlayerOption::HERO:
-		srv.optionNextHero(pack.color, pack.direction);
+		srv.optionNextHero(pack.color, pack.value);
+		break;
+	case LobbyChangePlayerOption::BONUS_ID:
+		srv.optionSetBonus(pack.color, pack.value);
 		break;
 		break;
 	case LobbyChangePlayerOption::BONUS:
 	case LobbyChangePlayerOption::BONUS:
-		srv.optionNextBonus(pack.color, pack.direction);
+		srv.optionNextBonus(pack.color, pack.value);
 		break;
 		break;
 	}
 	}