← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/multiplayer_dropdowns into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/multiplayer_dropdowns into lp:widelands with lp:~widelands-dev/widelands/multiplayer_dropdowns_2_init_team as a prerequisite.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1407396 in widelands: "MP Game Setup - UI errors"
  https://bugs.launchpad.net/widelands/+bug/1407396

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/multiplayer_dropdowns/+merge/326302

This is the final branch in a 3-branch series to change all buttons in multiplayer setup to dropdown menus.

All BUGFIXING and TESTING is to be done ON THIS BRANCH.

I split this into 3 branches to try to make the code review diffs more manageable.

The interaction is complex, so this will need thorough testing. I tried to click on all possible combinations, but I might have missed some.

1. The dropdowns should dynamically change their contents to only contain usable entries
2. Server / client should always be in sync (apart from delays for processing everything)
3. No missing information
4. No crashes
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/multiplayer_dropdowns into lp:widelands.
=== added file 'data/images/players/no_team.png'
Binary files data/images/players/no_team.png	1970-01-01 00:00:00 +0000 and data/images/players/no_team.png	2017-06-26 14:45:57 +0000 differ
=== added file 'data/images/players/team.png'
Binary files data/images/players/team.png	1970-01-01 00:00:00 +0000 and data/images/players/team.png	2017-06-26 14:45:57 +0000 differ
=== added file 'data/images/players/team_pc.png'
Binary files data/images/players/team_pc.png	1970-01-01 00:00:00 +0000 and data/images/players/team_pc.png	2017-06-26 14:45:57 +0000 differ
=== modified file 'src/ai/computer_player.cc'
--- src/ai/computer_player.cc	2017-01-25 18:55:59 +0000
+++ src/ai/computer_player.cc	2017-06-26 14:45:57 +0000
@@ -36,12 +36,13 @@
 	}
 
 	struct EmptyAIImpl : Implementation {
-		EmptyAIImpl() {
-			name = "empty";
-			/** TRANSLATORS: This is the name of an AI used in the game setup screens */
-			descname = _("No AI");
-			icon_filename = "images/ai/ai_empty.png";
-			type = Implementation::Type::kEmpty;
+		EmptyAIImpl()
+		   : Implementation(
+		        "empty",
+		        /** TRANSLATORS: This is the name of an AI used in the game setup screens */
+		        _("No AI"),
+		        "images/ai/ai_empty.png",
+		        Implementation::Type::kEmpty) {
 		}
 		ComputerPlayer* instantiate(Widelands::Game& g,
 		                            Widelands::PlayerNumber const pid) const override {

=== modified file 'src/ai/computer_player.h'
--- src/ai/computer_player.h	2017-04-22 08:02:21 +0000
+++ src/ai/computer_player.h	2017-06-26 14:45:57 +0000
@@ -23,9 +23,15 @@
 #include <string>
 #include <vector>
 
+#include <boost/algorithm/string/predicate.hpp>
+
 #include "base/macros.h"
 #include "logic/widelands.h"
 
+// We need to use a string prefix in the game setup screens to identify the AIs, so we make sure
+// that the AI names don't contain the separator that's used to parse the strings there.
+#define AI_NAME_SEPARATOR "|"
+
 namespace Widelands {
 class Game;
 }  // namespace Widelands
@@ -61,6 +67,17 @@
 		std::string descname;
 		std::string icon_filename;
 		Type type;
+		explicit Implementation(std::string init_name,
+		                        std::string init_descname,
+		                        std::string init_icon_filename,
+		                        Type init_type)
+		   : name(init_name),
+		     descname(init_descname),
+		     icon_filename(init_icon_filename),
+		     type(init_type) {
+			assert(!boost::contains(name, AI_NAME_SEPARATOR));
+		}
+
 		virtual ~Implementation() {
 		}
 		virtual ComputerPlayer* instantiate(Widelands::Game&, Widelands::PlayerNumber) const = 0;

=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h	2017-01-25 18:55:59 +0000
+++ src/ai/defaultai.h	2017-06-26 14:45:57 +0000
@@ -96,12 +96,13 @@
 
 	/// Implementation for Strong
 	struct NormalImpl : public ComputerPlayer::Implementation {
-		NormalImpl() {
-			name = "normal";
-			/** TRANSLATORS: This is the name of an AI used in the game setup screens */
-			descname = _("Normal AI");
-			icon_filename = "images/ai/ai_normal.png";
-			type = Implementation::Type::kDefault;
+		NormalImpl()
+		   : Implementation(
+		        "normal",
+		        /** TRANSLATORS: This is the name of an AI used in the game setup screens */
+		        _("Normal AI"),
+		        "images/ai/ai_normal.png",
+		        Implementation::Type::kDefault) {
 		}
 		ComputerPlayer* instantiate(Widelands::Game& game,
 		                            Widelands::PlayerNumber const p) const override {
@@ -110,12 +111,13 @@
 	};
 
 	struct WeakImpl : public ComputerPlayer::Implementation {
-		WeakImpl() {
-			name = "weak";
-			/** TRANSLATORS: This is the name of an AI used in the game setup screens */
-			descname = _("Weak AI");
-			icon_filename = "images/ai/ai_weak.png";
-			type = Implementation::Type::kDefault;
+		WeakImpl()
+		   : Implementation(
+		        "weak",
+		        /** TRANSLATORS: This is the name of an AI used in the game setup screens */
+		        _("Weak AI"),
+		        "images/ai/ai_weak.png",
+		        Implementation::Type::kDefault) {
 		}
 		ComputerPlayer* instantiate(Widelands::Game& game,
 		                            Widelands::PlayerNumber const p) const override {
@@ -124,12 +126,13 @@
 	};
 
 	struct VeryWeakImpl : public ComputerPlayer::Implementation {
-		VeryWeakImpl() {
-			name = "very_weak";
-			/** TRANSLATORS: This is the name of an AI used in the game setup screens */
-			descname = _("Very Weak AI");
-			icon_filename = "images/ai/ai_very_weak.png";
-			type = Implementation::Type::kDefault;
+		VeryWeakImpl()
+		   : Implementation(
+		        "very_weak",
+		        /** TRANSLATORS: This is the name of an AI used in the game setup screens */
+		        _("Very Weak AI"),
+		        "images/ai/ai_very_weak.png",
+		        Implementation::Type::kDefault) {
 		}
 		ComputerPlayer* instantiate(Widelands::Game& game,
 		                            Widelands::PlayerNumber const p) const override {

=== modified file 'src/base/macros.h'
--- src/base/macros.h	2017-06-23 08:33:03 +0000
+++ src/base/macros.h	2017-06-26 14:45:57 +0000
@@ -104,4 +104,8 @@
 // the side-effect upcast has of creating a new identifier which won't be used.
 #define is_a(type, source) (dynamic_cast<const type*>(source) != nullptr)
 
+// For printf placeholders, we need to cast int8_t/uint8_t to avoid confusion with char
+#define cast_unsigned(u) static_cast<unsigned int>(u)
+#define cast_signed(i) static_cast<int>(i)
+
 #endif  // end of include guard: WL_BASE_MACROS_H

=== modified file 'src/graphic/playercolor.h'
--- src/graphic/playercolor.h	2017-04-28 06:47:01 +0000
+++ src/graphic/playercolor.h	2017-06-26 14:45:57 +0000
@@ -49,6 +49,19 @@
    RGBColor(144, 144, 144),  // light gray
 };
 
+// Hard coded team colors
+const RGBColor kTeamColors[kMaxPlayers / 2 + 1] = {
+	RGBColor(100, 100, 100),  // No team
+   RGBColor(2, 2, 198),      // blue
+   RGBColor(255, 41, 0),     // red
+   RGBColor(255, 232, 0),    // yellow
+   RGBColor(59, 223, 3),     // green
+   RGBColor(57, 57, 57),     // black/dark gray
+   RGBColor(255, 172, 0),    // orange
+   RGBColor(215, 0, 218),    // purple
+   RGBColor(255, 255, 255),  // white
+};
+
 /// Looks for a player color mask image, and if it finds one,
 /// returns the image with added playercolor. If no player color
 /// image file is found, gets the image from 'image_filename'

=== modified file 'src/logic/CMakeLists.txt'
--- src/logic/CMakeLists.txt	2017-06-05 07:48:28 +0000
+++ src/logic/CMakeLists.txt	2017-06-26 14:45:57 +0000
@@ -13,6 +13,7 @@
     io_filesystem
     logic
     logic_constants
+    notifications
     scripting_lua_interface
     scripting_lua_table
 )

=== modified file 'src/logic/game.cc'
--- src/logic/game.cc	2017-06-25 08:20:25 +0000
+++ src/logic/game.cc	2017-06-26 14:45:57 +0000
@@ -265,10 +265,10 @@
 	for (uint32_t i = 0; i < settings.players.size(); ++i) {
 		const PlayerSettings& playersettings = settings.players[i];
 
-		if (playersettings.state == PlayerSettings::stateClosed ||
-		    playersettings.state == PlayerSettings::stateOpen)
+		if (playersettings.state == PlayerSettings::State::kClosed ||
+		    playersettings.state == PlayerSettings::State::kOpen)
 			continue;
-		else if (playersettings.state == PlayerSettings::stateShared) {
+		else if (playersettings.state == PlayerSettings::State::kShared) {
 			shared.push_back(playersettings);
 			shared_num.push_back(i + 1);
 			continue;

=== modified file 'src/logic/game_settings.cc'
--- src/logic/game_settings.cc	2017-06-05 07:48:28 +0000
+++ src/logic/game_settings.cc	2017-06-26 14:45:57 +0000
@@ -1,1 +1,38 @@
-// Dummy to make CMake happy
+/*
+ * Copyright (C) 2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "logic/game_settings.h"
+
+Widelands::PlayerNumber GameSettings::find_shared(PlayerSlot slot) const {
+	Widelands::PlayerNumber result = 1;
+	for (; result <= players.size(); ++result) {
+		if (PlayerSettings::can_be_shared(players.at(result - 1).state) && (result - 1) != slot) {
+			break;
+		}
+	}
+	return result;
+}
+
+bool GameSettings::is_shared_usable(PlayerSlot slot, Widelands::PlayerNumber shared) const {
+	return shared <= players.size() && (shared - 1) != slot;
+}
+
+bool GameSettings::uncloseable(PlayerSlot slot) const {
+	return (scenario && !players.at(slot).closeable) || savegame;
+}

=== modified file 'src/logic/game_settings.h'
--- src/logic/game_settings.h	2017-06-05 07:33:18 +0000
+++ src/logic/game_settings.h	2017-06-26 14:45:57 +0000
@@ -28,11 +28,22 @@
 #include "logic/map_objects/tribes/tribe_basic_info.h"
 #include "logic/player_end_result.h"
 #include "logic/widelands.h"
+#include "notifications/note_ids.h"
+#include "notifications/notifications.h"
 #include "scripting/lua_interface.h"
 #include "scripting/lua_table.h"
 
+// PlayerSlot 0 will give us Widelands::PlayerNumber 1 etc., so we rename it to avoid confusion.
+// TODO(GunChleoc): Rename all uint8_t to PlayerSlot or Widelands::PlayerNumber
+using PlayerSlot = Widelands::PlayerNumber;
+
 struct PlayerSettings {
-	enum State { stateOpen, stateHuman, stateComputer, stateClosed, stateShared };
+	enum class State { kOpen, kHuman, kComputer, kClosed, kShared };
+
+	/// Returns whether the given state allows sharing a slot at all
+	static bool can_be_shared(PlayerSettings::State state) {
+		return state != PlayerSettings::State::kClosed && state != PlayerSettings::State::kShared;
+	}
 
 	State state;
 	uint8_t initialization_index;
@@ -71,6 +82,28 @@
 	             // not
 };
 
+/// The gamehost/gameclient are sending those to notify about status ghanges, which are then picked
+/// up by the UI.
+struct NoteGameSettings {
+	CAN_BE_SENT_AS_NOTE(NoteId::GameSettings)
+
+	enum class Action {
+		kUser,    // A client has picked a different player slot / become an observer
+		kPlayer,  // A player slot has changed its status (type, tribe etc.)
+		kMap      // An new map/savegame was selected
+	};
+
+	Action action;
+	PlayerSlot position;
+	uint8_t usernum;
+
+	explicit NoteGameSettings(Action init_action,
+	                          PlayerSlot init_position = std::numeric_limits<uint8_t>::max(),
+	                          uint8_t init_usernum = UserSettings::none())
+	   : action(init_action), position(init_position), usernum(init_usernum) {
+	}
+};
+
 /**
  * Holds all settings about a game that can be configured before the
  * game actually starts.
@@ -92,6 +125,14 @@
 		}
 	}
 
+	/// Find a player number that the slot could share in. Does not guarantee that a viable slot was
+	/// actually found.
+	Widelands::PlayerNumber find_shared(PlayerSlot slot) const;
+	/// Check if the player number returned by find_shared is usable
+	bool is_shared_usable(PlayerSlot slot, Widelands::PlayerNumber shared) const;
+	/// Savegame slots and certain scenario slots can't be closed
+	bool uncloseable(PlayerSlot slot) const;
+
 	/// Number of player position
 	int16_t playernum;
 	/// Number of users entry
@@ -154,7 +195,9 @@
 	                     bool savegame = false) = 0;
 	virtual void set_player_state(uint8_t number, PlayerSettings::State) = 0;
 	virtual void set_player_ai(uint8_t number, const std::string&, bool const random_ai = false) = 0;
-	virtual void next_player_state(uint8_t number) = 0;
+	// Multiplayer no longer toggles per button
+	virtual void next_player_state(uint8_t /* number */) {
+	}
 	virtual void
 	set_player_tribe(uint8_t number, const std::string&, bool const random_tribe = false) = 0;
 	virtual void set_player_init(uint8_t number, uint8_t index) = 0;
@@ -163,10 +206,11 @@
 	virtual void set_player_number(uint8_t number) = 0;
 	virtual void set_player_team(uint8_t number, Widelands::TeamNumber team) = 0;
 	virtual void set_player_closeable(uint8_t number, bool closeable) = 0;
-	virtual void set_player_shared(uint8_t number, uint8_t shared) = 0;
+	virtual void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) = 0;
 	virtual void set_win_condition_script(const std::string& wc) = 0;
 	virtual std::string get_win_condition_script() = 0;
 
+	// For retrieveing tips texts
 	struct NoTribe {};
 	const std::string& get_players_tribe() {
 		if (UserSettings::highest_playernum() < settings().playernum)

=== modified file 'src/logic/single_player_game_settings_provider.cc'
--- src/logic/single_player_game_settings_provider.cc	2017-02-10 14:12:36 +0000
+++ src/logic/single_player_game_settings_provider.cc	2017-06-26 14:45:57 +0000
@@ -81,14 +81,15 @@
 
 	while (oldplayers < maxplayers) {
 		PlayerSettings& player = s.players[oldplayers];
-		player.state = (oldplayers == 0) ? PlayerSettings::stateHuman : PlayerSettings::stateComputer;
+		player.state =
+		   (oldplayers == 0) ? PlayerSettings::State::kHuman : PlayerSettings::State::kComputer;
 		player.tribe = s.tribes.at(0).name;
 		player.random_tribe = false;
 		player.initialization_index = 0;
 		player.name = (boost::format(_("Player %u")) % (oldplayers + 1)).str();
 		player.team = 0;
 		// Set default computerplayer ai type
-		if (player.state == PlayerSettings::stateComputer) {
+		if (player.state == PlayerSettings::State::kComputer) {
 			const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations();
 			if (impls.size() > 1) {
 				player.ai = impls.at(0)->name;
@@ -107,8 +108,8 @@
 	if (number == s.playernum || number >= s.players.size())
 		return;
 
-	if (state == PlayerSettings::stateOpen)
-		state = PlayerSettings::stateComputer;
+	if (state == PlayerSettings::State::kOpen)
+		state = PlayerSettings::State::kComputer;
 
 	s.players[number].state = state;
 }
@@ -147,7 +148,7 @@
 		s.players[number].ai = (*it)->name;
 	}
 
-	s.players[number].state = PlayerSettings::stateComputer;
+	s.players[number].state = PlayerSettings::State::kComputer;
 }
 
 void SinglePlayerGameSettingsProvider::set_player_tribe(uint8_t const number,
@@ -199,7 +200,7 @@
 	// nothing to do
 }
 
-void SinglePlayerGameSettingsProvider::set_player_shared(uint8_t, uint8_t) {
+void SinglePlayerGameSettingsProvider::set_player_shared(PlayerSlot, Widelands::PlayerNumber) {
 	// nothing to do
 }
 
@@ -219,8 +220,8 @@
 		return;
 	PlayerSettings const position = settings().players.at(number);
 	PlayerSettings const player = settings().players.at(settings().playernum);
-	if (number < settings().players.size() && (position.state == PlayerSettings::stateOpen ||
-	                                           position.state == PlayerSettings::stateComputer)) {
+	if (number < settings().players.size() && (position.state == PlayerSettings::State::kOpen ||
+	                                           position.state == PlayerSettings::State::kComputer)) {
 		set_player(number, player);
 		set_player(settings().playernum, position);
 		s.playernum = number;

=== modified file 'src/logic/single_player_game_settings_provider.h'
--- src/logic/single_player_game_settings_provider.h	2017-02-10 14:12:36 +0000
+++ src/logic/single_player_game_settings_provider.h	2017-06-26 14:45:57 +0000
@@ -54,7 +54,7 @@
 	void set_player_init(uint8_t const number, uint8_t const index) override;
 	void set_player_team(uint8_t number, Widelands::TeamNumber team) override;
 	void set_player_closeable(uint8_t, bool) override;
-	void set_player_shared(uint8_t, uint8_t) override;
+	void set_player_shared(PlayerSlot, Widelands::PlayerNumber) override;
 	void set_player_name(uint8_t const number, const std::string& name) override;
 	void set_player(uint8_t const number, const PlayerSettings& ps) override;
 	void set_player_number(uint8_t const number) override;

=== modified file 'src/network/CMakeLists.txt'
--- src/network/CMakeLists.txt	2017-05-14 21:06:23 +0000
+++ src/network/CMakeLists.txt	2017-06-26 14:45:57 +0000
@@ -33,7 +33,6 @@
     build_info
     chat
     game_io
-    graphic_playercolor
     helper
     io_fileread
     io_filesystem

=== modified file 'src/network/gameclient.cc'
--- src/network/gameclient.cc	2017-06-15 15:45:01 +0000
+++ src/network/gameclient.cc	2017-06-26 14:45:57 +0000
@@ -349,25 +349,25 @@
 	//  client is not allowed to do this
 }
 
-void GameClient::set_player_shared(uint8_t number, uint8_t player) {
+void GameClient::set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) {
 	if ((number != d->settings.playernum))
 		return;
 
 	SendPacket s;
 	s.unsigned_8(NETCMD_SETTING_CHANGESHARED);
 	s.unsigned_8(number);
-	s.unsigned_8(player);
+	s.unsigned_8(shared);
 	d->net->send(s);
 }
 
-void GameClient::set_player_init(uint8_t number, uint8_t) {
+void GameClient::set_player_init(uint8_t number, uint8_t initialization_index) {
 	if ((number != d->settings.playernum))
 		return;
 
-	// Host will decide what to change, therefore the init is not send, just the request to change
 	SendPacket s;
 	s.unsigned_8(NETCMD_SETTING_CHANGEINIT);
 	s.unsigned_8(number);
+	s.unsigned_8(initialization_index);
 	d->net->send(s);
 }
 
@@ -397,8 +397,8 @@
 		return;
 	// Same if the player is not selectable
 	if (number < d->settings.players.size() &&
-	    (d->settings.players.at(number).state == PlayerSettings::stateClosed ||
-	     d->settings.players.at(number).state == PlayerSettings::stateComputer))
+	    (d->settings.players.at(number).state == PlayerSettings::State::kClosed ||
+	     d->settings.players.at(number).state == PlayerSettings::State::kComputer))
 		return;
 
 	// Send request
@@ -452,6 +452,7 @@
 	player.random_ai = packet.unsigned_8() == 1;
 	player.team = packet.unsigned_8();
 	player.shared_in = packet.unsigned_8();
+	Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kPlayer, number));
 }
 
 void GameClient::receive_one_user(uint32_t const number, StreamRead& packet) {
@@ -466,10 +467,13 @@
 	d->settings.users.at(number).name = packet.string();
 	d->settings.users.at(number).position = packet.signed_32();
 	d->settings.users.at(number).ready = packet.unsigned_8() == 1;
+
 	if (static_cast<int32_t>(number) == d->settings.usernum) {
 		d->localplayername = d->settings.users.at(number).name;
 		d->settings.playernum = d->settings.users.at(number).position;
 	}
+	Notifications::publish(
+	   NoteGameSettings(NoteGameSettings::Action::kUser, d->settings.playernum, number));
 }
 
 void GameClient::send(const std::string& msg) {
@@ -560,6 +564,7 @@
 		// New map was set, so we clean up the buffer of a previously requested file
 		if (file_)
 			delete file_;
+		Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kMap));
 		break;
 	}
 

=== modified file 'src/network/gameclient.h'
--- src/network/gameclient.h	2017-06-15 15:45:01 +0000
+++ src/network/gameclient.h	2017-06-26 14:45:57 +0000
@@ -85,13 +85,13 @@
 	virtual void set_player_tribe(uint8_t number,
 	                              const std::string& tribe,
 	                              bool const random_tribe = false) override;
-	void set_player_init(uint8_t number, uint8_t index) override;
+	void set_player_init(uint8_t number, uint8_t initialization_index) override;
 	void set_player_name(uint8_t number, const std::string& name) override;
 	void set_player(uint8_t number, const PlayerSettings& ps) override;
 	void set_player_number(uint8_t number) override;
 	void set_player_team(uint8_t number, Widelands::TeamNumber team) override;
 	void set_player_closeable(uint8_t number, bool closeable) override;
-	void set_player_shared(uint8_t number, uint8_t shared) override;
+	void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) override;
 	void set_win_condition_script(const std::string&) override;
 	std::string get_win_condition_script() override;
 

=== modified file 'src/network/gamehost.cc'
--- src/network/gamehost.cc	2017-06-25 21:55:39 +0000
+++ src/network/gamehost.cc	2017-06-26 14:45:57 +0000
@@ -81,13 +81,16 @@
 		return true;
 	}
 	bool can_change_player_state(uint8_t const number) override {
+		if (number >= settings().players.size()) {
+			return false;
+		}
 		if (settings().savegame)
-			return settings().players.at(number).state != PlayerSettings::stateClosed;
+			return settings().players.at(number).state != PlayerSettings::State::kClosed;
 		else if (settings().scenario)
-			return ((settings().players.at(number).state == PlayerSettings::stateOpen ||
-			         settings().players.at(number).state == PlayerSettings::stateHuman) &&
+			return ((settings().players.at(number).state == PlayerSettings::State::kOpen ||
+			         settings().players.at(number).state == PlayerSettings::State::kHuman) &&
 			        settings().players.at(number).closeable) ||
-			       settings().players.at(number).state == PlayerSettings::stateClosed;
+			       settings().players.at(number).state == PlayerSettings::State::kClosed;
 		return true;
 	}
 	bool can_change_player_tribe(uint8_t const number) override {
@@ -105,7 +108,7 @@
 			return false;
 		if (number == settings().playernum)
 			return true;
-		return settings().players.at(number).state == PlayerSettings::stateComputer;
+		return settings().players.at(number).state == PlayerSettings::State::kComputer;
 	}
 
 	bool can_launch() override {
@@ -124,83 +127,6 @@
 
 		host_->set_player_state(number, state);
 	}
-	void next_player_state(uint8_t const number) override {
-		if (number > settings().players.size())
-			return;
-
-		PlayerSettings::State newstate = PlayerSettings::stateClosed;
-		switch (host_->settings().players.at(number).state) {
-		case PlayerSettings::stateClosed:
-			// In savegames : closed players can not be changed.
-			assert(!host_->settings().savegame);
-			newstate = PlayerSettings::stateOpen;
-			break;
-		case PlayerSettings::stateOpen:
-		case PlayerSettings::stateHuman:
-			if (host_->settings().scenario) {
-				assert(host_->settings().players.at(number).closeable);
-				newstate = PlayerSettings::stateClosed;
-				break;
-			}  // else fall through
-			FALLS_THROUGH;
-		case PlayerSettings::stateComputer: {
-			const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations();
-			ComputerPlayer::ImplementationVector::const_iterator it = impls.begin();
-			if (host_->settings().players.at(number).ai.empty()) {
-				set_player_ai(number, (*it)->name);
-				newstate = PlayerSettings::stateComputer;
-				break;
-			}
-			do {
-				++it;
-				if ((*(it - 1))->name == host_->settings().players.at(number).ai)
-					break;
-			} while (it != impls.end());
-			if (settings().players.at(number).random_ai) {
-				set_player_ai(number, std::string());
-				set_player_name(number, std::string());
-				// Do not share a player in savegames or scenarios
-				if (host_->settings().scenario || host_->settings().savegame)
-					newstate = PlayerSettings::stateOpen;
-				else {
-					uint8_t shared = 0;
-					for (; shared < settings().players.size(); ++shared) {
-						if (settings().players.at(shared).state != PlayerSettings::stateClosed &&
-						    settings().players.at(shared).state != PlayerSettings::stateShared)
-							break;
-					}
-					if (shared < settings().players.size()) {
-						newstate = PlayerSettings::stateShared;
-						set_player_shared(number, shared + 1);
-					} else
-						newstate = PlayerSettings::stateClosed;
-				}
-			} else if (it == impls.end()) {
-				do {
-					uint8_t random = (std::rand() % impls.size());  // Choose a random AI
-					it = impls.begin() + random;
-				} while ((*it)->type == ComputerPlayer::Implementation::Type::kEmpty);
-				set_player_ai(number, (*it)->name, true);
-				newstate = PlayerSettings::stateComputer;
-				break;
-			} else {
-				set_player_ai(number, (*it)->name);
-				newstate = PlayerSettings::stateComputer;
-			}
-			break;
-		}
-		case PlayerSettings::stateShared: {
-			// Do not close a player in savegames or scenarios
-			if (host_->settings().scenario || host_->settings().savegame)
-				newstate = PlayerSettings::stateOpen;
-			else
-				newstate = PlayerSettings::stateClosed;
-			break;
-		}
-		}
-
-		host_->set_player_state(number, newstate, true);
-	}
 
 	void
 	set_player_tribe(uint8_t number, const std::string& tribe, bool const random_tribe) override {
@@ -208,9 +134,10 @@
 			return;
 
 		if (number == settings().playernum ||
-		    settings().players.at(number).state == PlayerSettings::stateComputer ||
-		    settings().players.at(number).state == PlayerSettings::stateShared ||
-		    settings().players.at(number).state == PlayerSettings::stateOpen)  // For savegame loading
+		    settings().players.at(number).state == PlayerSettings::State::kComputer ||
+		    settings().players.at(number).state == PlayerSettings::State::kShared ||
+		    settings().players.at(number).state ==
+		       PlayerSettings::State::kOpen)  // For savegame loading
 			host_->set_player_tribe(number, tribe, random_tribe);
 	}
 
@@ -219,7 +146,7 @@
 			return;
 
 		if (number == settings().playernum ||
-		    settings().players.at(number).state == PlayerSettings::stateComputer)
+		    settings().players.at(number).state == PlayerSettings::State::kComputer)
 			host_->set_player_team(number, team);
 	}
 
@@ -229,7 +156,7 @@
 		host_->set_player_closeable(number, closeable);
 	}
 
-	void set_player_shared(uint8_t number, uint8_t shared) override {
+	void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) override {
 		if (number >= host_->settings().players.size())
 			return;
 		host_->set_player_shared(number, shared);
@@ -1024,9 +951,9 @@
 	// but not all should be closed!
 	bool one_not_closed = false;
 	for (PlayerSettings& setting : d->settings.players) {
-		if (setting.state != PlayerSettings::stateClosed)
+		if (setting.state != PlayerSettings::State::kClosed)
 			one_not_closed = true;
-		if (setting.state == PlayerSettings::stateOpen)
+		if (setting.state == PlayerSettings::State::kOpen)
 			return false;
 	}
 	return one_not_closed;
@@ -1076,7 +1003,7 @@
 
 	while (oldplayers < maxplayers) {
 		PlayerSettings& player = d->settings.players.at(oldplayers);
-		player.state = PlayerSettings::stateOpen;
+		player.state = PlayerSettings::State::kOpen;
 		player.name = "";
 		player.tribe = d->settings.tribes.at(0).name;
 		player.random_tribe = false;
@@ -1151,9 +1078,7 @@
 	if (player.state == state)
 		return;
 
-	SendPacket s;
-
-	if (player.state == PlayerSettings::stateHuman) {
+	if (player.state == PlayerSettings::State::kHuman) {
 		//  0 is host and has no client
 		if (d->settings.users.at(0).position == number) {
 			d->settings.users.at(0).position = UserSettings::none();
@@ -1173,13 +1098,6 @@
 						break;
 					}
 				}
-
-				//  broadcast change
-				s.unsigned_8(NETCMD_SETTING_USER);
-				s.unsigned_32(i);
-				write_setting_user(s, i);
-				broadcast(s);
-
 				break;
 			}
 		}
@@ -1187,15 +1105,53 @@
 
 	player.state = state;
 
-	if (player.state == PlayerSettings::stateComputer)
+	// Make sure that shared slots have a player number to share in
+	if (player.state == PlayerSettings::State::kShared) {
+		const PlayerSlot shared = d->settings.find_shared(number);
+		if (d->settings.is_shared_usable(number, shared)) {
+			set_player_shared(number, shared);
+		} else {
+			player.state = PlayerSettings::State::kClosed;
+		}
+	}
+
+	// Update shared positions for other players
+	for (size_t i = 0; i < d->settings.players.size(); ++i) {
+		if (i == number) {
+			// Don't set own state
+			continue;
+		}
+		if (d->settings.players.at(i).state == PlayerSettings::State::kShared) {
+			const PlayerSlot shared = d->settings.find_shared(i);
+			if (d->settings.is_shared_usable(i, shared)) {
+				set_player_shared(i, shared);
+			} else {
+				set_player_state(i, PlayerSettings::State::kClosed, host);
+			}
+		}
+	}
+
+	// Make sure that slots that are not closeable stay open
+	if (player.state == PlayerSettings::State::kClosed && d->settings.uncloseable(number)) {
+		player.state = PlayerSettings::State::kOpen;
+	}
+
+	if (player.state == PlayerSettings::State::kComputer)
 		player.name = get_computer_player_name(number);
 
-	// Broadcast change
+	// Broadcast change to player
+	SendPacket s;
 	s.reset();
 	s.unsigned_8(NETCMD_SETTING_PLAYER);
 	s.unsigned_8(number);
 	write_setting_player(s, number);
 	broadcast(s);
+
+	// Let clients know whether their slot has changed
+	s.reset();
+	s.unsigned_8(NETCMD_SETTING_ALLUSERS);
+	write_setting_all_users(s);
+	broadcast(s);
 }
 
 void GameHost::set_player_tribe(uint8_t const number,
@@ -1317,7 +1273,7 @@
 	// uses it.
 }
 
-void GameHost::set_player_shared(uint8_t number, uint8_t shared) {
+void GameHost::set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) {
 	if (number >= d->settings.players.size())
 		return;
 
@@ -1327,8 +1283,8 @@
 		return;
 
 	PlayerSettings& sharedplr = d->settings.players.at(shared - 1);
-	assert(sharedplr.state != PlayerSettings::stateClosed &&
-	       sharedplr.state != PlayerSettings::stateShared);
+	assert(PlayerSettings::can_be_shared(sharedplr.state));
+	assert(d->settings.is_shared_usable(number, shared));
 
 	player.shared_in = shared;
 	player.tribe = sharedplr.tribe;
@@ -1372,8 +1328,8 @@
 
 void GameHost::switch_to_player(uint32_t user, uint8_t number) {
 	if (number < d->settings.players.size() &&
-	    (d->settings.players.at(number).state != PlayerSettings::stateOpen &&
-	     d->settings.players.at(number).state != PlayerSettings::stateHuman))
+	    (d->settings.players.at(number).state != PlayerSettings::State::kOpen &&
+	     d->settings.players.at(number).state != PlayerSettings::State::kHuman))
 		return;
 
 	uint32_t old = d->settings.users.at(user).position;
@@ -1388,14 +1344,14 @@
 		temp2 = temp2.erase(op.name.find(temp), temp.size());
 		set_player_name(old, temp2);
 		if (temp2.empty())
-			set_player_state(old, PlayerSettings::stateOpen);
+			set_player_state(old, PlayerSettings::State::kOpen);
 	}
 
 	if (number < d->settings.players.size()) {
 		// Add clients name to new player slot
 		PlayerSettings& op = d->settings.players.at(number);
-		if (op.state == PlayerSettings::stateOpen) {
-			set_player_state(number, PlayerSettings::stateHuman);
+		if (op.state == PlayerSettings::State::kOpen) {
+			set_player_state(number, PlayerSettings::State::kHuman);
 			set_player_name(number, " " + name + " ");
 		} else
 			set_player_name(number, op.name + " " + name + " ");
@@ -1482,6 +1438,7 @@
 	packet.string(d->settings.mapfilename);
 	packet.unsigned_8(d->settings.savegame ? 1 : 0);
 	packet.unsigned_8(d->settings.scenario ? 1 : 0);
+	Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kMap));
 }
 
 void GameHost::write_setting_player(SendPacket& packet, uint8_t const number) {
@@ -1495,6 +1452,7 @@
 	packet.unsigned_8(player.random_ai ? 1 : 0);
 	packet.unsigned_8(player.team);
 	packet.unsigned_8(player.shared_in);
+	Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kPlayer, number));
 }
 
 void GameHost::write_setting_all_players(SendPacket& packet) {
@@ -1507,6 +1465,8 @@
 	packet.string(d->settings.users.at(number).name);
 	packet.signed_32(d->settings.users.at(number).position);
 	packet.unsigned_8(d->settings.users.at(number).ready ? 1 : 0);
+	Notifications::publish(NoteGameSettings(
+	   NoteGameSettings::Action::kUser, d->settings.users.at(number).position, number));
 }
 
 void GameHost::write_setting_all_users(SendPacket& packet) {
@@ -1678,7 +1638,7 @@
 
 	// Check if there is an unoccupied player left and if, assign.
 	for (uint8_t i = 0; i < d->settings.players.size(); ++i)
-		if (d->settings.players.at(i).state == PlayerSettings::stateOpen) {
+		if (d->settings.players.at(i).state == PlayerSettings::State::kOpen) {
 			switch_to_player(client.usernum, i);
 			break;
 		}
@@ -2100,10 +2060,13 @@
 
 	case NETCMD_SETTING_CHANGEINIT:
 		if (!d->game) {
+			// TODO(GunChleoc): For some nebulous reason, we don't receive the num that the client is
+			// sending when a player changes slot. So, keeping the access to the client off for now.
+			// Would be nice to have though.
 			uint8_t num = r.unsigned_8();
 			if (num != client.playernum)
 				throw DisconnectException("NO_ACCESS_TO_PLAYER");
-			d->npsb.toggle_init(num);
+			set_player_init(num, r.unsigned_8());
 		}
 		break;
 
@@ -2273,7 +2236,7 @@
 		}
 	}
 
-	set_player_state(number, PlayerSettings::stateOpen);
+	set_player_state(number, PlayerSettings::State::kOpen);
 	if (d->game)
 		init_computer_player(number + 1);
 }

=== modified file 'src/network/gamehost.h'
--- src/network/gamehost.h	2017-06-24 11:18:12 +0000
+++ src/network/gamehost.h	2017-06-26 14:45:57 +0000
@@ -76,7 +76,7 @@
 	void set_player_number(uint8_t number);
 	void set_player_team(uint8_t number, Widelands::TeamNumber team);
 	void set_player_closeable(uint8_t number, bool closeable);
-	void set_player_shared(uint8_t number, uint8_t shared);
+	void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared);
 	void switch_to_player(uint32_t user, uint8_t number);
 	void set_win_condition_script(const std::string& wc);
 

=== modified file 'src/network/network_player_settings_backend.cc'
--- src/network/network_player_settings_backend.cc	2017-02-14 10:18:15 +0000
+++ src/network/network_player_settings_backend.cc	2017-06-26 14:45:57 +0000
@@ -19,134 +19,69 @@
 
 #include "network/network_player_settings_backend.h"
 
-#include "base/i18n.h"
-#include "base/log.h"
-#include "base/wexception.h"
-#include "logic/game_settings.h"
-#include "logic/map_objects/tribes/tribe_descr.h"
-#include "logic/player.h"
-
-/// Toggle through the types
-void NetworkPlayerSettingsBackend::toggle_type(uint8_t id) {
-	if (id >= s->settings().players.size())
-		return;
-
-	s->next_player_state(id);
-}
-
-void NetworkPlayerSettingsBackend::set_tribe(uint8_t id, const std::string& tribename) {
+#include "ai/computer_player.h"
+
+void NetworkPlayerSettingsBackend::set_player_state(PlayerSlot id, PlayerSettings::State state) {
+	if (id >= s->settings().players.size()) {
+		return;
+	}
+	s->set_player_state(id, state);
+}
+
+void NetworkPlayerSettingsBackend::set_player_ai(PlayerSlot id,
+                                                 const std::string& name,
+                                                 bool random_ai) {
+	if (id >= s->settings().players.size()) {
+		return;
+	}
+	if (random_ai) {
+		const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations();
+		ComputerPlayer::ImplementationVector::const_iterator it = impls.begin();
+		if (impls.size() > 1) {
+			do {
+				size_t random = (std::rand() % impls.size());  // Choose a random AI
+				it = impls.begin() + random;
+			} while ((*it)->type == ComputerPlayer::Implementation::Type::kEmpty);
+		}
+		s->set_player_ai(id, (*it)->name, random_ai);
+	} else {
+		s->set_player_ai(id, name, random_ai);
+	}
+}
+
+void NetworkPlayerSettingsBackend::set_player_tribe(PlayerSlot id, const std::string& tribename) {
 	const GameSettings& settings = s->settings();
-
-	if (id >= settings.players.size() || tribename.empty())
+	if (id >= settings.players.size() || tribename.empty()) {
 		return;
-
-	if (settings.players.at(id).state != PlayerSettings::stateShared) {
+	}
+	if (settings.players.at(id).state != PlayerSettings::State::kShared) {
 		s->set_player_tribe(id, tribename, tribename == "random");
 	}
 }
 
 /// Set the shared in player for the given id
-void NetworkPlayerSettingsBackend::set_shared_in(uint8_t id, uint8_t shared_in) {
-	const GameSettings& settings = s->settings();
-	if (id > settings.players.size() || shared_in > settings.players.size())
-		return;
-	if (settings.players.at(id).state == PlayerSettings::stateShared) {
-		s->set_player_shared(id, shared_in);
-	}
-}
-
-/// Toggle through shared in players
-void NetworkPlayerSettingsBackend::toggle_shared_in(uint8_t id) {
-	const GameSettings& settings = s->settings();
-
-	if (id >= settings.players.size() ||
-	    settings.players.at(id).state != PlayerSettings::stateShared)
-		return;
-
-	uint8_t sharedplr = settings.players.at(id).shared_in;
-	for (; sharedplr < settings.players.size(); ++sharedplr) {
-		if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
-		    settings.players.at(sharedplr).state != PlayerSettings::stateShared)
-			break;
-	}
-	if (sharedplr < settings.players.size()) {
-		// We have already found the next player
-		set_shared_in(id, sharedplr + 1);
-		return;
-	}
-	sharedplr = 0;
-	for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) {
-		if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
-		    settings.players.at(sharedplr).state != PlayerSettings::stateShared)
-			break;
-	}
-	if (sharedplr < settings.players.at(id).shared_in) {
-		// We have found the next player
-		set_shared_in(id, sharedplr + 1);
-		return;
-	} else {
-		// No fitting player found
-		return toggle_type(id);
-	}
-}
-
-/// Toggle through the initializations
-void NetworkPlayerSettingsBackend::toggle_init(uint8_t id) {
-	const GameSettings& settings = s->settings();
-
-	if (id >= settings.players.size())
-		return;
-
-	const PlayerSettings& player = settings.players[id];
-	for (const TribeBasicInfo& temp_tribeinfo : settings.tribes) {
-		if (temp_tribeinfo.name == player.tribe) {
-			return s->set_player_init(
-			   id, (player.initialization_index + 1) % temp_tribeinfo.initializations.size());
-		}
-	}
-	NEVER_HERE();
-}
-
-/// Toggle through the teams
-void NetworkPlayerSettingsBackend::toggle_team(uint8_t id) {
-	const GameSettings& settings = s->settings();
-
-	if (id >= settings.players.size())
-		return;
-
-	Widelands::TeamNumber currentteam = settings.players.at(id).team;
-	Widelands::TeamNumber maxteam = settings.players.size() / 2;
-	Widelands::TeamNumber newteam;
-
-	if (currentteam >= maxteam)
-		newteam = 0;
-	else
-		newteam = currentteam + 1;
-
-	s->set_player_team(id, newteam);
-}
-
-/// Check if all settings for the player are still valid
-void NetworkPlayerSettingsBackend::refresh(uint8_t id) {
-	const GameSettings& settings = s->settings();
-
-	if (id >= settings.players.size())
-		return;
-
-	const PlayerSettings& player = settings.players[id];
-
-	if (player.state == PlayerSettings::stateShared) {
-		// ensure that the shared_in player is able to use this starting position
-		if (player.shared_in > settings.players.size())
-			toggle_shared_in(id);
-		if (settings.players.at(player.shared_in - 1).state == PlayerSettings::stateClosed ||
-		    settings.players.at(player.shared_in - 1).state == PlayerSettings::stateShared)
-			toggle_shared_in(id);
-
-		if (shared_in_tribe[id] != settings.players.at(player.shared_in - 1).tribe) {
-			s->set_player_tribe(id, settings.players.at(player.shared_in - 1).tribe,
-			                    settings.players.at(player.shared_in - 1).random_tribe);
-			shared_in_tribe[id] = settings.players.at(id).tribe;
-		}
-	}
+void NetworkPlayerSettingsBackend::set_player_shared(PlayerSlot id,
+                                                     Widelands::PlayerNumber shared) {
+	const GameSettings& settings = s->settings();
+	if (id >= settings.players.size() || shared > settings.players.size())
+		return;
+	if (settings.players.at(id).state == PlayerSettings::State::kShared) {
+		s->set_player_shared(id, shared);
+	}
+}
+
+/// Sets the initialization for the player slot (Headquarters, Fortified Village etc.)
+void NetworkPlayerSettingsBackend::set_player_init(PlayerSlot id, uint8_t initialization_index) {
+	if (id >= s->settings().players.size()) {
+		return;
+	}
+	s->set_player_init(id, initialization_index);
+}
+
+/// Sets the team for the player slot
+void NetworkPlayerSettingsBackend::set_player_team(PlayerSlot id, Widelands::TeamNumber team) {
+	if (id >= s->settings().players.size()) {
+		return;
+	}
+	s->set_player_team(id, team);
 }

=== modified file 'src/network/network_player_settings_backend.h'
--- src/network/network_player_settings_backend.h	2017-06-24 08:47:46 +0000
+++ src/network/network_player_settings_backend.h	2017-06-26 14:45:57 +0000
@@ -20,33 +20,22 @@
 #ifndef WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H
 #define WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H
 
-#include "graphic/playercolor.h"
 #include "logic/game_settings.h"
+#include "logic/widelands.h"
 
 struct NetworkPlayerSettingsBackend {
 
 	explicit NetworkPlayerSettingsBackend(GameSettingsProvider* const settings) : s(settings) {
-		for (uint8_t i = 0; i < kMaxPlayers; ++i)
-			shared_in_tribe[i] = std::string();
-	}
-
-	void toggle_type(uint8_t id);
-	void set_shared_in(uint8_t id, uint8_t shared_in);
-	void set_tribe(uint8_t id, const std::string& tribename);
-	void set_block_tribe_selection(bool blocked) {
-		tribe_selection_blocked = blocked;
-	}
-
-	void toggle_init(uint8_t id);
-	void toggle_team(uint8_t id);
-	void refresh(uint8_t id);
+	}
+
+	void set_player_state(PlayerSlot id, PlayerSettings::State state);
+	void set_player_ai(PlayerSlot id, const std::string& name, bool random_ai);
+	void set_player_shared(PlayerSlot id, Widelands::PlayerNumber shared);
+	void set_player_tribe(PlayerSlot id, const std::string& tribename);
+	void set_player_init(PlayerSlot id, uint8_t initialization_index);
+	void set_player_team(PlayerSlot id, Widelands::TeamNumber team);
 
 	GameSettingsProvider* const s;
-	std::string shared_in_tribe[kMaxPlayers];
-	bool tribe_selection_blocked;
-
-private:
-	void toggle_shared_in(uint8_t id);
 };
 
 #endif  // end of include guard: WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H

=== modified file 'src/network/network_protocol.h'
--- src/network/network_protocol.h	2017-05-06 11:00:19 +0000
+++ src/network/network_protocol.h	2017-06-26 14:45:57 +0000
@@ -395,9 +395,9 @@
     * \li unsigned_8: new shared player
     *
     * \note The client must not assume that the host will accept this
-    * request. Change of team number only becomes effective when/if the host
+    * request. Change of the initialization only becomes effective when/if the host
     * replies with a \ref NETCMD_SETTING_PLAYER or
-    *  \ref NETCMD_SETTING_ALLPLAYERS indicating the changed team.
+    *  \ref NETCMD_SETTING_ALLPLAYERS indicating the changed initialization.
     */
 	NETCMD_SETTING_CHANGESHARED = 27,
 
@@ -406,6 +406,7 @@
     * client wants to change a player's initialisation.
     *
     * \li unsigned_8: number of the player
+    * \li unsigned_8: index of the initialization
     *
     * \note The client must not assume that the host will accept this
     * request. Change of team number only becomes effective when/if the host

=== modified file 'src/notifications/note_ids.h'
--- src/notifications/note_ids.h	2017-03-02 12:21:57 +0000
+++ src/notifications/note_ids.h	2017-06-26 14:45:57 +0000
@@ -40,7 +40,9 @@
 	Economy,
 	GraphicResolutionChanged,
 	NoteExpeditionCanceled,
-	Sound
+	Sound,
+	Dropdown,
+	GameSettings
 };
 
 #endif  // end of include guard: WL_NOTIFICATIONS_NOTE_IDS_H

=== modified file 'src/ui_basic/dropdown.cc'
--- src/ui_basic/dropdown.cc	2017-06-02 08:14:40 +0000
+++ src/ui_basic/dropdown.cc	2017-06-26 14:45:57 +0000
@@ -43,6 +43,8 @@
 
 namespace UI {
 
+int BaseDropdown::next_id_ = 0;
+
 BaseDropdown::BaseDropdown(UI::Panel* parent,
                            int32_t x,
                            int32_t y,
@@ -56,11 +58,14 @@
    : UI::Panel(parent,
                x,
                y,
-               type == DropdownType::kTextual ? w : button_dimension,
+               type == DropdownType::kPictorial ? button_dimension : w,
                // Height only to fit the button, so we can use this in Box layout.
                base_height(button_dimension)),
+     id_(next_id_++),
      max_list_height_(h - 2 * get_h()),
      list_width_(w),
+     list_offset_x_(0),
+     list_offset_y_(0),
      button_dimension_(button_dimension),
      mouse_tolerance_(50),
      button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
@@ -79,13 +84,23 @@
                      "dropdown_label",
                      0,
                      0,
-                     type == DropdownType::kTextual ? w - button_dimension : button_dimension,
+                     type == DropdownType::kTextual ?
+                        w - button_dimension :
+                        type == DropdownType::kTextualNarrow ? w : button_dimension,
                      get_h(),
                      background,
                      label),
      label_(label),
      type_(type),
      is_enabled_(true) {
+
+	// Close whenever another dropdown is opened
+	subscriber_ = Notifications::subscribe<NoteDropdown>([this](const NoteDropdown& note) {
+		if (id_ != note.id) {
+			close();
+		}
+	});
+
 	assert(max_list_height_ > 0);
 	// Hook into highest parent that we can get so that we can drop down outside the panel.
 	// Positioning breaks down with TabPanels, so we exclude them.
@@ -125,26 +140,42 @@
 
 void BaseDropdown::layout() {
 	const int base_h = base_height(button_dimension_);
-	const int w = type_ == DropdownType::kTextual ? get_w() : button_dimension_;
+	const int w = type_ == DropdownType::kPictorial ? button_dimension_ : get_w();
 	button_box_.set_size(w, base_h);
 	display_button_.set_desired_size(
 	   type_ == DropdownType::kTextual ? w - button_dimension_ : w, base_h);
 	int new_list_height =
 	   std::min(static_cast<int>(list_->size()) * list_->get_lineheight(), max_list_height_);
-	list_->set_size(type_ == DropdownType::kTextual ? w : list_width_, new_list_height);
+	list_->set_size(type_ != DropdownType::kPictorial ? w : list_width_, new_list_height);
 	set_desired_size(w, base_h);
 
 	// Update list position. The list is hooked into the highest parent that we can get so that we
 	// can drop down outside the panel. Positioning breaks down with TabPanels, so we exclude them.
 	UI::Panel* parent = get_parent();
-	int new_list_y = get_y() + get_h() + parent->get_y();
+	int new_list_y = get_y() + parent->get_y();
 	int new_list_x = get_x() + parent->get_x();
 	while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
 		parent = parent->get_parent();
 		new_list_y += parent->get_y();
 		new_list_x += parent->get_x();
 	}
-	list_->set_pos(Vector2i(new_list_x, new_list_y));
+
+	// Drop up instead of down if it doesn't fit
+	if (new_list_y + list_->get_h() > g_gr->get_yres()) {
+		list_offset_y_ = -list_->get_h();
+	} else {
+		list_offset_y_ = display_button_.get_h();
+	}
+
+	// Right align instead of left align if it doesn't fit
+	if (new_list_x + list_->get_w() > g_gr->get_xres()) {
+		list_offset_x_ = display_button_.get_w() - list_->get_w();
+		if (push_button_ != nullptr) {
+			list_offset_x_ += push_button_->get_w();
+		}
+	}
+
+	list_->set_pos(Vector2i(new_list_x + list_offset_x_, new_list_y + list_offset_y_));
 }
 
 void BaseDropdown::add(const std::string& name,
@@ -178,16 +209,29 @@
 
 void BaseDropdown::set_label(const std::string& text) {
 	label_ = text;
-	if (type_ == DropdownType::kTextual) {
+	if (type_ != DropdownType::kPictorial) {
 		display_button_.set_title(label_);
 	}
 }
 
+void BaseDropdown::set_image(const Image* image) {
+	display_button_.set_pic(image);
+}
+
 void BaseDropdown::set_tooltip(const std::string& text) {
 	tooltip_ = text;
 	display_button_.set_tooltip(tooltip_);
 }
 
+void BaseDropdown::set_errored(const std::string& error_message) {
+	set_tooltip((boost::format(_("%1%: %2%")) % _("Error") % error_message).str());
+	if (type_ != DropdownType::kPictorial) {
+		set_label(_("Error"));
+	} else {
+		set_image(g_gr->images().get("images/ui_basic/different.png"));
+	}
+}
+
 void BaseDropdown::set_enabled(bool on) {
 	is_enabled_ = on;
 	set_can_focus(on);
@@ -213,6 +257,7 @@
 }
 
 void BaseDropdown::clear() {
+	close();
 	list_->clear();
 	current_selection_ = list_->selection_index();
 	list_->set_size(list_->get_w(), 0);
@@ -239,7 +284,7 @@
 	                            /** TRANSLATORS: Selection in Dropdown menus. */
 	                            pgettext("dropdown", "Not Selected");
 
-	if (type_ == DropdownType::kTextual) {
+	if (type_ != DropdownType::kPictorial) {
 		if (label_.empty()) {
 			display_button_.set_title(name);
 		} else {
@@ -268,19 +313,29 @@
 		return;
 	}
 	list_->set_visible(!list_->is_visible());
+	if (type_ != DropdownType::kTextual) {
+		display_button_.set_perm_pressed(list_->is_visible());
+	}
 	if (list_->is_visible()) {
 		list_->move_to_top();
 		focus();
+		Notifications::publish(NoteDropdown(id_));
 	}
 	// Make sure that the list covers and deactivates the elements below it
 	set_layout_toplevel(list_->is_visible());
 }
 
+void BaseDropdown::close() {
+	if (is_expanded()) {
+		toggle_list();
+	}
+}
+
 bool BaseDropdown::is_mouse_away() const {
-	return (get_mouse_position().x + mouse_tolerance_) < 0 ||
-	       get_mouse_position().x > (list_->get_w() + mouse_tolerance_) ||
-	       (get_mouse_position().y + mouse_tolerance_ / 2) < 0 ||
-	       get_mouse_position().y > (get_h() + list_->get_h() + mouse_tolerance_);
+	return (get_mouse_position().x + mouse_tolerance_) < list_offset_x_ ||
+	       get_mouse_position().x > (list_offset_x_ + list_->get_w() + mouse_tolerance_) ||
+	       (get_mouse_position().y + mouse_tolerance_ / 2) < list_offset_y_ ||
+	       get_mouse_position().y > (list_offset_y_ + get_h() + list_->get_h() + mouse_tolerance_);
 }
 
 bool BaseDropdown::handle_key(bool down, SDL_Keysym code) {

=== modified file 'src/ui_basic/dropdown.h'
--- src/ui_basic/dropdown.h	2017-04-21 09:56:42 +0000
+++ src/ui_basic/dropdown.h	2017-06-26 14:45:57 +0000
@@ -27,14 +27,26 @@
 
 #include "graphic/graphic.h"
 #include "graphic/image.h"
+#include "notifications/note_ids.h"
+#include "notifications/notifications.h"
 #include "ui_basic/box.h"
 #include "ui_basic/button.h"
 #include "ui_basic/listselect.h"
 #include "ui_basic/panel.h"
 
 namespace UI {
-
-enum class DropdownType { kTextual, kPictorial };
+// We use this to make sure that only 1 dropdown is open at the same time.
+struct NoteDropdown {
+	CAN_BE_SENT_AS_NOTE(NoteId::Dropdown)
+
+	int id;
+
+	explicit NoteDropdown(int init_id) : id(init_id) {
+	}
+};
+
+/// The narrow textual dropdown omits the extra push button
+enum class DropdownType { kTextual, kTextualNarrow, kPictorial };
 
 /// Implementation for a dropdown menu that lets the user select a value.
 class BaseDropdown : public Panel {
@@ -63,6 +75,7 @@
 	~BaseDropdown();
 
 public:
+	/// An entry was selected
 	boost::signals2::signal<void()> selected;
 
 	/// \return true if an element has been selected from the list
@@ -72,9 +85,15 @@
 	/// and displayed on the display button.
 	void set_label(const std::string& text);
 
+	/// Sets the image for the display button (for pictorial dropdowns).
+	void set_image(const Image* image);
+
 	/// Sets the tooltip for the display button.
 	void set_tooltip(const std::string& text);
 
+	/// Displays an error message on the button instead of the current selection.
+	void set_errored(const std::string& error_message);
+
 	/// Enables/disables the dropdown selection.
 	void set_enabled(bool on);
 
@@ -142,12 +161,22 @@
 	void set_value();
 	/// Toggles the dropdown list on and off.
 	void toggle_list();
+	/// Toggle the list closed if the dropdown is currently expanded.
+	void close();
 
 	/// Returns true if the mouse pointer left the vicinity of the dropdown.
 	bool is_mouse_away() const;
 
+	/// Give each dropdown a unique ID
+	static int next_id_;
+	const int id_;
+	std::unique_ptr<Notifications::Subscriber<NoteDropdown>> subscriber_;
+
+	// Dimensions
 	int max_list_height_;
 	int list_width_;
+	int list_offset_x_;
+	int list_offset_y_;
 	int button_dimension_;
 	const int mouse_tolerance_;  // Allow mouse outside the panel a bit before autocollapse
 	UI::Box button_box_;

=== modified file 'src/ui_basic/listselect.cc'
--- src/ui_basic/listselect.cc	2017-06-15 16:34:58 +0000
+++ src/ui_basic/listselect.cc	2017-06-26 14:45:57 +0000
@@ -359,9 +359,9 @@
 	if (selection_mode_ == ListselectLayout::kDropdown) {
 		RGBAColor black(0, 0, 0, 255);
 		//  top edge
-		dst.brighten_rect(Recti(0, 0, get_w(), 2), BUTTON_EDGE_BRIGHT_FACTOR / 4);
+		dst.brighten_rect(Recti(0, 0, get_w(), 2), BUTTON_EDGE_BRIGHT_FACTOR);
 		//  left edge
-		dst.brighten_rect(Recti(0, 0, 2, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
+		dst.brighten_rect(Recti(0, 2, 2, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
 		//  bottom edge
 		dst.fill_rect(Recti(2, get_h() - 2, get_eff_w() - 2, 1), black);
 		dst.fill_rect(Recti(1, get_h() - 1, get_eff_w() - 1, 1), black);

=== modified file 'src/ui_fsmenu/launch_game.cc'
--- src/ui_fsmenu/launch_game.cc	2017-05-18 06:52:04 +0000
+++ src/ui_fsmenu/launch_game.cc	2017-06-26 14:45:57 +0000
@@ -167,7 +167,7 @@
 					                            t->get_string("description"));
 				}
 			} catch (LuaTableKeyError& e) {
-				log("LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(),
+				log("Launch Game: Error loading win condition: %s %s\n", win_condition_script.c_str(),
 				    e.what());
 			}
 		}
@@ -177,9 +177,8 @@
 		                    "could not be loaded.")) %
 		    settings_->settings().mapfilename)
 		      .str();
-		win_condition_dropdown_.set_label(_("Error"));
-		win_condition_dropdown_.set_tooltip(error_message);
-		log("LaunchSPG: Exception: %s %s\n", error_message.c_str(), e.what());
+		win_condition_dropdown_.set_errored(error_message);
+		log("Launch Game: Exception: %s %s\n", error_message.c_str(), e.what());
 	}
 }
 
@@ -202,8 +201,8 @@
 			}
 		}
 	} catch (LuaTableKeyError& e) {
-		log(
-		   "LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(), e.what());
+		log("Launch Game: Error loading win condition: %s %s\n", win_condition_script.c_str(),
+		    e.what());
 	}
 	if (!is_usable) {
 		t.reset(nullptr);

=== modified file 'src/ui_fsmenu/launch_mpg.cc'
--- src/ui_fsmenu/launch_mpg.cc	2017-05-18 06:52:04 +0000
+++ src/ui_fsmenu/launch_mpg.cc	2017-06-26 14:45:57 +0000
@@ -181,7 +181,7 @@
 	map_info_.set_text(_("The host has not yet selected a map or saved game."));
 
 	mpsg_ = new MultiPlayerSetupGroup(
-	   this, get_w() / 50, get_h() / 8, get_w() * 57 / 80, get_h(), settings, butw_, buth_);
+	   this, get_w() / 50, change_map_or_save_.get_y(), get_w() * 57 / 80, get_h(), settings, buth_);
 
 	// If we are the host, open the map or save selection menu at startup
 	if (settings_->settings().usernum == 0 && settings_->settings().mapname.empty()) {
@@ -355,6 +355,8 @@
  * buttons and text.
  */
 void FullscreenMenuLaunchMPG::refresh() {
+	// TODO(GunChleoc): Investigate what we can handle with NoteGameSettings. Maybe we can get rid of
+	// refresh() and thus think().
 	const GameSettings& settings = settings_->settings();
 
 	if (settings.mapfilename != filename_proof_) {
@@ -425,8 +427,6 @@
 		}
 		win_condition_dropdown_.set_enabled(false);
 	}
-	// Update the multi player setup group
-	mpsg_->refresh();
 }
 
 /**
@@ -443,16 +443,21 @@
 	map.set_filename(settings.mapfilename);
 	ml->preload_map(true);
 	Widelands::PlayerNumber const nrplayers = map.get_nrplayers();
+	if (settings.players.size() != nrplayers) {
+		// Due to asynchronous notifications, the client can crash when and update is missing and the
+		// number of players is wrong.
+		return;
+	}
 	for (uint8_t i = 0; i < nrplayers; ++i) {
 		settings_->set_player_tribe(i, map.get_scenario_player_tribe(i + 1));
 		settings_->set_player_closeable(i, map.get_scenario_player_closeable(i + 1));
 		std::string ai(map.get_scenario_player_ai(i + 1));
 		if (!ai.empty()) {
-			settings_->set_player_state(i, PlayerSettings::stateComputer);
+			settings_->set_player_state(i, PlayerSettings::State::kComputer);
 			settings_->set_player_ai(i, ai);
-		} else if (settings.players.at(i).state != PlayerSettings::stateHuman &&
-		           settings.players.at(i).state != PlayerSettings::stateOpen) {
-			settings_->set_player_state(i, PlayerSettings::stateOpen);
+		} else if (settings.players.at(i).state != PlayerSettings::State::kHuman &&
+		           settings.players.at(i).state != PlayerSettings::State::kOpen) {
+			settings_->set_player_state(i, PlayerSettings::State::kOpen);
 		}
 	}
 }
@@ -470,22 +475,25 @@
 	std::string player_save_tribe[kMaxPlayers];
 	std::string player_save_ai[kMaxPlayers];
 
-	uint8_t i = 1;
-	for (; i <= nr_players_; ++i) {
+	for (uint8_t i = 1; i <= nr_players_; ++i) {
+		Section* s = prof.get_section((boost::format("player_%u") % cast_unsigned(i)).str());
+		if (s == nullptr) {
+			// Due to asynchronous notifications, the client can crash on savegame change when number
+			// of players goes down. So, we abort if the section does not exists to prevent crashes.
+			return;
+		}
+		player_save_name[i - 1] = s->get_string("name");
+		player_save_tribe[i - 1] = s->get_string("tribe");
+		player_save_ai[i - 1] = s->get_string("ai");
+
 		infotext += "\n* ";
-		Section& s =
-		   prof.get_safe_section((boost::format("player_%u") % static_cast<unsigned int>(i)).str());
-		player_save_name[i - 1] = s.get_string("name");
-		player_save_tribe[i - 1] = s.get_string("tribe");
-		player_save_ai[i - 1] = s.get_string("ai");
-
 		infotext += (boost::format(_("Player %u")) % static_cast<unsigned int>(i)).str();
 		if (player_save_tribe[i - 1].empty()) {
 			std::string closed_string = (boost::format("<%s>") % _("closed")).str();
 			infotext += ":\n    ";
 			infotext += closed_string;
 			// Close the player
-			settings_->set_player_state(i - 1, PlayerSettings::stateClosed);
+			settings_->set_player_state(i - 1, PlayerSettings::State::kClosed);
 			continue;  // if tribe is empty, the player does not exist
 		}
 
@@ -495,10 +503,10 @@
 
 		if (player_save_ai[i - 1].empty()) {
 			// Assure that player is open
-			if (settings_->settings().players.at(i - 1).state != PlayerSettings::stateHuman)
-				settings_->set_player_state(i - 1, PlayerSettings::stateOpen);
+			if (settings_->settings().players.at(i - 1).state != PlayerSettings::State::kHuman)
+				settings_->set_player_state(i - 1, PlayerSettings::State::kOpen);
 		} else {
-			settings_->set_player_state(i - 1, PlayerSettings::stateComputer);
+			settings_->set_player_state(i - 1, PlayerSettings::State::kComputer);
 			settings_->set_player_ai(i - 1, player_save_ai[i - 1]);
 		}
 
@@ -563,7 +571,9 @@
 	            "\n";
 	infotext +=
 	   std::string("• ") +
-	   (boost::format(ngettext("%u Player", "%u Players", nr_players_)) % nr_players_).str() + "\n";
+	   (boost::format(ngettext("%u Player", "%u Players", nr_players_)) % cast_unsigned(nr_players_))
+	      .str() +
+	   "\n";
 	if (settings_->settings().scenario)
 		infotext += std::string("• ") + (boost::format(_("Scenario mode selected"))).str() + "\n";
 	infotext += "\n";

=== modified file 'src/ui_fsmenu/launch_mpg.h'
--- src/ui_fsmenu/launch_mpg.h	2017-02-10 14:12:36 +0000
+++ src/ui_fsmenu/launch_mpg.h	2017-06-26 14:45:57 +0000
@@ -23,6 +23,7 @@
 #include <memory>
 #include <string>
 
+#include "logic/game_settings.h"
 #include "ui_basic/button.h"
 #include "ui_basic/dropdown.h"
 #include "ui_basic/multilinetextarea.h"

=== modified file 'src/ui_fsmenu/launch_spg.cc'
--- src/ui_fsmenu/launch_spg.cc	2017-05-04 05:08:14 +0000
+++ src/ui_fsmenu/launch_spg.cc	2017-06-26 14:45:57 +0000
@@ -210,8 +210,8 @@
 	for (uint8_t i = 0; i < nr_players_; ++i) {
 		pos_[i]->set_visible(true);
 		const PlayerSettings& player = settings.players[i];
-		pos_[i]->set_enabled(!is_scenario_ && (player.state == PlayerSettings::stateOpen ||
-		                                       player.state == PlayerSettings::stateComputer));
+		pos_[i]->set_enabled(!is_scenario_ && (player.state == PlayerSettings::State::kOpen ||
+		                                       player.state == PlayerSettings::State::kComputer));
 	}
 	for (uint32_t i = nr_players_; i < kMaxPlayers; ++i)
 		pos_[i]->set_visible(false);
@@ -291,14 +291,14 @@
 	// Check if a still valid place is open.
 	for (uint8_t i = 0; i < newplayernumber; ++i) {
 		PlayerSettings position = settings.players.at(i);
-		if (position.state == PlayerSettings::stateOpen) {
+		if (position.state == PlayerSettings::State::kOpen) {
 			switch_to_position(i);
 			return;
 		}
 	}
 
 	// Kick player 1 and take the position
-	settings_->set_player_state(0, PlayerSettings::stateClosed);
-	settings_->set_player_state(0, PlayerSettings::stateOpen);
+	settings_->set_player_state(0, PlayerSettings::State::kClosed);
+	settings_->set_player_state(0, PlayerSettings::State::kOpen);
 	switch_to_position(0);
 }

=== modified file 'src/wui/multiplayersetupgroup.cc'
--- src/wui/multiplayersetupgroup.cc	2017-05-03 07:24:06 +0000
+++ src/wui/multiplayersetupgroup.cc	2017-06-26 14:45:57 +0000
@@ -19,8 +19,10 @@
 
 #include "wui/multiplayersetupgroup.h"
 
+#include <memory>
 #include <string>
 
+#include <boost/algorithm/string.hpp>
 #include <boost/format.hpp>
 #include <boost/lexical_cast.hpp>
 
@@ -36,419 +38,551 @@
 #include "logic/map_objects/tribes/tribe_descr.h"
 #include "logic/map_objects/tribes/tribes.h"
 #include "logic/player.h"
+#include "logic/widelands.h"
 #include "ui_basic/button.h"
-#include "ui_basic/checkbox.h"
 #include "ui_basic/dropdown.h"
-#include "ui_basic/icon.h"
+#include "ui_basic/mouse_constants.h"
 #include "ui_basic/scrollbar.h"
 #include "ui_basic/textarea.h"
 
+#define AI_NAME_PREFIX "ai" AI_NAME_SEPARATOR
+
+constexpr int kPadding = 4;
+
+/// Holds the info and dropdown menu for a connected client
 struct MultiPlayerClientGroup : public UI::Box {
 	MultiPlayerClientGroup(UI::Panel* const parent,
-	                       uint8_t id,
-	                       int32_t const /* x */,
-	                       int32_t const /* y */,
 	                       int32_t const w,
 	                       int32_t const h,
+	                       PlayerSlot id,
 	                       GameSettingsProvider* const settings)
-	   : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h),
-	     type_icon(nullptr),
-	     type(nullptr),
+	   : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h, kPadding),
+	     slot_dropdown_(this, 0, 0, h, 200, h, _("Role"), UI::DropdownType::kPictorial),
+	     // Name needs to be initialized after the dropdown, otherwise the layout function will
+	     // crash.
+	     name(new UI::Textarea(this, 0, 0, w - h - UI::Scrollbar::kSize * 11 / 5, h)),
 	     s(settings),
 	     id_(id),
-	     save_(-2) {
+	     slot_selection_locked_(false) {
 		set_size(w, h);
-		name = new UI::Textarea(this, 0, 0, w - h - UI::Scrollbar::kSize * 11 / 5, h);
-		add(name);
-		// Either Button if changeable OR text if not
-		if (id == settings->settings().usernum) {  // Our Client
-			type = new UI::Button(
-			   this, "client_type", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
-			type->sigclicked.connect(
-			   boost::bind(&MultiPlayerClientGroup::toggle_type, boost::ref(*this)));
-			add(type);
-		} else {  // just a shown client
-			type_icon = new UI::Icon(
-			   this, 0, 0, h, h, g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png"));
-			add(type_icon);
-		}
-	}
-
-	/// Switch human players and spectator
-	void toggle_type() {
-		UserSettings us = s->settings().users.at(id_);
-		int16_t p = us.position;
-		if (p == UserSettings::none())
-			p = -1;
-
-		for (++p; p < static_cast<int16_t>(s->settings().players.size()); ++p) {
-			if (s->settings().players.at(p).state == PlayerSettings::stateHuman ||
-			    s->settings().players.at(p).state == PlayerSettings::stateOpen) {
-				s->set_player_number(p);
-				return;
-			}
-		}
-		s->set_player_number(UserSettings::none());
-	}
-
-	/// Care about visibility and current values
-	void refresh() {
-		UserSettings us = s->settings().users.at(id_);
-		if (us.position == UserSettings::not_connected()) {
-			name->set_text((boost::format("<%s>") % _("free")).str());
-			if (type)
-				type->set_visible(false);
-			else
-				type_icon->set_visible(false);
-		} else {
-			name->set_text(us.name);
-			if (save_ != us.position) {
-				const Image* position_image;
-				std::string temp_tooltip;
-				if (us.position < UserSettings::highest_playernum()) {
-					position_image =
-					   playercolor_image(us.position, "images/players/genstats_player.png");
-					temp_tooltip =
-					   (boost::format(_("Player %u")) % static_cast<unsigned int>(us.position + 1))
-					      .str();
-				} else {
-					position_image = g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png");
-					temp_tooltip = _("Spectator");
-				}
-
-				// Either Button if changeable OR text if not
-				if (id_ == s->settings().usernum) {
-					type->set_pic(position_image);
-					type->set_tooltip(temp_tooltip);
-					type->set_visible(true);
-				} else {
-					type_icon->set_icon(position_image);
-					type_icon->set_tooltip(temp_tooltip);
-					type_icon->set_visible(true);
-				}
-				save_ = us.position;
-			}
-		}
-	}
-
-	UI::Textarea* name;
-	UI::Icon* type_icon;
-	UI::Button* type;
+
+		add(&slot_dropdown_);
+		add(name, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+
+		slot_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat);
+		slot_dropdown_.selected.connect(
+		   boost::bind(&MultiPlayerClientGroup::set_slot, boost::ref(*this)));
+
+		update();
+		layout();
+
+		subscriber_ =
+		   Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings& note) {
+			   switch (note.action) {
+			   case NoteGameSettings::Action::kMap:
+				   /// In case the client gets kicked off its slot due to number of player slots in the
+				   /// map
+				   update();
+				   break;
+			   case NoteGameSettings::Action::kUser:
+				   /// Player slot might have been closed, bumping the client to observer status
+				   if (id_ == note.usernum || note.usernum == UserSettings::none()) {
+					   update();
+				   }
+				   break;
+			   case NoteGameSettings::Action::kPlayer:
+				   break;
+			   }
+			});
+	}
+
+	/// Update dropdown sizes
+	void layout() override {
+		UI::Box::layout();
+		slot_dropdown_.set_height(g_gr->get_yres() * 3 / 4);
+	}
+
+	/// This will update the client's player slot with the value currently selected in the slot
+	/// dropdown.
+	void set_slot() {
+		const GameSettings& settings = s->settings();
+		if (id_ != settings.usernum) {
+			return;
+		}
+		slot_selection_locked_ = true;
+		if (slot_dropdown_.has_selection()) {
+			const uint8_t new_slot = slot_dropdown_.get_selected();
+			if (new_slot != settings.users.at(id_).position) {
+				s->set_player_number(slot_dropdown_.get_selected());
+			}
+		}
+		slot_selection_locked_ = false;
+	}
+
+	/// Rebuild the slot dropdown from the server settings. This will keep the host and client UIs in
+	/// sync.
+	void rebuild_slot_dropdown(const GameSettings& settings) {
+		if (slot_selection_locked_) {
+			return;
+		}
+		const UserSettings& user_setting = settings.users.at(id_);
+
+		slot_dropdown_.clear();
+		for (PlayerSlot slot = 0; slot < settings.players.size(); ++slot) {
+			if (settings.players.at(slot).state == PlayerSettings::State::kHuman ||
+			    settings.players.at(slot).state == PlayerSettings::State::kOpen) {
+				slot_dropdown_.add((boost::format(_("Player %u")) % cast_unsigned(slot + 1)).str(),
+				                   slot, playercolor_image(slot, "images/players/genstats_player.png"),
+				                   slot == user_setting.position);
+			}
+		}
+		slot_dropdown_.add(_("Spectator"), UserSettings::none(),
+		                   g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png"),
+		                   user_setting.position == UserSettings::none());
+		slot_dropdown_.set_visible(true);
+		slot_dropdown_.set_enabled(id_ == settings.usernum);
+	}
+
+	/// Take care of visibility and current values
+	void update() {
+		const GameSettings& settings = s->settings();
+		const UserSettings& user_setting = settings.users.at(id_);
+
+		if (user_setting.position == UserSettings::not_connected()) {
+			set_visible(false);
+			return;
+		}
+
+		name->set_text(user_setting.name);
+		rebuild_slot_dropdown(settings);
+	}
+
+	UI::Dropdown<uintptr_t> slot_dropdown_;  /// Select the player slot.
+	UI::Textarea* name;                      /// Client nick name
 	GameSettingsProvider* const s;
-	uint8_t const id_;
-	int16_t save_;  // saved position to check rewrite need.
+	uint8_t const id_;            /// User number
+	bool slot_selection_locked_;  // Ensure that dropdowns will close on selection.
+	std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_;
 };
 
+/// Holds the dropdown menus for a player slot
 struct MultiPlayerPlayerGroup : public UI::Box {
 	MultiPlayerPlayerGroup(UI::Panel* const parent,
-	                       uint8_t id,
+	                       PlayerSlot id,
 	                       int32_t const /* x */,
 	                       int32_t const /* y */,
 	                       int32_t const w,
 	                       int32_t const h,
 	                       GameSettingsProvider* const settings,
 	                       NetworkPlayerSettingsBackend* const npsb)
-	   : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h),
-	     player(nullptr),
-	     type(nullptr),
-	     init(nullptr),
+	   : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h, kPadding / 2),
 	     s(settings),
 	     n(npsb),
 	     id_(id),
+	     player(this,
+	            "player",
+	            0,
+	            0,
+	            h,
+	            h,
+	            g_gr->images().get("images/ui_basic/but1.png"),
+	            playercolor_image(id, "images/players/player_position_menu.png"),
+	            (boost::format(_("Player %u")) % cast_unsigned(id_ + 1)).str(),
+	            UI::Button::Style::kFlat),
+	     type_dropdown_(this, 0, 0, 50, 200, h, _("Type"), UI::DropdownType::kPictorial),
 	     tribes_dropdown_(this, 0, 0, 50, 200, h, _("Tribe"), UI::DropdownType::kPictorial),
-	     last_state_(PlayerSettings::stateClosed),
-	     last_player_amount_(0) {
+	     init_dropdown_(
+	        this, 0, 0, w - 4 * h - 3 * kPadding, 200, h, "", UI::DropdownType::kTextualNarrow),
+	     team_dropdown_(this, 0, 0, h, 200, h, _("Team"), UI::DropdownType::kPictorial),
+	     last_state_(PlayerSettings::State::kClosed),
+	     type_selection_locked_(false),
+	     tribe_selection_locked_(false),
+	     init_selection_locked_(false),
+	     team_selection_locked_(false) {
 		set_size(w, h);
-		tribes_dropdown_.set_visible(false);
-		tribes_dropdown_.set_enabled(false);
+
+		player.set_disable_style(UI::ButtonDisableStyle::kFlat);
+		player.set_enabled(false);
+
+		type_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat);
+		tribes_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat);
+		init_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat);
+		team_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat);
+
+		type_dropdown_.selected.connect(
+		   boost::bind(&MultiPlayerPlayerGroup::set_type, boost::ref(*this)));
 		tribes_dropdown_.selected.connect(
 		   boost::bind(&MultiPlayerPlayerGroup::set_tribe_or_shared_in, boost::ref(*this)));
+		init_dropdown_.selected.connect(
+		   boost::bind(&MultiPlayerPlayerGroup::set_init, boost::ref(*this)));
+		team_dropdown_.selected.connect(
+		   boost::bind(&MultiPlayerPlayerGroup::set_team, boost::ref(*this)));
 
-		const Image* player_image = playercolor_image(id, "images/players/player_position_menu.png");
-		assert(player_image);
-		player = new UI::Icon(this, 0, 0, h, h, player_image);
-		add(player);
-		type = new UI::Button(
-		   this, "player_type", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
-		type->sigclicked.connect(
-		   boost::bind(&MultiPlayerPlayerGroup::toggle_type, boost::ref(*this)));
-		add(type);
+		add_space(0);
+		add(&player);
+		add(&type_dropdown_);
 		add(&tribes_dropdown_);
-		init = new UI::Button(this, "player_init", 0, 0, w - 4 * h, h,
-		                      g_gr->images().get("images/ui_basic/but1.png"), "");
-		init->sigclicked.connect(
-		   boost::bind(&MultiPlayerPlayerGroup::toggle_init, boost::ref(*this)));
-		add(init);
-		team = new UI::Button(
-		   this, "player_team", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
-		team->sigclicked.connect(
-		   boost::bind(&MultiPlayerPlayerGroup::toggle_team, boost::ref(*this)));
-		add(team);
-	}
-
-	/// Toggle through the types
-	void toggle_type() {
-		n->toggle_type(id_);
+		add(&init_dropdown_);
+		add(&team_dropdown_);
+		add_space(0);
+
+		subscriber_ =
+		   Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings& note) {
+			   switch (note.action) {
+			   case NoteGameSettings::Action::kMap:
+				   // We don't care about map updates, since we receive enough notifications for the
+				   // slots.
+				   break;
+			   default:
+				   if (s->settings().players.empty()) {
+					   // No map/savegame yet
+					   return;
+				   }
+				   if (id_ == note.position ||
+				       s->settings().players[id_].state == PlayerSettings::State::kShared) {
+					   update();
+				   }
+			   }
+			});
+
+		// Init dropdowns
+		update();
+		layout();
+	}
+
+	/// Update dropdown sizes
+	void layout() override {
+		const int max_height = g_gr->get_yres() * 3 / 4;
+		type_dropdown_.set_height(max_height);
+		tribes_dropdown_.set_height(max_height);
+		init_dropdown_.set_height(max_height);
+		team_dropdown_.set_height(max_height);
+		UI::Box::layout();
+	}
+
+	/// This will update the game settings for the type with the value
+	/// currently selected in the type dropdown.
+	void set_type() {
+		if (!s->can_change_player_state(id_)) {
+			return;
+		}
+		type_selection_locked_ = true;
+		if (type_dropdown_.has_selection()) {
+			const std::string& selected = type_dropdown_.get_selected();
+			PlayerSettings::State state = PlayerSettings::State::kComputer;
+			if (selected == "closed") {
+				state = PlayerSettings::State::kClosed;
+			} else if (selected == "open") {
+				state = PlayerSettings::State::kOpen;
+			} else if (selected == "shared_in") {
+				state = PlayerSettings::State::kShared;
+			} else {
+				if (selected == AI_NAME_PREFIX "random") {
+					n->set_player_ai(id_, "", true);
+				} else {
+					if (boost::starts_with(selected, AI_NAME_PREFIX)) {
+						std::vector<std::string> parts;
+						boost::split(parts, selected, boost::is_any_of(AI_NAME_SEPARATOR));
+						assert(parts.size() == 2);
+						n->set_player_ai(id_, parts[1], false);
+					} else {
+						throw wexception("Unknown player state: %s\n", selected.c_str());
+					}
+				}
+			}
+			n->set_player_state(id_, state);
+		}
+		type_selection_locked_ = false;
+	}
+
+	/// Rebuild the type dropdown from the server settings. This will keep the host and client UIs in
+	/// sync.
+	void rebuild_type_dropdown(const GameSettings& settings) {
+		if (type_selection_locked_) {
+			return;
+		}
+		type_dropdown_.clear();
+		// AIs
+		for (const auto* impl : ComputerPlayer::get_implementations()) {
+			type_dropdown_.add(_(impl->descname),
+			                   (boost::format(AI_NAME_PREFIX "%s") % impl->name).str(),
+			                   g_gr->images().get(impl->icon_filename), false, _(impl->descname));
+		}
+		/** TRANSLATORS: This is the name of an AI used in the game setup screens */
+		type_dropdown_.add(_("Random AI"), AI_NAME_PREFIX "random",
+		                   g_gr->images().get("images/ai/ai_random.png"), false, _("Random AI"));
+
+		// Slot state. Only add shared_in if there are viable slots
+		if (settings.is_shared_usable(id_, settings.find_shared(id_))) {
+			type_dropdown_.add(_("Shared in"), "shared_in",
+			                   g_gr->images().get("images/ui_fsmenu/shared_in.png"), false,
+			                   _("Shared in"));
+		}
+
+		// Do not close a player in savegames or scenarios
+		if (!settings.uncloseable(id_)) {
+			type_dropdown_.add(_("Closed"), "closed", g_gr->images().get("images/ui_basic/stop.png"),
+			                   false, _("Closed"));
+		}
+
+		type_dropdown_.add(
+		   _("Open"), "open", g_gr->images().get("images/ui_basic/continue.png"), false, _("Open"));
+
+		type_dropdown_.set_enabled(s->can_change_player_state(id_));
+
+		// Now select the entry according to server settings
+		const PlayerSettings& player_setting = settings.players[id_];
+		if (player_setting.state == PlayerSettings::State::kHuman) {
+			type_dropdown_.set_image(g_gr->images().get("images/wui/stats/genstats_nrworkers.png"));
+			type_dropdown_.set_tooltip((boost::format(_("%1%: %2%")) % _("Type") % _("Human")).str());
+		} else if (player_setting.state == PlayerSettings::State::kClosed) {
+			type_dropdown_.select("closed");
+		} else if (player_setting.state == PlayerSettings::State::kOpen) {
+			type_dropdown_.select("open");
+		} else if (player_setting.state == PlayerSettings::State::kShared) {
+			type_dropdown_.select("shared_in");
+		} else {
+			if (player_setting.state == PlayerSettings::State::kComputer) {
+				if (player_setting.ai.empty()) {
+					type_dropdown_.set_errored(_("No AI"));
+				} else {
+					if (player_setting.random_ai) {
+						type_dropdown_.select(AI_NAME_PREFIX "random");
+					} else {
+						const ComputerPlayer::Implementation* impl =
+						   ComputerPlayer::get_implementation(player_setting.ai);
+						type_dropdown_.select((boost::format(AI_NAME_PREFIX "%s") % impl->name).str());
+					}
+				}
+			}
+		}
+	}
+
+	/// Whether the client who is running the UI is allowed to change the tribe for this player slot.
+	bool has_tribe_access() {
+		return s->settings().players[id_].state == PlayerSettings::State::kShared ?
+		          s->can_change_player_init(id_) :
+		          s->can_change_player_tribe(id_);
 	}
 
 	/// This will update the game settings for the tribe or shared_in with the value
 	/// currently selected in the tribes dropdown.
 	void set_tribe_or_shared_in() {
-		n->set_block_tribe_selection(true);
-		tribes_dropdown_.set_disable_style(s->settings().players[id_].state ==
-		                                         PlayerSettings::stateShared ?
+		if (!has_tribe_access()) {
+			return;
+		}
+		const PlayerSettings& player_settings = s->settings().players[id_];
+		tribe_selection_locked_ = true;
+		tribes_dropdown_.set_disable_style(player_settings.state == PlayerSettings::State::kShared ?
 		                                      UI::ButtonDisableStyle::kPermpressed :
-		                                      UI::ButtonDisableStyle::kMonochrome);
+		                                      UI::ButtonDisableStyle::kFlat);
 		if (tribes_dropdown_.has_selection()) {
-			if (s->settings().players[id_].state == PlayerSettings::stateShared) {
-				n->set_shared_in(
+			if (player_settings.state == PlayerSettings::State::kShared) {
+				n->set_player_shared(
 				   id_, boost::lexical_cast<unsigned int>(tribes_dropdown_.get_selected()));
 			} else {
-				n->set_tribe(id_, tribes_dropdown_.get_selected());
-			}
-		}
-		n->set_block_tribe_selection(false);
-	}
-
-	/// Toggle through the initializations
-	void toggle_init() {
-		n->toggle_init(id_);
-	}
-
-	/// Toggle through the teams
-	void toggle_team() {
-		n->toggle_team(id_);
-	}
-
-	/// Helper function to cast shared_in for use in the dropdown.
-	const std::string shared_in_as_string(uint8_t shared_in) {
-		return boost::lexical_cast<std::string>(static_cast<unsigned int>(shared_in));
-	}
-
-	/// Update the tribes dropdown from the server settings if the server setting changed.
-	/// This will keep the host and client UIs in sync.
-	void update_tribes_dropdown(const PlayerSettings& player_setting) {
-		if (player_setting.state == PlayerSettings::stateClosed ||
-		    player_setting.state == PlayerSettings::stateOpen) {
+				n->set_player_tribe(id_, tribes_dropdown_.get_selected());
+			}
+		}
+		tribe_selection_locked_ = false;
+	}
+
+	/// Rebuild the tribes dropdown from the server settings. This will keep the host and client UIs
+	/// in sync.
+	void rebuild_tribes_dropdown(const GameSettings& settings) {
+		if (tribe_selection_locked_) {
+			return;
+		}
+		const PlayerSettings& player_setting = settings.players[id_];
+		tribes_dropdown_.clear();
+		if (player_setting.state == PlayerSettings::State::kShared) {
+			for (size_t i = 0; i < settings.players.size(); ++i) {
+				if (i != id_) {
+					// Do not add players that are also shared_in or closed.
+					const PlayerSettings& other_setting = settings.players[i];
+					if (!PlayerSettings::can_be_shared(other_setting.state)) {
+						continue;
+					}
+
+					const Image* player_image =
+					   playercolor_image(i, "images/players/player_position_menu.png");
+					assert(player_image);
+					const std::string player_name =
+					   /** TRANSLATORS: This is an option in multiplayer setup for sharing
+					      another player's starting position. */
+					   (boost::format(_("Shared in Player %u")) % cast_unsigned(i + 1)).str();
+					tribes_dropdown_.add(player_name,
+					                     boost::lexical_cast<std::string>(cast_unsigned(i + 1)),
+					                     player_image, (i + 1) == player_setting.shared_in, player_name);
+				}
+			}
+			tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1);
+		} else {
+			{
+				i18n::Textdomain td("tribes");
+				for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
+					tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name,
+					                     g_gr->images().get(tribeinfo.icon), false, tribeinfo.tooltip);
+				}
+			}
+			tribes_dropdown_.add(pgettext("tribe", "Random"), "random",
+			                     g_gr->images().get("images/ui_fsmenu/random.png"), false,
+			                     _("The tribe will be selected at random"));
+			if (player_setting.random_tribe) {
+				tribes_dropdown_.select("random");
+			} else {
+				tribes_dropdown_.select(player_setting.tribe);
+			}
+		}
+		const bool has_access = has_tribe_access();
+		if (tribes_dropdown_.is_enabled() != has_access) {
+			tribes_dropdown_.set_enabled(has_access && tribes_dropdown_.size() > 1);
+		}
+		if (player_setting.state == PlayerSettings::State::kClosed ||
+		    player_setting.state == PlayerSettings::State::kOpen) {
 			return;
 		}
 		if (!tribes_dropdown_.is_visible()) {
 			tribes_dropdown_.set_visible(true);
 		}
-		if (!tribes_dropdown_.is_expanded() && !n->tribe_selection_blocked &&
-		    tribes_dropdown_.has_selection()) {
-			const std::string selected_tribe = tribes_dropdown_.get_selected();
-			if (player_setting.state == PlayerSettings::stateShared) {
-				const std::string shared_in = shared_in_as_string(player_setting.shared_in);
-				if (shared_in != selected_tribe) {
-					tribes_dropdown_.select(shared_in);
-				}
-			} else {
-				if (player_setting.random_tribe) {
-					if (selected_tribe != "random") {
-						tribes_dropdown_.select("random");
-					}
-				} else if (selected_tribe != player_setting.tribe) {
-					tribes_dropdown_.select(player_setting.tribe);
-				}
-			}
-		}
-	}
-
-	/// If the map was changed or the selection mode changed between shared_in and tribe, rebuild the
-	/// dropdown.
-	void rebuild_tribes_dropdown(const GameSettings& settings) {
-		const PlayerSettings& player_setting = settings.players[id_];
-
-		if (player_setting.state == PlayerSettings::stateClosed ||
-		    player_setting.state == PlayerSettings::stateOpen) {
-			return;
-		}
-
-		if (tribes_dropdown_.empty() || last_player_amount_ != settings.players.size() ||
-		    ((player_setting.state == PlayerSettings::stateShared ||
-		      last_state_ == PlayerSettings::stateShared) &&
-		     player_setting.state != last_state_)) {
-			tribes_dropdown_.clear();
-
-			// We need to see the playercolor if setting shared_in is disabled
-			tribes_dropdown_.set_disable_style(player_setting.state == PlayerSettings::stateShared ?
-			                                      UI::ButtonDisableStyle::kPermpressed :
-			                                      UI::ButtonDisableStyle::kMonochrome);
-
-			if (player_setting.state == PlayerSettings::stateShared) {
-				for (size_t i = 0; i < settings.players.size(); ++i) {
-					if (i != id_) {
-						// TODO(GunChleoc): Do not add players that are also shared_in.
-						const Image* player_image =
-						   playercolor_image(i, "images/players/player_position_menu.png");
-						assert(player_image);
-						const std::string player_name =
-						   /** TRANSLATORS: This is an option in multiplayer setup for sharing
-						      another player's starting position. */
-						   (boost::format(_("Shared in Player %u")) % static_cast<unsigned int>(i + 1))
-						      .str();
-						tribes_dropdown_.add(
-						   player_name, shared_in_as_string(i + 1), player_image, false, player_name);
-					}
-				}
-				int shared_in = 0;
-				while (shared_in == id_) {
-					++shared_in;
-				}
-				tribes_dropdown_.select(shared_in_as_string(shared_in + 1));
-				tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1);
-			} else {
-				{
-					i18n::Textdomain td("tribes");
-					for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
-						tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name,
-						                     g_gr->images().get(tribeinfo.icon), false,
-						                     tribeinfo.tooltip);
-					}
-				}
-				tribes_dropdown_.add(pgettext("tribe", "Random"), "random",
-				                     g_gr->images().get("images/ui_fsmenu/random.png"), false,
-				                     _("The tribe will be selected at random"));
-				if (player_setting.random_tribe) {
-					tribes_dropdown_.select("random");
-				} else {
-					tribes_dropdown_.select(player_setting.tribe);
-				}
-			}
-		}
-		last_player_amount_ = settings.players.size();
+	}
+
+	/// This will update the game settings for the initialization with the value
+	/// currently selected in the initialization dropdown.
+	void set_init() {
+		if (!s->can_change_player_init(id_)) {
+			return;
+		}
+		init_selection_locked_ = true;
+		if (init_dropdown_.has_selection()) {
+			n->set_player_init(id_, init_dropdown_.get_selected());
+		}
+		init_selection_locked_ = false;
+	}
+
+	/// Rebuild the init dropdown from the server settings. This will keep the host and client UIs in
+	/// sync.
+	void rebuild_init_dropdown(const GameSettings& settings) {
+		if (init_selection_locked_) {
+			return;
+		}
+
+		init_dropdown_.clear();
+		const PlayerSettings& player_setting = settings.players[id_];
+		if (settings.scenario) {
+			init_dropdown_.set_label(_("Scenario"));
+		} else if (settings.savegame) {
+			/** Translators: This is a game type */
+			init_dropdown_.set_label(_("Saved Game"));
+		} else {
+			init_dropdown_.set_label("");
+			i18n::Textdomain td("tribes");  // for translated initialisation
+			const TribeBasicInfo tribeinfo = Widelands::get_tribeinfo(player_setting.tribe);
+			for (size_t i = 0; i < tribeinfo.initializations.size(); ++i) {
+				const TribeBasicInfo::Initialization& addme = tribeinfo.initializations[i];
+				init_dropdown_.add(_(addme.descname), i, nullptr,
+				                   i == player_setting.initialization_index, _(addme.tooltip));
+			}
+		}
+
+		init_dropdown_.set_visible(true);
+		init_dropdown_.set_enabled(s->can_change_player_init(id_));
+	}
+
+	/// This will update the team settings with the value currently selected in the teams dropdown.
+	void set_team() {
+		team_selection_locked_ = true;
+		if (team_dropdown_.has_selection()) {
+			n->set_player_team(id_, team_dropdown_.get_selected());
+		}
+		team_selection_locked_ = false;
+	}
+
+	/// Rebuild the team dropdown from the server settings. This will keep the host and client UIs in
+	/// sync.
+	void rebuild_team_dropdown(const GameSettings& settings) {
+		if (team_selection_locked_) {
+			return;
+		}
+		const PlayerSettings& player_setting = settings.players[id_];
+		if (player_setting.state == PlayerSettings::State::kShared) {
+			team_dropdown_.set_visible(false);
+			team_dropdown_.set_enabled(false);
+			return;
+		}
+
+		team_dropdown_.clear();
+		team_dropdown_.add(_("No Team"), 0, g_gr->images().get("images/players/no_team.png"));
+#ifndef NDEBUG
+		const size_t no_of_team_colors = sizeof(kTeamColors) / sizeof(kTeamColors[0]);
+#endif
+		for (Widelands::TeamNumber t = 1; t <= settings.players.size() / 2; ++t) {
+			assert(t < no_of_team_colors);
+			team_dropdown_.add((boost::format(_("Team %d")) % cast_unsigned(t)).str(), t,
+			                   playercolor_image(kTeamColors[t], "images/players/team.png"));
+		}
+		team_dropdown_.select(player_setting.team);
+		team_dropdown_.set_visible(true);
+		team_dropdown_.set_enabled(s->can_change_player_team(id_));
 	}
 
 	/// Refresh all user interfaces
-	void refresh() {
+	void update() {
 		const GameSettings& settings = s->settings();
-
 		if (id_ >= settings.players.size()) {
 			set_visible(false);
 			return;
 		}
 
-		n->refresh(id_);
-
+		const PlayerSettings& player_setting = settings.players[id_];
+		rebuild_type_dropdown(settings);
 		set_visible(true);
 
-		const PlayerSettings& player_setting = settings.players[id_];
-		bool typeaccess = s->can_change_player_state(id_);
-		bool tribeaccess = s->can_change_player_tribe(id_);
-		bool const initaccess = s->can_change_player_init(id_);
-		bool teamaccess = s->can_change_player_team(id_);
-		type->set_enabled(typeaccess);
-
-		rebuild_tribes_dropdown(settings);
-
-		if (player_setting.state == PlayerSettings::stateClosed) {
-			type->set_tooltip(_("Closed"));
-			type->set_pic(g_gr->images().get("images/ui_basic/stop.png"));
-			team->set_visible(false);
-			team->set_enabled(false);
-			tribes_dropdown_.set_visible(false);
-			tribes_dropdown_.set_enabled(false);
-			init->set_visible(false);
-			init->set_enabled(false);
-			return;
-		} else if (player_setting.state == PlayerSettings::stateOpen) {
-			type->set_tooltip(_("Open"));
-			type->set_pic(g_gr->images().get("images/ui_basic/continue.png"));
-			team->set_visible(false);
-			team->set_enabled(false);
-			tribes_dropdown_.set_visible(false);
-			tribes_dropdown_.set_enabled(false);
-			init->set_visible(false);
-			init->set_enabled(false);
-			return;
-		} else if (player_setting.state == PlayerSettings::stateShared) {
-			type->set_tooltip(_("Shared in"));
-			type->set_pic(g_gr->images().get("images/ui_fsmenu/shared_in.png"));
-
-			update_tribes_dropdown(player_setting);
-
-			if (tribes_dropdown_.is_enabled() != initaccess) {
-				tribes_dropdown_.set_enabled(initaccess && !n->tribe_selection_blocked &&
-				                             tribes_dropdown_.size() > 1);
-			}
-
-			team->set_visible(false);
-			team->set_enabled(false);
-
+		if (player_setting.state == PlayerSettings::State::kClosed ||
+		    player_setting.state == PlayerSettings::State::kOpen) {
+			team_dropdown_.set_visible(false);
+			team_dropdown_.set_enabled(false);
+			tribes_dropdown_.set_visible(false);
+			tribes_dropdown_.set_enabled(false);
+			init_dropdown_.set_visible(false);
+			init_dropdown_.set_enabled(false);
 		} else {
-			std::string title;
-			std::string pic = "images/";
-			if (player_setting.state == PlayerSettings::stateComputer) {
-				if (player_setting.ai.empty()) {
-					title = _("Computer");
-					pic += "novalue.png";
-				} else {
-					if (player_setting.random_ai) {
-						/** TRANSLATORS: This is the name of an AI used in the game setup screens */
-						title = _("Random AI");
-						pic += "ai/ai_random.png";
-					} else {
-						const ComputerPlayer::Implementation* impl =
-						   ComputerPlayer::get_implementation(player_setting.ai);
-						title = _(impl->descname);
-						pic = impl->icon_filename;
-					}
-				}
-			} else {  // PlayerSettings::stateHuman
-				title = _("Human");
-				pic += "wui/stats/genstats_nrworkers.png";
-			}
-			type->set_tooltip(title.c_str());
-			type->set_pic(g_gr->images().get(pic));
-
-			update_tribes_dropdown(player_setting);
-
-			if (tribes_dropdown_.is_enabled() != tribeaccess) {
-				tribes_dropdown_.set_enabled(tribeaccess && !n->tribe_selection_blocked);
-			}
-
-			if (player_setting.team) {
-				team->set_title(std::to_string(static_cast<unsigned int>(player_setting.team)));
-			} else {
-				team->set_title("--");
-			}
-			team->set_visible(true);
-			team->set_enabled(teamaccess);
-		}
-		init->set_enabled(initaccess);
-		init->set_visible(true);
-
-		if (settings.scenario)
-			init->set_title(_("Scenario"));
-		else if (settings.savegame)
-			/** Translators: This is a game type */
-			init->set_title(_("Saved Game"));
-		else {
-			i18n::Textdomain td("tribes");  // for translated initialisation
-			for (const TribeBasicInfo& tribeinfo : settings.tribes) {
-				if (tribeinfo.name == player_setting.tribe) {
-					init->set_title(
-					   _(tribeinfo.initializations.at(player_setting.initialization_index).descname));
-					init->set_tooltip(
-					   _(tribeinfo.initializations.at(player_setting.initialization_index).tooltip));
-					break;
-				}
-			}
-		}
-		last_state_ = player_setting.state;
+			rebuild_tribes_dropdown(settings);
+			rebuild_init_dropdown(settings);
+			rebuild_team_dropdown(settings);
+		}
+
+		// Trigger update for the other players for shared_in mode when slots open and close
+		if (last_state_ != player_setting.state) {
+			last_state_ = player_setting.state;
+			for (PlayerSlot slot = 0; slot < s->settings().players.size(); ++slot) {
+				if (slot != id_) {
+					n->set_player_state(slot, settings.players[slot].state);
+				}
+			}
+		}
 	}
 
-	UI::Icon* player;
-	UI::Button* type;
-	UI::Button* init;
-	UI::Button* team;
 	GameSettingsProvider* const s;
 	NetworkPlayerSettingsBackend* const n;
-	uint8_t const id_;
+	PlayerSlot const id_;
+
+	UI::Button player;
+	UI::Dropdown<std::string>
+	   type_dropdown_;  /// Select who owns the slot (human, AI, open, closed, shared-in).
 	UI::Dropdown<std::string> tribes_dropdown_;  /// Select the tribe or shared_in player.
-	PlayerSettings::State last_state_;           /// The dropdown needs updating if this changes
-	size_t last_player_amount_;                  /// The dropdown needs rebuilding if this changes
+	UI::Dropdown<uintptr_t>
+	   init_dropdown_;  /// Select the initialization (Headquarters, Fortified Village etc.)
+	UI::Dropdown<uintptr_t> team_dropdown_;  /// Select the team number
+	PlayerSettings::State
+	   last_state_;  /// The dropdowns for the other slots need updating if this changes
+	/// Lock rebuilding dropdowns so that they can close on selection
+	bool type_selection_locked_;
+	bool tribe_selection_locked_;
+	bool init_selection_locked_;
+	bool team_selection_locked_;
+
+	std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_;
 };
 
 MultiPlayerSetupGroup::MultiPlayerSetupGroup(UI::Panel* const parent,
@@ -457,69 +591,41 @@
                                              int32_t const w,
                                              int32_t const h,
                                              GameSettingsProvider* const settings,
-                                             uint32_t /* butw */,
                                              uint32_t buth)
-   : UI::Panel(parent, x, y, w, h),
+   : UI::Box(parent, x, y, UI::Box::Horizontal, w, h, 8 * kPadding),
      s(settings),
      npsb(new NetworkPlayerSettingsBackend(s)),
-     clientbox(this, 0, buth, UI::Box::Vertical, w / 3, h - buth),
-     playerbox(this, w * 6 / 15, buth, UI::Box::Vertical, w * 9 / 15, h - buth),
+     clientbox(this, 0, 0, UI::Box::Vertical),
+     playerbox(this, 0, 0, UI::Box::Vertical, w * 9 / 15, h, kPadding),
      buth_(buth) {
-	int small_font = UI_FONT_SIZE_SMALL * 3 / 4;
-
-	// Clientbox and labels
-	labels.push_back(new UI::Textarea(
-	   this, UI::Scrollbar::kSize * 6 / 5, buth / 3, w / 3 - buth - UI::Scrollbar::kSize * 2, buth));
-	labels.back()->set_text(_("Client name"));
-	labels.back()->set_fontsize(small_font);
-
-	labels.push_back(new UI::Textarea(
-	   this, w / 3 - buth - UI::Scrollbar::kSize * 6 / 5, buth / 3, buth * 2, buth));
-	labels.back()->set_text(_("Role"));
-	labels.back()->set_fontsize(small_font);
-
-	clientbox.set_size(w / 3, h - buth);
+	clientbox.set_size(w / 3, h);
 	clientbox.set_scrolling(true);
 
-	// Playerbox and labels
-	labels.push_back(new UI::Textarea(this, w * 6 / 15, buth / 3, buth, buth));
-	labels.back()->set_text(_("Start"));
-	labels.back()->set_fontsize(small_font);
-
-	labels.push_back(new UI::Textarea(this, w * 6 / 15 + buth, buth / 3 - 10, buth, buth));
-	labels.back()->set_text(_("Type"));
-	labels.back()->set_fontsize(small_font);
-
-	labels.push_back(new UI::Textarea(this, w * 6 / 15 + buth * 2, buth / 3, buth, buth));
-	labels.back()->set_text(_("Tribe"));
-	labels.back()->set_fontsize(small_font);
-
-	labels.push_back(new UI::Textarea(
-	   this, w * 6 / 15 + buth * 3, buth / 3, w * 9 / 15 - 4 * buth, buth, UI::Align::kCenter));
-	labels.back()->set_text(_("Initialization"));
-	labels.back()->set_fontsize(small_font);
-
-	labels.push_back(new UI::Textarea(this, w - buth, buth / 3, buth, buth, UI::Align::kRight));
-	labels.back()->set_text(_("Team"));
-	labels.back()->set_fontsize(small_font);
-
-	playerbox.set_size(w * 9 / 15, h - buth);
+	add(&clientbox, UI::Box::Resizing::kExpandBoth);
+	add(&playerbox);
+
+	// Playerbox
+	playerbox.set_size(w * 9 / 15, h);
+	playerbox.add_space(0);
 	multi_player_player_groups.resize(kMaxPlayers);
-	for (uint8_t i = 0; i < multi_player_player_groups.size(); ++i) {
+	for (PlayerSlot i = 0; i < multi_player_player_groups.size(); ++i) {
 		multi_player_player_groups.at(i) =
-		   new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get());
+		   new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth_, s, npsb.get());
 		playerbox.add(multi_player_player_groups.at(i));
 	}
-	refresh();
+	playerbox.add_space(0);
+
+	subscriber_ =
+	   Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings&) { update(); });
+	set_size(w, h);
+	update();
 }
 
 MultiPlayerSetupGroup::~MultiPlayerSetupGroup() {
 }
 
-/**
- * Update display and enabled buttons based on current settings.
- */
-void MultiPlayerSetupGroup::refresh() {
+/// Update which slots are available based on current settings.
+void MultiPlayerSetupGroup::update() {
 	const GameSettings& settings = s->settings();
 
 	// Update / initialize client groups
@@ -529,15 +635,30 @@
 	for (uint32_t i = 0; i < settings.users.size(); ++i) {
 		if (!multi_player_client_groups.at(i)) {
 			multi_player_client_groups.at(i) =
-			   new MultiPlayerClientGroup(&clientbox, i, 0, 0, clientbox.get_w(), buth_, s);
-			clientbox.add(
-			   &*multi_player_client_groups.at(i), UI::Box::Resizing::kAlign, UI::Align::kCenter);
-		}
-		multi_player_client_groups.at(i)->refresh();
-	}
-
-	// Update player groups
-	for (uint32_t i = 0; i < kMaxPlayers; ++i) {
-		multi_player_player_groups.at(i)->refresh();
+			   new MultiPlayerClientGroup(&clientbox, clientbox.get_w(), buth_, i, s);
+			clientbox.add(multi_player_client_groups.at(i), UI::Box::Resizing::kFullSize);
+			multi_player_client_groups.at(i)->layout();
+		}
+		multi_player_client_groups.at(i)->set_visible(true);
+	}
+
+	// Keep track of which player slots are visible
+	for (PlayerSlot i = 0; i < multi_player_player_groups.size(); ++i) {
+		const bool should_be_visible = i < settings.players.size();
+		const bool is_visible = multi_player_player_groups.at(i)->is_visible();
+		if (should_be_visible != is_visible) {
+			multi_player_player_groups.at(i)->set_visible(should_be_visible);
+		}
+	}
+}
+
+void MultiPlayerSetupGroup::draw(RenderTarget& dst) {
+	for (MultiPlayerPlayerGroup* player_group : multi_player_player_groups) {
+		if (player_group->is_visible()) {
+			dst.brighten_rect(
+			   Recti(playerbox.get_x(), playerbox.get_y() + player_group->get_y() - kPadding / 2,
+			         playerbox.get_w() + kPadding, player_group->get_h() + kPadding),
+			   -MOUSE_OVER_BRIGHT_FACTOR);
+		}
 	}
 }

=== modified file 'src/wui/multiplayersetupgroup.h'
--- src/wui/multiplayersetupgroup.h	2017-01-25 18:55:59 +0000
+++ src/wui/multiplayersetupgroup.h	2017-06-26 14:45:57 +0000
@@ -33,7 +33,6 @@
 #include "ui_basic/textarea.h"
 
 struct GameSettingsProvider;
-struct MultiPlayerSetupGroupOptions;
 struct MultiPlayerClientGroup;
 struct MultiPlayerPlayerGroup;
 
@@ -44,26 +43,27 @@
  * clients, computers and closed players.
  *
  */
-struct MultiPlayerSetupGroup : public UI::Panel {
+struct MultiPlayerSetupGroup : public UI::Box {
 	MultiPlayerSetupGroup(UI::Panel* parent,
 	                      int32_t x,
 	                      int32_t y,
 	                      int32_t w,
 	                      int32_t h,
 	                      GameSettingsProvider* settings,
-	                      uint32_t butw,
 	                      uint32_t buth);
 	~MultiPlayerSetupGroup();
 
-	void refresh();
-
 private:
+	void update();
+	void draw(RenderTarget& dst) override;
+
 	GameSettingsProvider* const s;
 	std::unique_ptr<NetworkPlayerSettingsBackend> npsb;
 	std::vector<MultiPlayerClientGroup*> multi_player_client_groups;  // not owned
 	std::vector<MultiPlayerPlayerGroup*> multi_player_player_groups;  // not owned
+	std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_;
+
 	UI::Box clientbox, playerbox;
-	std::vector<UI::Textarea*> labels;
 
 	uint32_t buth_;
 

=== modified file 'src/wui/playerdescrgroup.cc'
--- src/wui/playerdescrgroup.cc	2017-01-30 14:40:12 +0000
+++ src/wui/playerdescrgroup.cc	2017-06-26 14:45:57 +0000
@@ -116,7 +116,7 @@
 
 	d->btnEnablePlayer->set_enabled(stateaccess);
 
-	if (player.state == PlayerSettings::stateClosed) {
+	if (player.state == PlayerSettings::State::kClosed) {
 		d->btnEnablePlayer->set_state(false);
 		d->btnPlayerTeam->set_visible(false);
 		d->btnPlayerTeam->set_enabled(false);
@@ -132,7 +132,7 @@
 		d->btnPlayerType->set_visible(true);
 		d->btnPlayerType->set_enabled(stateaccess);
 
-		if (player.state == PlayerSettings::stateOpen) {
+		if (player.state == PlayerSettings::State::kOpen) {
 			d->btnPlayerType->set_title(_("Open"));
 			d->btnPlayerTeam->set_visible(false);
 			d->btnPlayerTeam->set_visible(false);
@@ -144,7 +144,7 @@
 		} else {
 			std::string title;
 
-			if (player.state == PlayerSettings::stateComputer) {
+			if (player.state == PlayerSettings::State::kComputer) {
 				if (player.ai.empty())
 					title = _("Computer");
 				else {
@@ -156,7 +156,7 @@
 						title = _(impl->descname);
 					}
 				}
-			} else {  // PlayerSettings::stateHuman
+			} else {  // PlayerSettings::State::stateHuman
 				title = _("Human");
 			}
 			d->btnPlayerType->set_title(title);
@@ -215,11 +215,11 @@
 		return;
 
 	if (on) {
-		if (settings.players[d->plnum].state == PlayerSettings::stateClosed)
+		if (settings.players[d->plnum].state == PlayerSettings::State::kClosed)
 			d->settings->next_player_state(d->plnum);
 	} else {
-		if (settings.players[d->plnum].state != PlayerSettings::stateClosed)
-			d->settings->set_player_state(d->plnum, PlayerSettings::stateClosed);
+		if (settings.players[d->plnum].state != PlayerSettings::State::kClosed)
+			d->settings->set_player_state(d->plnum, PlayerSettings::State::kClosed);
 	}
 }
 


Follow ups