Merge lp:~widelands-dev/widelands/multiplayer_dropdowns_2_init_team into lp:widelands

Proposed by GunChleoc on 2017-06-26
Status: Merged
Merge reported by: GunChleoc
Merged at revision: not available
Proposed branch: lp:~widelands-dev/widelands/multiplayer_dropdowns_2_init_team
Merge into: lp:widelands
Prerequisite: lp:~widelands-dev/widelands/multiplayer_dropdowns_1_type
Diff against target: 2254 lines (+767/-580)
25 files modified
src/ai/computer_player.cc (+7/-6)
src/ai/computer_player.h (+17/-0)
src/ai/defaultai.h (+21/-18)
src/base/macros.h (+4/-0)
src/graphic/playercolor.h (+13/-0)
src/logic/CMakeLists.txt (+1/-0)
src/logic/game.cc (+3/-3)
src/logic/game_settings.h (+19/-2)
src/logic/single_player_game_settings_provider.cc (+8/-7)
src/network/gameclient.cc (+6/-4)
src/network/gameclient.h (+1/-1)
src/network/gamehost.cc (+30/-101)
src/network/network_player_settings_backend.cc (+118/-79)
src/network/network_player_settings_backend.h (+10/-12)
src/network/network_protocol.h (+3/-2)
src/notifications/note_ids.h (+3/-1)
src/ui_basic/dropdown.cc (+67/-12)
src/ui_basic/dropdown.h (+31/-2)
src/ui_basic/listselect.cc (+2/-2)
src/ui_fsmenu/launch_game.cc (+5/-6)
src/ui_fsmenu/launch_mpg.cc (+12/-10)
src/ui_fsmenu/launch_spg.cc (+5/-5)
src/wui/multiplayersetupgroup.cc (+372/-298)
src/wui/multiplayersetupgroup.h (+2/-2)
src/wui/playerdescrgroup.cc (+7/-7)
To merge this branch: bzr merge lp:~widelands-dev/widelands/multiplayer_dropdowns_2_init_team
Reviewer Review Type Date Requested Status
SirVer 2017-06-26 Needs Fixing on 2017-08-31
Review via email: mp+326303@code.launchpad.net

Commit message

For multiplayer game setup, use dropdowns instead of buttons to select player initializations (Headquarters, Fortified Village etc) and teams.

- Team dropdowns now use flag images with team color.
- Removed the tiny lables, since the function of each dropdown can be identified per its tooltip
- Added cast_unsigned(u) and cast_signed(u) convenience macros

Description of the change

Branch2 in a 3-branch series to implement dropdown menus in multiplayer setup. This branch implements the init and team dropdowns.

DO NOT TEST THIS BRANCH!

All testing and bugfixing is to be done in https://code.launchpad.net/~widelands-dev/widelands/multiplayer_dropdowns/+merge/326302

To post a comment you must log in.
8405. By GunChleoc on 2017-06-26

clang-format.

8406. By GunChleoc on 2017-06-26

Added "no review" comments.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2380. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/247251329.
Appveyor build 2208. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_multiplayer_dropdowns_2_init_team-2208.

8407. By GunChleoc on 2017-06-28

Merged trunk.

8408. By GunChleoc on 2017-08-11

Fixed compiler error.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2524. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/263623188.
Appveyor build 2348. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_multiplayer_dropdowns_2_init_team-2348.

8409. By GunChleoc on 2017-08-18

Merged trunk.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2573. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/265946005.
Appveyor build 2395. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_multiplayer_dropdowns_2_init_team-2395.

8410. By GunChleoc on 2017-08-30

Merged trunk.

bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2617. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/269988391.
Appveyor build 2439. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_multiplayer_dropdowns_2_init_team-2439.

SirVer (sirver) wrote :

Please revert the macros cast_unsigned and cast_signed and stick to using the static_cast<> operators - they are verbose, but are understood by any c++ programmer without additional context. Code is only written once, but read multiple times - so optimizing for the convenience of the writer is the wrong thing to optimize for.

Otherwise code LGTM and compiles. Not tested.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'data/images/players/no_team.png'
2Binary files data/images/players/no_team.png 1970-01-01 00:00:00 +0000 and data/images/players/no_team.png 2017-08-30 12:04:30 +0000 differ
3=== added file 'data/images/players/team.png'
4Binary files data/images/players/team.png 1970-01-01 00:00:00 +0000 and data/images/players/team.png 2017-08-30 12:04:30 +0000 differ
5=== added file 'data/images/players/team_pc.png'
6Binary files data/images/players/team_pc.png 1970-01-01 00:00:00 +0000 and data/images/players/team_pc.png 2017-08-30 12:04:30 +0000 differ
7=== modified file 'src/ai/computer_player.cc'
8--- src/ai/computer_player.cc 2017-01-25 18:55:59 +0000
9+++ src/ai/computer_player.cc 2017-08-30 12:04:30 +0000
10@@ -36,12 +36,13 @@
11 }
12
13 struct EmptyAIImpl : Implementation {
14- EmptyAIImpl() {
15- name = "empty";
16- /** TRANSLATORS: This is the name of an AI used in the game setup screens */
17- descname = _("No AI");
18- icon_filename = "images/ai/ai_empty.png";
19- type = Implementation::Type::kEmpty;
20+ EmptyAIImpl()
21+ : Implementation(
22+ "empty",
23+ /** TRANSLATORS: This is the name of an AI used in the game setup screens */
24+ _("No AI"),
25+ "images/ai/ai_empty.png",
26+ Implementation::Type::kEmpty) {
27 }
28 ComputerPlayer* instantiate(Widelands::Game& g,
29 Widelands::PlayerNumber const pid) const override {
30
31=== modified file 'src/ai/computer_player.h'
32--- src/ai/computer_player.h 2017-04-22 08:02:21 +0000
33+++ src/ai/computer_player.h 2017-08-30 12:04:30 +0000
34@@ -23,9 +23,15 @@
35 #include <string>
36 #include <vector>
37
38+#include <boost/algorithm/string/predicate.hpp>
39+
40 #include "base/macros.h"
41 #include "logic/widelands.h"
42
43+// We need to use a string prefix in the game setup screens to identify the AIs, so we make sure
44+// that the AI names don't contain the separator that's used to parse the strings there.
45+#define AI_NAME_SEPARATOR "|"
46+
47 namespace Widelands {
48 class Game;
49 } // namespace Widelands
50@@ -61,6 +67,17 @@
51 std::string descname;
52 std::string icon_filename;
53 Type type;
54+ explicit Implementation(std::string init_name,
55+ std::string init_descname,
56+ std::string init_icon_filename,
57+ Type init_type)
58+ : name(init_name),
59+ descname(init_descname),
60+ icon_filename(init_icon_filename),
61+ type(init_type) {
62+ assert(!boost::contains(name, AI_NAME_SEPARATOR));
63+ }
64+
65 virtual ~Implementation() {
66 }
67 virtual ComputerPlayer* instantiate(Widelands::Game&, Widelands::PlayerNumber) const = 0;
68
69=== modified file 'src/ai/defaultai.h'
70--- src/ai/defaultai.h 2017-08-14 18:31:43 +0000
71+++ src/ai/defaultai.h 2017-08-30 12:04:30 +0000
72@@ -91,12 +91,13 @@
73
74 /// Implementation for Strong
75 struct NormalImpl : public ComputerPlayer::Implementation {
76- NormalImpl() {
77- name = "normal";
78- /** TRANSLATORS: This is the name of an AI used in the game setup screens */
79- descname = _("Normal AI");
80- icon_filename = "images/ai/ai_normal.png";
81- type = Implementation::Type::kDefault;
82+ NormalImpl()
83+ : Implementation(
84+ "normal",
85+ /** TRANSLATORS: This is the name of an AI used in the game setup screens */
86+ _("Normal AI"),
87+ "images/ai/ai_normal.png",
88+ Implementation::Type::kDefault) {
89 }
90 ComputerPlayer* instantiate(Widelands::Game& game,
91 Widelands::PlayerNumber const p) const override {
92@@ -105,12 +106,13 @@
93 };
94
95 struct WeakImpl : public ComputerPlayer::Implementation {
96- WeakImpl() {
97- name = "weak";
98- /** TRANSLATORS: This is the name of an AI used in the game setup screens */
99- descname = _("Weak AI");
100- icon_filename = "images/ai/ai_weak.png";
101- type = Implementation::Type::kDefault;
102+ WeakImpl()
103+ : Implementation(
104+ "weak",
105+ /** TRANSLATORS: This is the name of an AI used in the game setup screens */
106+ _("Weak AI"),
107+ "images/ai/ai_weak.png",
108+ Implementation::Type::kDefault) {
109 }
110 ComputerPlayer* instantiate(Widelands::Game& game,
111 Widelands::PlayerNumber const p) const override {
112@@ -119,12 +121,13 @@
113 };
114
115 struct VeryWeakImpl : public ComputerPlayer::Implementation {
116- VeryWeakImpl() {
117- name = "very_weak";
118- /** TRANSLATORS: This is the name of an AI used in the game setup screens */
119- descname = _("Very Weak AI");
120- icon_filename = "images/ai/ai_very_weak.png";
121- type = Implementation::Type::kDefault;
122+ VeryWeakImpl()
123+ : Implementation(
124+ "very_weak",
125+ /** TRANSLATORS: This is the name of an AI used in the game setup screens */
126+ _("Very Weak AI"),
127+ "images/ai/ai_very_weak.png",
128+ Implementation::Type::kDefault) {
129 }
130 ComputerPlayer* instantiate(Widelands::Game& game,
131 Widelands::PlayerNumber const p) const override {
132
133=== modified file 'src/base/macros.h'
134--- src/base/macros.h 2017-07-01 20:32:02 +0000
135+++ src/base/macros.h 2017-08-30 12:04:30 +0000
136@@ -104,6 +104,10 @@
137 // the side-effect upcast has of creating a new identifier which won't be used.
138 #define is_a(type, source) (dynamic_cast<const type*>(source) != nullptr)
139
140+// For printf placeholders, we need to cast int8_t/uint8_t to avoid confusion with char
141+#define cast_unsigned(u) static_cast<unsigned int>(u)
142+#define cast_signed(i) static_cast<int>(i)
143+
144 // consistency check for printf arguments
145 #ifdef __GNUC__
146 #ifdef _WIN32
147
148=== modified file 'src/graphic/playercolor.h'
149--- src/graphic/playercolor.h 2017-04-28 06:47:01 +0000
150+++ src/graphic/playercolor.h 2017-08-30 12:04:30 +0000
151@@ -49,6 +49,19 @@
152 RGBColor(144, 144, 144), // light gray
153 };
154
155+// Hard coded team colors
156+const RGBColor kTeamColors[kMaxPlayers / 2 + 1] = {
157+ RGBColor(100, 100, 100), // No team
158+ RGBColor(2, 2, 198), // blue
159+ RGBColor(255, 41, 0), // red
160+ RGBColor(255, 232, 0), // yellow
161+ RGBColor(59, 223, 3), // green
162+ RGBColor(57, 57, 57), // black/dark gray
163+ RGBColor(255, 172, 0), // orange
164+ RGBColor(215, 0, 218), // purple
165+ RGBColor(255, 255, 255), // white
166+};
167+
168 /// Looks for a player color mask image, and if it finds one,
169 /// returns the image with added playercolor. If no player color
170 /// image file is found, gets the image from 'image_filename'
171
172=== modified file 'src/logic/CMakeLists.txt'
173--- src/logic/CMakeLists.txt 2017-07-11 19:38:04 +0000
174+++ src/logic/CMakeLists.txt 2017-08-30 12:04:30 +0000
175@@ -13,6 +13,7 @@
176 io_filesystem
177 logic
178 logic_constants
179+ notifications
180 scripting_lua_interface
181 scripting_lua_table
182 )
183
184=== modified file 'src/logic/game.cc'
185--- src/logic/game.cc 2017-08-20 06:43:11 +0000
186+++ src/logic/game.cc 2017-08-30 12:04:30 +0000
187@@ -269,10 +269,10 @@
188 for (uint32_t i = 0; i < settings.players.size(); ++i) {
189 const PlayerSettings& playersettings = settings.players[i];
190
191- if (playersettings.state == PlayerSettings::stateClosed ||
192- playersettings.state == PlayerSettings::stateOpen)
193+ if (playersettings.state == PlayerSettings::State::kClosed ||
194+ playersettings.state == PlayerSettings::State::kOpen)
195 continue;
196- else if (playersettings.state == PlayerSettings::stateShared) {
197+ else if (playersettings.state == PlayerSettings::State::kShared) {
198 shared.push_back(playersettings);
199 shared_num.push_back(i + 1);
200 continue;
201
202=== modified file 'src/logic/game_settings.h'
203--- src/logic/game_settings.h 2017-08-16 04:31:56 +0000
204+++ src/logic/game_settings.h 2017-08-30 12:04:30 +0000
205@@ -28,11 +28,26 @@
206 #include "logic/map_objects/tribes/tribe_basic_info.h"
207 #include "logic/player_end_result.h"
208 #include "logic/widelands.h"
209+#include "notifications/note_ids.h"
210+#include "notifications/notifications.h"
211 #include "scripting/lua_interface.h"
212 #include "scripting/lua_table.h"
213
214+// Player slot 0 will give us PlayerNumber 1 etc., so we rename it to avoid confusion
215+// TODO(GunChleoc): Rename all uint8_t to PlayerSlot or Widelands::PlayerNumber
216+using PlayerSlot = Widelands::PlayerNumber;
217+
218+struct NoteGameSettings {
219+ CAN_BE_SENT_AS_NOTE(NoteId::GameSettings)
220+
221+ Widelands::PlayerNumber position;
222+
223+ explicit NoteGameSettings(Widelands::PlayerNumber init_position) : position(init_position) {
224+ }
225+};
226+
227 struct PlayerSettings {
228- enum State { stateOpen, stateHuman, stateComputer, stateClosed, stateShared };
229+ enum class State { kOpen, kHuman, kComputer, kClosed, kShared };
230
231 State state;
232 uint8_t initialization_index;
233@@ -154,7 +169,9 @@
234 bool savegame = false) = 0;
235 virtual void set_player_state(uint8_t number, PlayerSettings::State) = 0;
236 virtual void set_player_ai(uint8_t number, const std::string&, bool const random_ai = false) = 0;
237- virtual void next_player_state(uint8_t number) = 0;
238+ // Multiplayer no longer toggles per button
239+ virtual void next_player_state(uint8_t /* number */) {
240+ }
241 virtual void
242 set_player_tribe(uint8_t number, const std::string&, bool const random_tribe = false) = 0;
243 virtual void set_player_init(uint8_t number, uint8_t index) = 0;
244
245=== modified file 'src/logic/single_player_game_settings_provider.cc'
246--- src/logic/single_player_game_settings_provider.cc 2017-08-09 19:25:04 +0000
247+++ src/logic/single_player_game_settings_provider.cc 2017-08-30 12:04:30 +0000
248@@ -81,14 +81,15 @@
249
250 while (oldplayers < maxplayers) {
251 PlayerSettings& player = s.players[oldplayers];
252- player.state = (oldplayers == 0) ? PlayerSettings::stateHuman : PlayerSettings::stateComputer;
253+ player.state =
254+ (oldplayers == 0) ? PlayerSettings::State::kHuman : PlayerSettings::State::kComputer;
255 player.tribe = s.tribes.at(0).name;
256 player.random_tribe = false;
257 player.initialization_index = 0;
258 player.name = (boost::format(_("Player %u")) % (oldplayers + 1)).str();
259 player.team = 0;
260 // Set default computerplayer ai type
261- if (player.state == PlayerSettings::stateComputer) {
262+ if (player.state == PlayerSettings::State::kComputer) {
263 const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations();
264 if (impls.size() > 1) {
265 player.ai = impls.at(0)->name;
266@@ -107,8 +108,8 @@
267 if (number == s.playernum || number >= s.players.size())
268 return;
269
270- if (state == PlayerSettings::stateOpen)
271- state = PlayerSettings::stateComputer;
272+ if (state == PlayerSettings::State::kOpen)
273+ state = PlayerSettings::State::kComputer;
274
275 s.players[number].state = state;
276 }
277@@ -147,7 +148,7 @@
278 s.players[number].ai = (*it)->name;
279 }
280
281- s.players[number].state = PlayerSettings::stateComputer;
282+ s.players[number].state = PlayerSettings::State::kComputer;
283 }
284
285 void SinglePlayerGameSettingsProvider::set_player_tribe(uint8_t const number,
286@@ -219,8 +220,8 @@
287 return;
288 PlayerSettings const position = settings().players.at(number);
289 PlayerSettings const player = settings().players.at(settings().playernum);
290- if (number < settings().players.size() && (position.state == PlayerSettings::stateOpen ||
291- position.state == PlayerSettings::stateComputer)) {
292+ if (number < settings().players.size() && (position.state == PlayerSettings::State::kOpen ||
293+ position.state == PlayerSettings::State::kComputer)) {
294 set_player(number, player);
295 set_player(settings().playernum, position);
296 s.playernum = number;
297
298=== modified file 'src/network/gameclient.cc'
299--- src/network/gameclient.cc 2017-08-09 17:55:34 +0000
300+++ src/network/gameclient.cc 2017-08-30 12:04:30 +0000
301@@ -367,14 +367,14 @@
302 d->net->send(s);
303 }
304
305-void GameClient::set_player_init(uint8_t number, uint8_t) {
306+void GameClient::set_player_init(uint8_t number, uint8_t initialization_index) {
307 if ((number != d->settings.playernum))
308 return;
309
310- // Host will decide what to change, therefore the init is not send, just the request to change
311 SendPacket s;
312 s.unsigned_8(NETCMD_SETTING_CHANGEINIT);
313 s.unsigned_8(number);
314+ s.unsigned_8(initialization_index);
315 d->net->send(s);
316 }
317
318@@ -404,8 +404,8 @@
319 return;
320 // Same if the player is not selectable
321 if (number < d->settings.players.size() &&
322- (d->settings.players.at(number).state == PlayerSettings::stateClosed ||
323- d->settings.players.at(number).state == PlayerSettings::stateComputer))
324+ (d->settings.players.at(number).state == PlayerSettings::State::kClosed ||
325+ d->settings.players.at(number).state == PlayerSettings::State::kComputer))
326 return;
327
328 // Send request
329@@ -459,6 +459,7 @@
330 player.random_ai = packet.unsigned_8() == 1;
331 player.team = packet.unsigned_8();
332 player.shared_in = packet.unsigned_8();
333+ Notifications::publish(NoteGameSettings(number));
334 }
335
336 void GameClient::receive_one_user(uint32_t const number, StreamRead& packet) {
337@@ -476,6 +477,7 @@
338 if (static_cast<int32_t>(number) == d->settings.usernum) {
339 d->localplayername = d->settings.users.at(number).name;
340 d->settings.playernum = d->settings.users.at(number).position;
341+ Notifications::publish(NoteGameSettings(d->settings.playernum));
342 }
343 }
344
345
346=== modified file 'src/network/gameclient.h'
347--- src/network/gameclient.h 2017-07-05 19:21:57 +0000
348+++ src/network/gameclient.h 2017-08-30 12:04:30 +0000
349@@ -84,7 +84,7 @@
350 virtual void set_player_tribe(uint8_t number,
351 const std::string& tribe,
352 bool const random_tribe = false) override;
353- void set_player_init(uint8_t number, uint8_t index) override;
354+ void set_player_init(uint8_t number, uint8_t initialization_index) override;
355 void set_player_name(uint8_t number, const std::string& name) override;
356 void set_player(uint8_t number, const PlayerSettings& ps) override;
357 void set_player_number(uint8_t number) override;
358
359=== modified file 'src/network/gamehost.cc'
360--- src/network/gamehost.cc 2017-08-19 23:24:28 +0000
361+++ src/network/gamehost.cc 2017-08-30 12:04:30 +0000
362@@ -82,12 +82,12 @@
363 }
364 bool can_change_player_state(uint8_t const number) override {
365 if (settings().savegame)
366- return settings().players.at(number).state != PlayerSettings::stateClosed;
367+ return settings().players.at(number).state != PlayerSettings::State::kClosed;
368 else if (settings().scenario)
369- return ((settings().players.at(number).state == PlayerSettings::stateOpen ||
370- settings().players.at(number).state == PlayerSettings::stateHuman) &&
371+ return ((settings().players.at(number).state == PlayerSettings::State::kOpen ||
372+ settings().players.at(number).state == PlayerSettings::State::kHuman) &&
373 settings().players.at(number).closeable) ||
374- settings().players.at(number).state == PlayerSettings::stateClosed;
375+ settings().players.at(number).state == PlayerSettings::State::kClosed;
376 return true;
377 }
378 bool can_change_player_tribe(uint8_t const number) override {
379@@ -105,7 +105,7 @@
380 return false;
381 if (number == settings().playernum)
382 return true;
383- return settings().players.at(number).state == PlayerSettings::stateComputer;
384+ return settings().players.at(number).state == PlayerSettings::State::kComputer;
385 }
386
387 bool can_launch() override {
388@@ -124,83 +124,6 @@
389
390 host_->set_player_state(number, state);
391 }
392- void next_player_state(uint8_t const number) override {
393- if (number > settings().players.size())
394- return;
395-
396- PlayerSettings::State newstate = PlayerSettings::stateClosed;
397- switch (host_->settings().players.at(number).state) {
398- case PlayerSettings::stateClosed:
399- // In savegames : closed players can not be changed.
400- assert(!host_->settings().savegame);
401- newstate = PlayerSettings::stateOpen;
402- break;
403- case PlayerSettings::stateOpen:
404- case PlayerSettings::stateHuman:
405- if (host_->settings().scenario) {
406- assert(host_->settings().players.at(number).closeable);
407- newstate = PlayerSettings::stateClosed;
408- break;
409- } // else fall through
410- FALLS_THROUGH;
411- case PlayerSettings::stateComputer: {
412- const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations();
413- ComputerPlayer::ImplementationVector::const_iterator it = impls.begin();
414- if (host_->settings().players.at(number).ai.empty()) {
415- set_player_ai(number, (*it)->name);
416- newstate = PlayerSettings::stateComputer;
417- break;
418- }
419- do {
420- ++it;
421- if ((*(it - 1))->name == host_->settings().players.at(number).ai)
422- break;
423- } while (it != impls.end());
424- if (settings().players.at(number).random_ai) {
425- set_player_ai(number, std::string());
426- set_player_name(number, std::string());
427- // Do not share a player in savegames or scenarios
428- if (host_->settings().scenario || host_->settings().savegame)
429- newstate = PlayerSettings::stateOpen;
430- else {
431- uint8_t shared = 0;
432- for (; shared < settings().players.size(); ++shared) {
433- if (settings().players.at(shared).state != PlayerSettings::stateClosed &&
434- settings().players.at(shared).state != PlayerSettings::stateShared)
435- break;
436- }
437- if (shared < settings().players.size()) {
438- newstate = PlayerSettings::stateShared;
439- set_player_shared(number, shared + 1);
440- } else
441- newstate = PlayerSettings::stateClosed;
442- }
443- } else if (it == impls.end()) {
444- do {
445- uint8_t random = (std::rand() % impls.size()); // Choose a random AI
446- it = impls.begin() + random;
447- } while ((*it)->type == ComputerPlayer::Implementation::Type::kEmpty);
448- set_player_ai(number, (*it)->name, true);
449- newstate = PlayerSettings::stateComputer;
450- break;
451- } else {
452- set_player_ai(number, (*it)->name);
453- newstate = PlayerSettings::stateComputer;
454- }
455- break;
456- }
457- case PlayerSettings::stateShared: {
458- // Do not close a player in savegames or scenarios
459- if (host_->settings().scenario || host_->settings().savegame)
460- newstate = PlayerSettings::stateOpen;
461- else
462- newstate = PlayerSettings::stateClosed;
463- break;
464- }
465- }
466-
467- host_->set_player_state(number, newstate, true);
468- }
469
470 void
471 set_player_tribe(uint8_t number, const std::string& tribe, bool const random_tribe) override {
472@@ -208,9 +131,10 @@
473 return;
474
475 if (number == settings().playernum ||
476- settings().players.at(number).state == PlayerSettings::stateComputer ||
477- settings().players.at(number).state == PlayerSettings::stateShared ||
478- settings().players.at(number).state == PlayerSettings::stateOpen) // For savegame loading
479+ settings().players.at(number).state == PlayerSettings::State::kComputer ||
480+ settings().players.at(number).state == PlayerSettings::State::kShared ||
481+ settings().players.at(number).state ==
482+ PlayerSettings::State::kOpen) // For savegame loading
483 host_->set_player_tribe(number, tribe, random_tribe);
484 }
485
486@@ -219,7 +143,7 @@
487 return;
488
489 if (number == settings().playernum ||
490- settings().players.at(number).state == PlayerSettings::stateComputer)
491+ settings().players.at(number).state == PlayerSettings::State::kComputer)
492 host_->set_player_team(number, team);
493 }
494
495@@ -1019,9 +943,9 @@
496 // but not all should be closed!
497 bool one_not_closed = false;
498 for (PlayerSettings& setting : d->settings.players) {
499- if (setting.state != PlayerSettings::stateClosed)
500+ if (setting.state != PlayerSettings::State::kClosed)
501 one_not_closed = true;
502- if (setting.state == PlayerSettings::stateOpen)
503+ if (setting.state == PlayerSettings::State::kOpen)
504 return false;
505 }
506 return one_not_closed;
507@@ -1071,7 +995,7 @@
508
509 while (oldplayers < maxplayers) {
510 PlayerSettings& player = d->settings.players.at(oldplayers);
511- player.state = PlayerSettings::stateOpen;
512+ player.state = PlayerSettings::State::kOpen;
513 player.name = "";
514 player.tribe = d->settings.tribes.at(0).name;
515 player.random_tribe = false;
516@@ -1148,7 +1072,7 @@
517
518 SendPacket s;
519
520- if (player.state == PlayerSettings::stateHuman) {
521+ if (player.state == PlayerSettings::State::kHuman) {
522 // 0 is host and has no client
523 if (d->settings.users.at(0).position == number) {
524 d->settings.users.at(0).position = UserSettings::none();
525@@ -1182,7 +1106,7 @@
526
527 player.state = state;
528
529- if (player.state == PlayerSettings::stateComputer)
530+ if (player.state == PlayerSettings::State::kComputer)
531 player.name = get_computer_player_name(number);
532
533 // Broadcast change
534@@ -1322,8 +1246,8 @@
535 return;
536
537 PlayerSettings& sharedplr = d->settings.players.at(shared - 1);
538- assert(sharedplr.state != PlayerSettings::stateClosed &&
539- sharedplr.state != PlayerSettings::stateShared);
540+ assert(sharedplr.state != PlayerSettings::State::kClosed &&
541+ sharedplr.state != PlayerSettings::State::kShared);
542
543 player.shared_in = shared;
544 player.tribe = sharedplr.tribe;
545@@ -1367,8 +1291,8 @@
546
547 void GameHost::switch_to_player(uint32_t user, uint8_t number) {
548 if (number < d->settings.players.size() &&
549- (d->settings.players.at(number).state != PlayerSettings::stateOpen &&
550- d->settings.players.at(number).state != PlayerSettings::stateHuman))
551+ (d->settings.players.at(number).state != PlayerSettings::State::kOpen &&
552+ d->settings.players.at(number).state != PlayerSettings::State::kHuman))
553 return;
554
555 uint32_t old = d->settings.users.at(user).position;
556@@ -1383,14 +1307,14 @@
557 temp2 = temp2.erase(op.name.find(temp), temp.size());
558 set_player_name(old, temp2);
559 if (temp2.empty())
560- set_player_state(old, PlayerSettings::stateOpen);
561+ set_player_state(old, PlayerSettings::State::kOpen);
562 }
563
564 if (number < d->settings.players.size()) {
565 // Add clients name to new player slot
566 PlayerSettings& op = d->settings.players.at(number);
567- if (op.state == PlayerSettings::stateOpen) {
568- set_player_state(number, PlayerSettings::stateHuman);
569+ if (op.state == PlayerSettings::State::kOpen) {
570+ set_player_state(number, PlayerSettings::State::kHuman);
571 set_player_name(number, " " + name + " ");
572 } else
573 set_player_name(number, op.name + " " + name + " ");
574@@ -1490,6 +1414,7 @@
575 packet.unsigned_8(player.random_ai ? 1 : 0);
576 packet.unsigned_8(player.team);
577 packet.unsigned_8(player.shared_in);
578+ Notifications::publish(NoteGameSettings(number));
579 }
580
581 void GameHost::write_setting_all_players(SendPacket& packet) {
582@@ -1502,6 +1427,7 @@
583 packet.string(d->settings.users.at(number).name);
584 packet.signed_32(d->settings.users.at(number).position);
585 packet.unsigned_8(d->settings.users.at(number).ready ? 1 : 0);
586+ Notifications::publish(NoteGameSettings(d->settings.users.at(number).position));
587 }
588
589 void GameHost::write_setting_all_users(SendPacket& packet) {
590@@ -1673,7 +1599,7 @@
591
592 // Check if there is an unoccupied player left and if, assign.
593 for (uint8_t i = 0; i < d->settings.players.size(); ++i)
594- if (d->settings.players.at(i).state == PlayerSettings::stateOpen) {
595+ if (d->settings.players.at(i).state == PlayerSettings::State::kOpen) {
596 switch_to_player(client.usernum, i);
597 break;
598 }
599@@ -2096,10 +2022,13 @@
600
601 case NETCMD_SETTING_CHANGEINIT:
602 if (!d->game) {
603+ // TODO(GunChleoc): For some nebulous reason, we don't receive the num that the client is
604+ // sending when a player changes slot. So, keeping the access to the client off for now.
605+ // Would be nice to have though.
606 uint8_t num = r.unsigned_8();
607 if (num != client.playernum)
608 throw DisconnectException("NO_ACCESS_TO_PLAYER");
609- d->npsb.toggle_init(num);
610+ set_player_init(num, r.unsigned_8());
611 }
612 break;
613
614@@ -2267,7 +2196,7 @@
615 }
616 }
617
618- set_player_state(number, PlayerSettings::stateOpen);
619+ set_player_state(number, PlayerSettings::State::kOpen);
620 if (d->game)
621 init_computer_player(number + 1);
622 }
623
624=== modified file 'src/network/network_player_settings_backend.cc'
625--- src/network/network_player_settings_backend.cc 2017-02-14 10:18:15 +0000
626+++ src/network/network_player_settings_backend.cc 2017-08-30 12:04:30 +0000
627@@ -19,6 +19,7 @@
628
629 #include "network/network_player_settings_backend.h"
630
631+#include "ai/computer_player.h"
632 #include "base/i18n.h"
633 #include "base/log.h"
634 #include "base/wexception.h"
635@@ -26,127 +27,165 @@
636 #include "logic/map_objects/tribes/tribe_descr.h"
637 #include "logic/player.h"
638
639-/// Toggle through the types
640-void NetworkPlayerSettingsBackend::toggle_type(uint8_t id) {
641- if (id >= s->settings().players.size())
642- return;
643-
644- s->next_player_state(id);
645-}
646-
647-void NetworkPlayerSettingsBackend::set_tribe(uint8_t id, const std::string& tribename) {
648+void NetworkPlayerSettingsBackend::set_player_state(PlayerSlot id, PlayerSettings::State state) {
649+ if (id >= s->settings().players.size()) {
650+ return;
651+ }
652+ // Do not close a player in savegames or scenarios
653+ if (state == PlayerSettings::State::kClosed &&
654+ ((s->settings().scenario && !s->settings().players.at(id).closeable) ||
655+ s->settings().savegame)) {
656+ state = PlayerSettings::State::kOpen;
657+ }
658+
659+ s->set_player_state(id, state);
660+ if (state == PlayerSettings::State::kShared) {
661+ PlayerSlot shared = 0;
662+ for (; shared < s->settings().players.size(); ++shared) {
663+ if (s->settings().players.at(shared).state != PlayerSettings::State::kClosed &&
664+ s->settings().players.at(shared).state != PlayerSettings::State::kShared)
665+ break;
666+ }
667+ if (shared < s->settings().players.size()) {
668+ s->set_player_shared(id, shared + 1);
669+ } else {
670+ s->set_player_state(id, PlayerSettings::State::kClosed);
671+ }
672+ }
673+}
674+
675+void NetworkPlayerSettingsBackend::set_player_ai(PlayerSlot id,
676+ const std::string& name,
677+ bool random_ai) {
678+ if (id >= s->settings().players.size()) {
679+ return;
680+ }
681+ if (random_ai) {
682+ const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations();
683+ ComputerPlayer::ImplementationVector::const_iterator it = impls.begin();
684+ if (impls.size() > 1) {
685+ do {
686+ size_t random = (std::rand() % impls.size()); // Choose a random AI
687+ it = impls.begin() + random;
688+ } while ((*it)->type == ComputerPlayer::Implementation::Type::kEmpty);
689+ }
690+ s->set_player_ai(id, (*it)->name, random_ai);
691+ } else {
692+ s->set_player_ai(id, name, random_ai);
693+ }
694+}
695+
696+void NetworkPlayerSettingsBackend::set_tribe(PlayerSlot id, const std::string& tribename) {
697 const GameSettings& settings = s->settings();
698
699 if (id >= settings.players.size() || tribename.empty())
700 return;
701
702- if (settings.players.at(id).state != PlayerSettings::stateShared) {
703+ if (settings.players.at(id).state != PlayerSettings::State::kShared) {
704 s->set_player_tribe(id, tribename, tribename == "random");
705 }
706 }
707
708 /// Set the shared in player for the given id
709-void NetworkPlayerSettingsBackend::set_shared_in(uint8_t id, uint8_t shared_in) {
710+void NetworkPlayerSettingsBackend::set_shared_in(PlayerSlot id, Widelands::PlayerNumber shared_in) {
711 const GameSettings& settings = s->settings();
712 if (id > settings.players.size() || shared_in > settings.players.size())
713 return;
714- if (settings.players.at(id).state == PlayerSettings::stateShared) {
715+ if (settings.players.at(id).state == PlayerSettings::State::kShared) {
716 s->set_player_shared(id, shared_in);
717 }
718 }
719
720-/// Toggle through shared in players
721-void NetworkPlayerSettingsBackend::toggle_shared_in(uint8_t id) {
722+/// Toggle through shared in players. We don't set them here yet to avoid triggering extra
723+/// notifications from the server. If '0' is returned, no suitable player was found.
724+// NOCOM this function is deleted in the final branch, so you can skip reviewing the code in it.
725+Widelands::PlayerNumber NetworkPlayerSettingsBackend::find_next_shared_in(PlayerSlot id) {
726 const GameSettings& settings = s->settings();
727
728 if (id >= settings.players.size() ||
729- settings.players.at(id).state != PlayerSettings::stateShared)
730- return;
731+ settings.players.at(id).state != PlayerSettings::State::kShared)
732+ return 0;
733
734- uint8_t sharedplr = settings.players.at(id).shared_in;
735+ Widelands::PlayerNumber sharedplr = settings.players.at(id).shared_in;
736 for (; sharedplr < settings.players.size(); ++sharedplr) {
737- if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
738- settings.players.at(sharedplr).state != PlayerSettings::stateShared)
739+ if (settings.players.at(sharedplr).state != PlayerSettings::State::kClosed &&
740+ settings.players.at(sharedplr).state != PlayerSettings::State::kShared)
741 break;
742 }
743 if (sharedplr < settings.players.size()) {
744 // We have already found the next player
745- set_shared_in(id, sharedplr + 1);
746- return;
747+ return ++sharedplr;
748 }
749 sharedplr = 0;
750 for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) {
751- if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed &&
752- settings.players.at(sharedplr).state != PlayerSettings::stateShared)
753+ if (settings.players.at(sharedplr).state != PlayerSettings::State::kClosed &&
754+ settings.players.at(sharedplr).state != PlayerSettings::State::kShared)
755 break;
756 }
757 if (sharedplr < settings.players.at(id).shared_in) {
758- // We have found the next player
759- set_shared_in(id, sharedplr + 1);
760- return;
761- } else {
762- // No fitting player found
763- return toggle_type(id);
764- }
765-}
766-
767-/// Toggle through the initializations
768-void NetworkPlayerSettingsBackend::toggle_init(uint8_t id) {
769- const GameSettings& settings = s->settings();
770-
771- if (id >= settings.players.size())
772- return;
773-
774- const PlayerSettings& player = settings.players[id];
775- for (const TribeBasicInfo& temp_tribeinfo : settings.tribes) {
776- if (temp_tribeinfo.name == player.tribe) {
777- return s->set_player_init(
778- id, (player.initialization_index + 1) % temp_tribeinfo.initializations.size());
779- }
780- }
781- NEVER_HERE();
782-}
783-
784-/// Toggle through the teams
785-void NetworkPlayerSettingsBackend::toggle_team(uint8_t id) {
786- const GameSettings& settings = s->settings();
787-
788- if (id >= settings.players.size())
789- return;
790-
791- Widelands::TeamNumber currentteam = settings.players.at(id).team;
792- Widelands::TeamNumber maxteam = settings.players.size() / 2;
793- Widelands::TeamNumber newteam;
794-
795- if (currentteam >= maxteam)
796- newteam = 0;
797- else
798- newteam = currentteam + 1;
799-
800- s->set_player_team(id, newteam);
801+ ++sharedplr;
802+ }
803+ return sharedplr;
804+}
805+
806+/// Sets the initialization for the player slot (Headquarters, Fortified Village etc.)
807+void NetworkPlayerSettingsBackend::set_init(PlayerSlot id, uint8_t initialization_index) {
808+ const GameSettings& settings = s->settings();
809+ if (id >= settings.players.size()) {
810+ return;
811+ }
812+ s->set_player_init(id, initialization_index);
813+}
814+
815+/// Sets the team for the player slot
816+void NetworkPlayerSettingsBackend::set_team(PlayerSlot id, Widelands::TeamNumber team) {
817+ const GameSettings& settings = s->settings();
818+ if (id >= settings.players.size())
819+ return;
820+ s->set_player_team(id, team);
821 }
822
823 /// Check if all settings for the player are still valid
824-void NetworkPlayerSettingsBackend::refresh(uint8_t id) {
825+// NOCOM this function is deleted in the final branch, so you can skip reviewing the code in it.
826+void NetworkPlayerSettingsBackend::refresh(PlayerSlot id) {
827 const GameSettings& settings = s->settings();
828
829 if (id >= settings.players.size())
830 return;
831
832 const PlayerSettings& player = settings.players[id];
833-
834- if (player.state == PlayerSettings::stateShared) {
835+ if (player.state == PlayerSettings::State::kShared) {
836+ const Widelands::PlayerNumber old_shared_in = player.shared_in;
837+ Widelands::PlayerNumber new_shared_in = player.shared_in;
838 // ensure that the shared_in player is able to use this starting position
839- if (player.shared_in > settings.players.size())
840- toggle_shared_in(id);
841- if (settings.players.at(player.shared_in - 1).state == PlayerSettings::stateClosed ||
842- settings.players.at(player.shared_in - 1).state == PlayerSettings::stateShared)
843- toggle_shared_in(id);
844-
845- if (shared_in_tribe[id] != settings.players.at(player.shared_in - 1).tribe) {
846- s->set_player_tribe(id, settings.players.at(player.shared_in - 1).tribe,
847- settings.players.at(player.shared_in - 1).random_tribe);
848- shared_in_tribe[id] = settings.players.at(id).tribe;
849+ if (new_shared_in == 0 || new_shared_in > settings.players.size()) {
850+ new_shared_in = find_next_shared_in(id);
851+ if (new_shared_in == 0) {
852+ // No fitting player found
853+ set_player_state(id, PlayerSettings::State::kClosed);
854+ return;
855+ }
856+ }
857+
858+ if (settings.players.at(new_shared_in - 1).state == PlayerSettings::State::kClosed ||
859+ settings.players.at(new_shared_in - 1).state == PlayerSettings::State::kShared) {
860+ new_shared_in = find_next_shared_in(id);
861+ if (new_shared_in == 0) {
862+ // No fitting player found
863+ set_player_state(id, PlayerSettings::State::kClosed);
864+ return;
865+ }
866+ }
867+
868+ if (new_shared_in != old_shared_in &&
869+ settings.players.at(new_shared_in - 1).state != PlayerSettings::State::kClosed &&
870+ settings.players.at(new_shared_in - 1).state != PlayerSettings::State::kShared) {
871+ if (shared_in_tribe[id] != settings.players.at(new_shared_in - 1).tribe) {
872+ s->set_player_tribe(id, settings.players.at(new_shared_in - 1).tribe,
873+ settings.players.at(new_shared_in - 1).random_tribe);
874+ shared_in_tribe[id] = settings.players.at(id).tribe;
875+ }
876+ set_shared_in(id, new_shared_in);
877 }
878 }
879 }
880
881=== modified file 'src/network/network_player_settings_backend.h'
882--- src/network/network_player_settings_backend.h 2017-08-16 10:14:29 +0000
883+++ src/network/network_player_settings_backend.h 2017-08-30 12:04:30 +0000
884@@ -22,31 +22,29 @@
885
886 #include "graphic/playercolor.h"
887 #include "logic/game_settings.h"
888+#include "logic/widelands.h"
889
890 struct NetworkPlayerSettingsBackend {
891
892 explicit NetworkPlayerSettingsBackend(GameSettingsProvider* const settings) : s(settings) {
893- for (uint8_t i = 0; i < kMaxPlayers; ++i)
894+ for (PlayerSlot i = 0; i < kMaxPlayers; ++i)
895 shared_in_tribe[i] = std::string();
896 }
897
898- void toggle_type(uint8_t id);
899- void set_shared_in(uint8_t id, uint8_t shared_in);
900- void set_tribe(uint8_t id, const std::string& tribename);
901- void set_block_tribe_selection(bool blocked) {
902- tribe_selection_blocked = blocked;
903- }
904+ void set_player_state(PlayerSlot id, PlayerSettings::State state);
905+ void set_player_ai(PlayerSlot id, const std::string& name, bool random_ai);
906+ void set_shared_in(PlayerSlot id, Widelands::PlayerNumber shared_in);
907+ void set_tribe(PlayerSlot id, const std::string& tribename);
908+ void set_init(PlayerSlot id, uint8_t initialization_index);
909+ void set_team(PlayerSlot id, Widelands::TeamNumber team);
910
911- void toggle_init(uint8_t id);
912- void toggle_team(uint8_t id);
913- void refresh(uint8_t id);
914+ void refresh(PlayerSlot id);
915
916 GameSettingsProvider* const s;
917 std::string shared_in_tribe[kMaxPlayers];
918- bool tribe_selection_blocked = false;
919
920 private:
921- void toggle_shared_in(uint8_t id);
922+ Widelands::PlayerNumber find_next_shared_in(PlayerSlot id);
923 };
924
925 #endif // end of include guard: WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H
926
927=== modified file 'src/network/network_protocol.h'
928--- src/network/network_protocol.h 2017-05-06 11:00:19 +0000
929+++ src/network/network_protocol.h 2017-08-30 12:04:30 +0000
930@@ -395,9 +395,9 @@
931 * \li unsigned_8: new shared player
932 *
933 * \note The client must not assume that the host will accept this
934- * request. Change of team number only becomes effective when/if the host
935+ * request. Change of the initialization only becomes effective when/if the host
936 * replies with a \ref NETCMD_SETTING_PLAYER or
937- * \ref NETCMD_SETTING_ALLPLAYERS indicating the changed team.
938+ * \ref NETCMD_SETTING_ALLPLAYERS indicating the changed initialization.
939 */
940 NETCMD_SETTING_CHANGESHARED = 27,
941
942@@ -406,6 +406,7 @@
943 * client wants to change a player's initialisation.
944 *
945 * \li unsigned_8: number of the player
946+ * \li unsigned_8: index of the initialization
947 *
948 * \note The client must not assume that the host will accept this
949 * request. Change of team number only becomes effective when/if the host
950
951=== modified file 'src/notifications/note_ids.h'
952--- src/notifications/note_ids.h 2017-03-02 12:21:57 +0000
953+++ src/notifications/note_ids.h 2017-08-30 12:04:30 +0000
954@@ -40,7 +40,9 @@
955 Economy,
956 GraphicResolutionChanged,
957 NoteExpeditionCanceled,
958- Sound
959+ Sound,
960+ Dropdown,
961+ GameSettings
962 };
963
964 #endif // end of include guard: WL_NOTIFICATIONS_NOTE_IDS_H
965
966=== modified file 'src/ui_basic/dropdown.cc'
967--- src/ui_basic/dropdown.cc 2017-06-02 08:14:40 +0000
968+++ src/ui_basic/dropdown.cc 2017-08-30 12:04:30 +0000
969@@ -43,6 +43,8 @@
970
971 namespace UI {
972
973+int BaseDropdown::next_id_ = 0;
974+
975 BaseDropdown::BaseDropdown(UI::Panel* parent,
976 int32_t x,
977 int32_t y,
978@@ -56,11 +58,14 @@
979 : UI::Panel(parent,
980 x,
981 y,
982- type == DropdownType::kTextual ? w : button_dimension,
983+ type == DropdownType::kPictorial ? button_dimension : w,
984 // Height only to fit the button, so we can use this in Box layout.
985 base_height(button_dimension)),
986+ id_(next_id_++),
987 max_list_height_(h - 2 * get_h()),
988 list_width_(w),
989+ list_offset_x_(0),
990+ list_offset_y_(0),
991 button_dimension_(button_dimension),
992 mouse_tolerance_(50),
993 button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
994@@ -79,13 +84,23 @@
995 "dropdown_label",
996 0,
997 0,
998- type == DropdownType::kTextual ? w - button_dimension : button_dimension,
999+ type == DropdownType::kTextual ?
1000+ w - button_dimension :
1001+ type == DropdownType::kTextualNarrow ? w : button_dimension,
1002 get_h(),
1003 background,
1004 label),
1005 label_(label),
1006 type_(type),
1007 is_enabled_(true) {
1008+
1009+ // Close whenever another dropdown is opened
1010+ subscriber_ = Notifications::subscribe<NoteDropdown>([this](const NoteDropdown& note) {
1011+ if (id_ != note.id) {
1012+ close();
1013+ }
1014+ });
1015+
1016 assert(max_list_height_ > 0);
1017 // Hook into highest parent that we can get so that we can drop down outside the panel.
1018 // Positioning breaks down with TabPanels, so we exclude them.
1019@@ -125,26 +140,42 @@
1020
1021 void BaseDropdown::layout() {
1022 const int base_h = base_height(button_dimension_);
1023- const int w = type_ == DropdownType::kTextual ? get_w() : button_dimension_;
1024+ const int w = type_ == DropdownType::kPictorial ? button_dimension_ : get_w();
1025 button_box_.set_size(w, base_h);
1026 display_button_.set_desired_size(
1027 type_ == DropdownType::kTextual ? w - button_dimension_ : w, base_h);
1028 int new_list_height =
1029 std::min(static_cast<int>(list_->size()) * list_->get_lineheight(), max_list_height_);
1030- list_->set_size(type_ == DropdownType::kTextual ? w : list_width_, new_list_height);
1031+ list_->set_size(type_ != DropdownType::kPictorial ? w : list_width_, new_list_height);
1032 set_desired_size(w, base_h);
1033
1034 // Update list position. The list is hooked into the highest parent that we can get so that we
1035 // can drop down outside the panel. Positioning breaks down with TabPanels, so we exclude them.
1036 UI::Panel* parent = get_parent();
1037- int new_list_y = get_y() + get_h() + parent->get_y();
1038+ int new_list_y = get_y() + parent->get_y();
1039 int new_list_x = get_x() + parent->get_x();
1040 while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
1041 parent = parent->get_parent();
1042 new_list_y += parent->get_y();
1043 new_list_x += parent->get_x();
1044 }
1045- list_->set_pos(Vector2i(new_list_x, new_list_y));
1046+
1047+ // Drop up instead of down if it doesn't fit
1048+ if (new_list_y + list_->get_h() > g_gr->get_yres()) {
1049+ list_offset_y_ = -list_->get_h();
1050+ } else {
1051+ list_offset_y_ = display_button_.get_h();
1052+ }
1053+
1054+ // Right align instead of left align if it doesn't fit
1055+ if (new_list_x + list_->get_w() > g_gr->get_xres()) {
1056+ list_offset_x_ = display_button_.get_w() - list_->get_w();
1057+ if (push_button_ != nullptr) {
1058+ list_offset_x_ += push_button_->get_w();
1059+ }
1060+ }
1061+
1062+ list_->set_pos(Vector2i(new_list_x + list_offset_x_, new_list_y + list_offset_y_));
1063 }
1064
1065 void BaseDropdown::add(const std::string& name,
1066@@ -178,16 +209,29 @@
1067
1068 void BaseDropdown::set_label(const std::string& text) {
1069 label_ = text;
1070- if (type_ == DropdownType::kTextual) {
1071+ if (type_ != DropdownType::kPictorial) {
1072 display_button_.set_title(label_);
1073 }
1074 }
1075
1076+void BaseDropdown::set_image(const Image* image) {
1077+ display_button_.set_pic(image);
1078+}
1079+
1080 void BaseDropdown::set_tooltip(const std::string& text) {
1081 tooltip_ = text;
1082 display_button_.set_tooltip(tooltip_);
1083 }
1084
1085+void BaseDropdown::set_errored(const std::string& error_message) {
1086+ set_tooltip((boost::format(_("%1%: %2%")) % _("Error") % error_message).str());
1087+ if (type_ != DropdownType::kPictorial) {
1088+ set_label(_("Error"));
1089+ } else {
1090+ set_image(g_gr->images().get("images/ui_basic/different.png"));
1091+ }
1092+}
1093+
1094 void BaseDropdown::set_enabled(bool on) {
1095 is_enabled_ = on;
1096 set_can_focus(on);
1097@@ -213,6 +257,7 @@
1098 }
1099
1100 void BaseDropdown::clear() {
1101+ close();
1102 list_->clear();
1103 current_selection_ = list_->selection_index();
1104 list_->set_size(list_->get_w(), 0);
1105@@ -239,7 +284,7 @@
1106 /** TRANSLATORS: Selection in Dropdown menus. */
1107 pgettext("dropdown", "Not Selected");
1108
1109- if (type_ == DropdownType::kTextual) {
1110+ if (type_ != DropdownType::kPictorial) {
1111 if (label_.empty()) {
1112 display_button_.set_title(name);
1113 } else {
1114@@ -268,19 +313,29 @@
1115 return;
1116 }
1117 list_->set_visible(!list_->is_visible());
1118+ if (type_ != DropdownType::kTextual) {
1119+ display_button_.set_perm_pressed(list_->is_visible());
1120+ }
1121 if (list_->is_visible()) {
1122 list_->move_to_top();
1123 focus();
1124+ Notifications::publish(NoteDropdown(id_));
1125 }
1126 // Make sure that the list covers and deactivates the elements below it
1127 set_layout_toplevel(list_->is_visible());
1128 }
1129
1130+void BaseDropdown::close() {
1131+ if (is_expanded()) {
1132+ toggle_list();
1133+ }
1134+}
1135+
1136 bool BaseDropdown::is_mouse_away() const {
1137- return (get_mouse_position().x + mouse_tolerance_) < 0 ||
1138- get_mouse_position().x > (list_->get_w() + mouse_tolerance_) ||
1139- (get_mouse_position().y + mouse_tolerance_ / 2) < 0 ||
1140- get_mouse_position().y > (get_h() + list_->get_h() + mouse_tolerance_);
1141+ return (get_mouse_position().x + mouse_tolerance_) < list_offset_x_ ||
1142+ get_mouse_position().x > (list_offset_x_ + list_->get_w() + mouse_tolerance_) ||
1143+ (get_mouse_position().y + mouse_tolerance_ / 2) < list_offset_y_ ||
1144+ get_mouse_position().y > (list_offset_y_ + get_h() + list_->get_h() + mouse_tolerance_);
1145 }
1146
1147 bool BaseDropdown::handle_key(bool down, SDL_Keysym code) {
1148
1149=== modified file 'src/ui_basic/dropdown.h'
1150--- src/ui_basic/dropdown.h 2017-04-21 09:56:42 +0000
1151+++ src/ui_basic/dropdown.h 2017-08-30 12:04:30 +0000
1152@@ -27,14 +27,26 @@
1153
1154 #include "graphic/graphic.h"
1155 #include "graphic/image.h"
1156+#include "notifications/note_ids.h"
1157+#include "notifications/notifications.h"
1158 #include "ui_basic/box.h"
1159 #include "ui_basic/button.h"
1160 #include "ui_basic/listselect.h"
1161 #include "ui_basic/panel.h"
1162
1163 namespace UI {
1164-
1165-enum class DropdownType { kTextual, kPictorial };
1166+// We use this to make sure that only 1 dropdown is open at the same time.
1167+struct NoteDropdown {
1168+ CAN_BE_SENT_AS_NOTE(NoteId::Dropdown)
1169+
1170+ int id;
1171+
1172+ explicit NoteDropdown(int init_id) : id(init_id) {
1173+ }
1174+};
1175+
1176+/// The narrow textual dropdown omits the extra push button
1177+enum class DropdownType { kTextual, kTextualNarrow, kPictorial };
1178
1179 /// Implementation for a dropdown menu that lets the user select a value.
1180 class BaseDropdown : public Panel {
1181@@ -63,6 +75,7 @@
1182 ~BaseDropdown();
1183
1184 public:
1185+ /// An entry was selected
1186 boost::signals2::signal<void()> selected;
1187
1188 /// \return true if an element has been selected from the list
1189@@ -72,9 +85,15 @@
1190 /// and displayed on the display button.
1191 void set_label(const std::string& text);
1192
1193+ /// Sets the image for the display button (for pictorial dropdowns).
1194+ void set_image(const Image* image);
1195+
1196 /// Sets the tooltip for the display button.
1197 void set_tooltip(const std::string& text);
1198
1199+ /// Displays an error message on the button instead of the current selection.
1200+ void set_errored(const std::string& error_message);
1201+
1202 /// Enables/disables the dropdown selection.
1203 void set_enabled(bool on);
1204
1205@@ -142,12 +161,22 @@
1206 void set_value();
1207 /// Toggles the dropdown list on and off.
1208 void toggle_list();
1209+ /// Toggle the list closed if the dropdown is currently expanded.
1210+ void close();
1211
1212 /// Returns true if the mouse pointer left the vicinity of the dropdown.
1213 bool is_mouse_away() const;
1214
1215+ /// Give each dropdown a unique ID
1216+ static int next_id_;
1217+ const int id_;
1218+ std::unique_ptr<Notifications::Subscriber<NoteDropdown>> subscriber_;
1219+
1220+ // Dimensions
1221 int max_list_height_;
1222 int list_width_;
1223+ int list_offset_x_;
1224+ int list_offset_y_;
1225 int button_dimension_;
1226 const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse
1227 UI::Box button_box_;
1228
1229=== modified file 'src/ui_basic/listselect.cc'
1230--- src/ui_basic/listselect.cc 2017-06-15 16:34:58 +0000
1231+++ src/ui_basic/listselect.cc 2017-08-30 12:04:30 +0000
1232@@ -359,9 +359,9 @@
1233 if (selection_mode_ == ListselectLayout::kDropdown) {
1234 RGBAColor black(0, 0, 0, 255);
1235 // top edge
1236- dst.brighten_rect(Recti(0, 0, get_w(), 2), BUTTON_EDGE_BRIGHT_FACTOR / 4);
1237+ dst.brighten_rect(Recti(0, 0, get_w(), 2), BUTTON_EDGE_BRIGHT_FACTOR);
1238 // left edge
1239- dst.brighten_rect(Recti(0, 0, 2, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
1240+ dst.brighten_rect(Recti(0, 2, 2, get_h()), BUTTON_EDGE_BRIGHT_FACTOR);
1241 // bottom edge
1242 dst.fill_rect(Recti(2, get_h() - 2, get_eff_w() - 2, 1), black);
1243 dst.fill_rect(Recti(1, get_h() - 1, get_eff_w() - 1, 1), black);
1244
1245=== modified file 'src/ui_fsmenu/launch_game.cc'
1246--- src/ui_fsmenu/launch_game.cc 2017-05-18 06:52:04 +0000
1247+++ src/ui_fsmenu/launch_game.cc 2017-08-30 12:04:30 +0000
1248@@ -167,7 +167,7 @@
1249 t->get_string("description"));
1250 }
1251 } catch (LuaTableKeyError& e) {
1252- log("LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(),
1253+ log("Launch Game: Error loading win condition: %s %s\n", win_condition_script.c_str(),
1254 e.what());
1255 }
1256 }
1257@@ -177,9 +177,8 @@
1258 "could not be loaded.")) %
1259 settings_->settings().mapfilename)
1260 .str();
1261- win_condition_dropdown_.set_label(_("Error"));
1262- win_condition_dropdown_.set_tooltip(error_message);
1263- log("LaunchSPG: Exception: %s %s\n", error_message.c_str(), e.what());
1264+ win_condition_dropdown_.set_errored(error_message);
1265+ log("Launch Game: Exception: %s %s\n", error_message.c_str(), e.what());
1266 }
1267 }
1268
1269@@ -202,8 +201,8 @@
1270 }
1271 }
1272 } catch (LuaTableKeyError& e) {
1273- log(
1274- "LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(), e.what());
1275+ log("Launch Game: Error loading win condition: %s %s\n", win_condition_script.c_str(),
1276+ e.what());
1277 }
1278 if (!is_usable) {
1279 t.reset(nullptr);
1280
1281=== modified file 'src/ui_fsmenu/launch_mpg.cc'
1282--- src/ui_fsmenu/launch_mpg.cc 2017-05-18 06:52:04 +0000
1283+++ src/ui_fsmenu/launch_mpg.cc 2017-08-30 12:04:30 +0000
1284@@ -181,7 +181,7 @@
1285 map_info_.set_text(_("The host has not yet selected a map or saved game."));
1286
1287 mpsg_ = new MultiPlayerSetupGroup(
1288- this, get_w() / 50, get_h() / 8, get_w() * 57 / 80, get_h(), settings, butw_, buth_);
1289+ this, get_w() / 50, change_map_or_save_.get_y(), get_w() * 57 / 80, get_h(), settings, buth_);
1290
1291 // If we are the host, open the map or save selection menu at startup
1292 if (settings_->settings().usernum == 0 && settings_->settings().mapname.empty()) {
1293@@ -448,11 +448,11 @@
1294 settings_->set_player_closeable(i, map.get_scenario_player_closeable(i + 1));
1295 std::string ai(map.get_scenario_player_ai(i + 1));
1296 if (!ai.empty()) {
1297- settings_->set_player_state(i, PlayerSettings::stateComputer);
1298+ settings_->set_player_state(i, PlayerSettings::State::kComputer);
1299 settings_->set_player_ai(i, ai);
1300- } else if (settings.players.at(i).state != PlayerSettings::stateHuman &&
1301- settings.players.at(i).state != PlayerSettings::stateOpen) {
1302- settings_->set_player_state(i, PlayerSettings::stateOpen);
1303+ } else if (settings.players.at(i).state != PlayerSettings::State::kHuman &&
1304+ settings.players.at(i).state != PlayerSettings::State::kOpen) {
1305+ settings_->set_player_state(i, PlayerSettings::State::kOpen);
1306 }
1307 }
1308 }
1309@@ -485,7 +485,7 @@
1310 infotext += ":\n ";
1311 infotext += closed_string;
1312 // Close the player
1313- settings_->set_player_state(i - 1, PlayerSettings::stateClosed);
1314+ settings_->set_player_state(i - 1, PlayerSettings::State::kClosed);
1315 continue; // if tribe is empty, the player does not exist
1316 }
1317
1318@@ -495,10 +495,10 @@
1319
1320 if (player_save_ai[i - 1].empty()) {
1321 // Assure that player is open
1322- if (settings_->settings().players.at(i - 1).state != PlayerSettings::stateHuman)
1323- settings_->set_player_state(i - 1, PlayerSettings::stateOpen);
1324+ if (settings_->settings().players.at(i - 1).state != PlayerSettings::State::kHuman)
1325+ settings_->set_player_state(i - 1, PlayerSettings::State::kOpen);
1326 } else {
1327- settings_->set_player_state(i - 1, PlayerSettings::stateComputer);
1328+ settings_->set_player_state(i - 1, PlayerSettings::State::kComputer);
1329 settings_->set_player_ai(i - 1, player_save_ai[i - 1]);
1330 }
1331
1332@@ -563,7 +563,9 @@
1333 "\n";
1334 infotext +=
1335 std::string("• ") +
1336- (boost::format(ngettext("%u Player", "%u Players", nr_players_)) % nr_players_).str() + "\n";
1337+ (boost::format(ngettext("%u Player", "%u Players", nr_players_)) % cast_unsigned(nr_players_))
1338+ .str() +
1339+ "\n";
1340 if (settings_->settings().scenario)
1341 infotext += std::string("• ") + (boost::format(_("Scenario mode selected"))).str() + "\n";
1342 infotext += "\n";
1343
1344=== modified file 'src/ui_fsmenu/launch_spg.cc'
1345--- src/ui_fsmenu/launch_spg.cc 2017-05-04 05:08:14 +0000
1346+++ src/ui_fsmenu/launch_spg.cc 2017-08-30 12:04:30 +0000
1347@@ -210,8 +210,8 @@
1348 for (uint8_t i = 0; i < nr_players_; ++i) {
1349 pos_[i]->set_visible(true);
1350 const PlayerSettings& player = settings.players[i];
1351- pos_[i]->set_enabled(!is_scenario_ && (player.state == PlayerSettings::stateOpen ||
1352- player.state == PlayerSettings::stateComputer));
1353+ pos_[i]->set_enabled(!is_scenario_ && (player.state == PlayerSettings::State::kOpen ||
1354+ player.state == PlayerSettings::State::kComputer));
1355 }
1356 for (uint32_t i = nr_players_; i < kMaxPlayers; ++i)
1357 pos_[i]->set_visible(false);
1358@@ -291,14 +291,14 @@
1359 // Check if a still valid place is open.
1360 for (uint8_t i = 0; i < newplayernumber; ++i) {
1361 PlayerSettings position = settings.players.at(i);
1362- if (position.state == PlayerSettings::stateOpen) {
1363+ if (position.state == PlayerSettings::State::kOpen) {
1364 switch_to_position(i);
1365 return;
1366 }
1367 }
1368
1369 // Kick player 1 and take the position
1370- settings_->set_player_state(0, PlayerSettings::stateClosed);
1371- settings_->set_player_state(0, PlayerSettings::stateOpen);
1372+ settings_->set_player_state(0, PlayerSettings::State::kClosed);
1373+ settings_->set_player_state(0, PlayerSettings::State::kOpen);
1374 switch_to_position(0);
1375 }
1376
1377=== modified file 'src/wui/multiplayersetupgroup.cc'
1378--- src/wui/multiplayersetupgroup.cc 2017-05-03 07:24:06 +0000
1379+++ src/wui/multiplayersetupgroup.cc 2017-08-30 12:04:30 +0000
1380@@ -19,8 +19,10 @@
1381
1382 #include "wui/multiplayersetupgroup.h"
1383
1384+#include <memory>
1385 #include <string>
1386
1387+#include <boost/algorithm/string.hpp>
1388 #include <boost/format.hpp>
1389 #include <boost/lexical_cast.hpp>
1390
1391@@ -36,6 +38,7 @@
1392 #include "logic/map_objects/tribes/tribe_descr.h"
1393 #include "logic/map_objects/tribes/tribes.h"
1394 #include "logic/player.h"
1395+#include "logic/widelands.h"
1396 #include "ui_basic/button.h"
1397 #include "ui_basic/checkbox.h"
1398 #include "ui_basic/dropdown.h"
1399@@ -43,9 +46,11 @@
1400 #include "ui_basic/scrollbar.h"
1401 #include "ui_basic/textarea.h"
1402
1403+#define AI_NAME_PREFIX "ai" AI_NAME_SEPARATOR
1404+
1405 struct MultiPlayerClientGroup : public UI::Box {
1406 MultiPlayerClientGroup(UI::Panel* const parent,
1407- uint8_t id,
1408+ PlayerSlot id,
1409 int32_t const /* x */,
1410 int32_t const /* y */,
1411 int32_t const w,
1412@@ -82,8 +87,8 @@
1413 p = -1;
1414
1415 for (++p; p < static_cast<int16_t>(s->settings().players.size()); ++p) {
1416- if (s->settings().players.at(p).state == PlayerSettings::stateHuman ||
1417- s->settings().players.at(p).state == PlayerSettings::stateOpen) {
1418+ if (s->settings().players.at(p).state == PlayerSettings::State::kHuman ||
1419+ s->settings().players.at(p).state == PlayerSettings::State::kOpen) {
1420 s->set_player_number(p);
1421 return;
1422 }
1423@@ -109,8 +114,7 @@
1424 position_image =
1425 playercolor_image(us.position, "images/players/genstats_player.png");
1426 temp_tooltip =
1427- (boost::format(_("Player %u")) % static_cast<unsigned int>(us.position + 1))
1428- .str();
1429+ (boost::format(_("Player %u")) % cast_unsigned(us.position + 1)).str();
1430 } else {
1431 position_image = g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png");
1432 temp_tooltip = _("Spectator");
1433@@ -135,13 +139,13 @@
1434 UI::Icon* type_icon;
1435 UI::Button* type;
1436 GameSettingsProvider* const s;
1437- uint8_t const id_;
1438+ PlayerSlot const id_;
1439 int16_t save_; // saved position to check rewrite need.
1440 };
1441
1442 struct MultiPlayerPlayerGroup : public UI::Box {
1443 MultiPlayerPlayerGroup(UI::Panel* const parent,
1444- uint8_t id,
1445+ PlayerSlot id,
1446 int32_t const /* x */,
1447 int32_t const /* y */,
1448 int32_t const w,
1449@@ -149,175 +153,343 @@
1450 GameSettingsProvider* const settings,
1451 NetworkPlayerSettingsBackend* const npsb)
1452 : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h),
1453- player(nullptr),
1454- type(nullptr),
1455- init(nullptr),
1456 s(settings),
1457 n(npsb),
1458 id_(id),
1459+ player(this,
1460+ "player",
1461+ 0,
1462+ 0,
1463+ h,
1464+ h,
1465+ nullptr,
1466+ playercolor_image(id, "images/players/player_position_menu.png"),
1467+ (boost::format(_("Player %u")) % cast_unsigned(id_ + 1)).str(),
1468+ UI::Button::Style::kFlat),
1469+ type_dropdown_(this, 0, 0, 50, 200, h, _("Type"), UI::DropdownType::kPictorial),
1470 tribes_dropdown_(this, 0, 0, 50, 200, h, _("Tribe"), UI::DropdownType::kPictorial),
1471- last_state_(PlayerSettings::stateClosed),
1472- last_player_amount_(0) {
1473+ init_dropdown_(this, 0, 0, w - 4 * h, 200, h, "", UI::DropdownType::kTextualNarrow),
1474+ team_dropdown_(this, 0, 0, h, 200, h, _("Team"), UI::DropdownType::kPictorial),
1475+ last_state_(PlayerSettings::State::kClosed),
1476+ tribe_selection_locked_(false),
1477+ init_selection_locked_(false),
1478+ team_selection_locked_(false) {
1479 set_size(w, h);
1480- tribes_dropdown_.set_visible(false);
1481- tribes_dropdown_.set_enabled(false);
1482+
1483+ player.set_disable_style(UI::ButtonDisableStyle::kFlat);
1484+ player.set_enabled(false);
1485+
1486+ type_dropdown_.selected.connect(
1487+ boost::bind(&MultiPlayerPlayerGroup::set_type, boost::ref(*this)));
1488 tribes_dropdown_.selected.connect(
1489 boost::bind(&MultiPlayerPlayerGroup::set_tribe_or_shared_in, boost::ref(*this)));
1490+ init_dropdown_.selected.connect(
1491+ boost::bind(&MultiPlayerPlayerGroup::set_init, boost::ref(*this)));
1492+ team_dropdown_.selected.connect(
1493+ boost::bind(&MultiPlayerPlayerGroup::set_team, boost::ref(*this)));
1494
1495- const Image* player_image = playercolor_image(id, "images/players/player_position_menu.png");
1496- assert(player_image);
1497- player = new UI::Icon(this, 0, 0, h, h, player_image);
1498- add(player);
1499- type = new UI::Button(
1500- this, "player_type", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
1501- type->sigclicked.connect(
1502- boost::bind(&MultiPlayerPlayerGroup::toggle_type, boost::ref(*this)));
1503- add(type);
1504+ add(&player);
1505+ add(&type_dropdown_);
1506 add(&tribes_dropdown_);
1507- init = new UI::Button(this, "player_init", 0, 0, w - 4 * h, h,
1508- g_gr->images().get("images/ui_basic/but1.png"), "");
1509- init->sigclicked.connect(
1510- boost::bind(&MultiPlayerPlayerGroup::toggle_init, boost::ref(*this)));
1511- add(init);
1512- team = new UI::Button(
1513- this, "player_team", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), "");
1514- team->sigclicked.connect(
1515- boost::bind(&MultiPlayerPlayerGroup::toggle_team, boost::ref(*this)));
1516- add(team);
1517- }
1518-
1519- /// Toggle through the types
1520- void toggle_type() {
1521- n->toggle_type(id_);
1522+ add(&init_dropdown_);
1523+ add(&team_dropdown_);
1524+
1525+ subscriber_ =
1526+ Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings& note) {
1527+ if (id_ == note.position) {
1528+ refresh();
1529+ }
1530+ });
1531+
1532+ // Init dropdowns
1533+ refresh();
1534+ layout();
1535+ }
1536+
1537+ void layout() override {
1538+ const int max_height = g_gr->get_yres() * 3 / 4;
1539+ type_dropdown_.set_height(max_height);
1540+ tribes_dropdown_.set_height(max_height);
1541+ init_dropdown_.set_height(max_height);
1542+ team_dropdown_.set_height(max_height);
1543+ UI::Box::layout();
1544+ }
1545+
1546+ /// This will update the game settings for the type with the value
1547+ /// currently selected in the type dropdown.
1548+ void set_type() {
1549+ if (!s->can_change_player_state(id_)) {
1550+ return;
1551+ }
1552+ if (type_dropdown_.has_selection()) {
1553+ const std::string& selected = type_dropdown_.get_selected();
1554+ PlayerSettings::State state = PlayerSettings::State::kComputer;
1555+ if (selected == "closed") {
1556+ state = PlayerSettings::State::kClosed;
1557+ } else if (selected == "open") {
1558+ state = PlayerSettings::State::kOpen;
1559+ } else if (selected == "shared_in") {
1560+ state = PlayerSettings::State::kShared;
1561+ } else {
1562+ if (selected == AI_NAME_PREFIX "random") {
1563+ n->set_player_ai(id_, "", true);
1564+ } else {
1565+ if (boost::starts_with(selected, AI_NAME_PREFIX)) {
1566+ std::vector<std::string> parts;
1567+ boost::split(parts, selected, boost::is_any_of(AI_NAME_SEPARATOR));
1568+ assert(parts.size() == 2);
1569+ n->set_player_ai(id_, parts[1], false);
1570+ } else {
1571+ throw wexception("Unknown player state: %s\n", selected.c_str());
1572+ }
1573+ }
1574+ }
1575+ n->set_player_state(id_, state);
1576+ }
1577+ }
1578+
1579+ /// Rebuild the type dropdown from the server settings. This will keep the host and client UIs in
1580+ /// sync.
1581+ void rebuild_type_dropdown(const PlayerSettings& player_setting) {
1582+ if (type_dropdown_.empty()) {
1583+ type_dropdown_.clear();
1584+ // AIs
1585+ for (const auto* impl : ComputerPlayer::get_implementations()) {
1586+ type_dropdown_.add(_(impl->descname),
1587+ (boost::format(AI_NAME_PREFIX "%s") % impl->name).str(),
1588+ g_gr->images().get(impl->icon_filename), false, _(impl->descname));
1589+ }
1590+ /** TRANSLATORS: This is the name of an AI used in the game setup screens */
1591+ type_dropdown_.add(_("Random AI"), AI_NAME_PREFIX "random",
1592+ g_gr->images().get("images/ai/ai_random.png"), false, _("Random AI"));
1593+
1594+ // Slot state
1595+ type_dropdown_.add(_("Shared in"), "shared_in",
1596+ g_gr->images().get("images/ui_fsmenu/shared_in.png"), false,
1597+ _("Shared in"));
1598+
1599+ // Do not close a player in savegames or scenarios
1600+ if (!s->settings().savegame &&
1601+ (!s->settings().scenario || s->settings().players.at(id_).closeable)) {
1602+ type_dropdown_.add(_("Closed"), "closed",
1603+ g_gr->images().get("images/ui_basic/stop.png"), false, _("Closed"));
1604+ }
1605+
1606+ type_dropdown_.add(_("Open"), "open", g_gr->images().get("images/ui_basic/continue.png"),
1607+ false, _("Open"));
1608+
1609+ type_dropdown_.set_enabled(s->can_change_player_state(id_));
1610+ }
1611+
1612+ // Now select the entry according to server settings
1613+ if (player_setting.state == PlayerSettings::State::kHuman) {
1614+ type_dropdown_.set_image(g_gr->images().get("images/wui/stats/genstats_nrworkers.png"));
1615+ type_dropdown_.set_tooltip((boost::format(_("%1%: %2%")) % _("Type") % _("Human")).str());
1616+ } else if (player_setting.state == PlayerSettings::State::kClosed) {
1617+ type_dropdown_.select("closed");
1618+ } else if (player_setting.state == PlayerSettings::State::kOpen) {
1619+ type_dropdown_.select("open");
1620+ } else if (player_setting.state == PlayerSettings::State::kShared) {
1621+ type_dropdown_.select("shared_in");
1622+ } else {
1623+ if (player_setting.state == PlayerSettings::State::kComputer) {
1624+ if (player_setting.ai.empty()) {
1625+ type_dropdown_.set_errored(_("No AI"));
1626+ } else {
1627+ if (player_setting.random_ai) {
1628+ type_dropdown_.select(AI_NAME_PREFIX "random");
1629+ } else {
1630+ const ComputerPlayer::Implementation* impl =
1631+ ComputerPlayer::get_implementation(player_setting.ai);
1632+ type_dropdown_.select((boost::format(AI_NAME_PREFIX "%s") % impl->name).str());
1633+ }
1634+ }
1635+ }
1636+ }
1637+ }
1638+
1639+ bool has_tribe_access() {
1640+ return s->settings().players[id_].state == PlayerSettings::State::kShared ?
1641+ s->can_change_player_init(id_) :
1642+ s->can_change_player_tribe(id_);
1643 }
1644
1645 /// This will update the game settings for the tribe or shared_in with the value
1646 /// currently selected in the tribes dropdown.
1647 void set_tribe_or_shared_in() {
1648- n->set_block_tribe_selection(true);
1649+ if (!has_tribe_access()) {
1650+ return;
1651+ }
1652+
1653+ tribe_selection_locked_ = true;
1654 tribes_dropdown_.set_disable_style(s->settings().players[id_].state ==
1655- PlayerSettings::stateShared ?
1656+ PlayerSettings::State::kShared ?
1657 UI::ButtonDisableStyle::kPermpressed :
1658 UI::ButtonDisableStyle::kMonochrome);
1659 if (tribes_dropdown_.has_selection()) {
1660- if (s->settings().players[id_].state == PlayerSettings::stateShared) {
1661+ if (s->settings().players[id_].state == PlayerSettings::State::kShared) {
1662 n->set_shared_in(
1663 id_, boost::lexical_cast<unsigned int>(tribes_dropdown_.get_selected()));
1664 } else {
1665 n->set_tribe(id_, tribes_dropdown_.get_selected());
1666 }
1667 }
1668- n->set_block_tribe_selection(false);
1669- }
1670-
1671- /// Toggle through the initializations
1672- void toggle_init() {
1673- n->toggle_init(id_);
1674- }
1675-
1676- /// Toggle through the teams
1677- void toggle_team() {
1678- n->toggle_team(id_);
1679+ tribe_selection_locked_ = false;
1680 }
1681
1682 /// Helper function to cast shared_in for use in the dropdown.
1683- const std::string shared_in_as_string(uint8_t shared_in) {
1684- return boost::lexical_cast<std::string>(static_cast<unsigned int>(shared_in));
1685+ const std::string shared_in_as_string(PlayerSlot shared_in) {
1686+ return boost::lexical_cast<std::string>(cast_unsigned(shared_in));
1687 }
1688
1689- /// Update the tribes dropdown from the server settings if the server setting changed.
1690- /// This will keep the host and client UIs in sync.
1691- void update_tribes_dropdown(const PlayerSettings& player_setting) {
1692- if (player_setting.state == PlayerSettings::stateClosed ||
1693- player_setting.state == PlayerSettings::stateOpen) {
1694+ /// Rebuild the tribes dropdown from the server settings. This will keep the host and client UIs
1695+ /// in sync.
1696+ void rebuild_tribes_dropdown(const GameSettings& settings) {
1697+ if (tribe_selection_locked_) {
1698+ return;
1699+ }
1700+ const PlayerSettings& player_setting = settings.players[id_];
1701+ tribes_dropdown_.clear();
1702+
1703+ // We need to see the playercolor if setting shared_in is disabled
1704+ tribes_dropdown_.set_disable_style(player_setting.state == PlayerSettings::State::kShared ?
1705+ UI::ButtonDisableStyle::kPermpressed :
1706+ UI::ButtonDisableStyle::kMonochrome);
1707+
1708+ if (player_setting.state == PlayerSettings::State::kShared) {
1709+ for (size_t i = 0; i < settings.players.size(); ++i) {
1710+ if (i != id_) {
1711+ // Do not add players that are also shared_in or closed.
1712+ const PlayerSettings& other_setting = settings.players[i];
1713+ if (other_setting.state == PlayerSettings::State::kShared ||
1714+ other_setting.state == PlayerSettings::State::kClosed) {
1715+ continue;
1716+ }
1717+
1718+ const Image* player_image =
1719+ playercolor_image(i, "images/players/player_position_menu.png");
1720+ assert(player_image);
1721+ const std::string player_name =
1722+ /** TRANSLATORS: This is an option in multiplayer setup for sharing
1723+ another player's starting position. */
1724+ (boost::format(_("Shared in Player %u")) % cast_unsigned(i + 1)).str();
1725+ tribes_dropdown_.add(
1726+ player_name, shared_in_as_string(i + 1), player_image, false, player_name);
1727+ }
1728+ }
1729+ int shared_in = 0;
1730+ while (shared_in == id_) {
1731+ ++shared_in;
1732+ }
1733+ tribes_dropdown_.select(shared_in_as_string(shared_in + 1));
1734+ tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1);
1735+ } else {
1736+ {
1737+ i18n::Textdomain td("tribes");
1738+ for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
1739+ tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name,
1740+ g_gr->images().get(tribeinfo.icon), false, tribeinfo.tooltip);
1741+ }
1742+ }
1743+ tribes_dropdown_.add(pgettext("tribe", "Random"), "random",
1744+ g_gr->images().get("images/ui_fsmenu/random.png"), false,
1745+ _("The tribe will be selected at random"));
1746+ if (player_setting.random_tribe) {
1747+ tribes_dropdown_.select("random");
1748+ } else {
1749+ tribes_dropdown_.select(player_setting.tribe);
1750+ }
1751+ }
1752+ const bool has_access = has_tribe_access();
1753+ if (tribes_dropdown_.is_enabled() != has_access) {
1754+ tribes_dropdown_.set_enabled(has_access && tribes_dropdown_.size() > 1);
1755+ }
1756+ if (player_setting.state == PlayerSettings::State::kClosed ||
1757+ player_setting.state == PlayerSettings::State::kOpen) {
1758 return;
1759 }
1760 if (!tribes_dropdown_.is_visible()) {
1761 tribes_dropdown_.set_visible(true);
1762 }
1763- if (!tribes_dropdown_.is_expanded() && !n->tribe_selection_blocked &&
1764- tribes_dropdown_.has_selection()) {
1765- const std::string selected_tribe = tribes_dropdown_.get_selected();
1766- if (player_setting.state == PlayerSettings::stateShared) {
1767- const std::string shared_in = shared_in_as_string(player_setting.shared_in);
1768- if (shared_in != selected_tribe) {
1769- tribes_dropdown_.select(shared_in);
1770- }
1771- } else {
1772- if (player_setting.random_tribe) {
1773- if (selected_tribe != "random") {
1774- tribes_dropdown_.select("random");
1775- }
1776- } else if (selected_tribe != player_setting.tribe) {
1777- tribes_dropdown_.select(player_setting.tribe);
1778- }
1779- }
1780- }
1781- }
1782-
1783- /// If the map was changed or the selection mode changed between shared_in and tribe, rebuild the
1784- /// dropdown.
1785- void rebuild_tribes_dropdown(const GameSettings& settings) {
1786- const PlayerSettings& player_setting = settings.players[id_];
1787-
1788- if (player_setting.state == PlayerSettings::stateClosed ||
1789- player_setting.state == PlayerSettings::stateOpen) {
1790- return;
1791- }
1792-
1793- if (tribes_dropdown_.empty() || last_player_amount_ != settings.players.size() ||
1794- ((player_setting.state == PlayerSettings::stateShared ||
1795- last_state_ == PlayerSettings::stateShared) &&
1796- player_setting.state != last_state_)) {
1797- tribes_dropdown_.clear();
1798-
1799- // We need to see the playercolor if setting shared_in is disabled
1800- tribes_dropdown_.set_disable_style(player_setting.state == PlayerSettings::stateShared ?
1801- UI::ButtonDisableStyle::kPermpressed :
1802- UI::ButtonDisableStyle::kMonochrome);
1803-
1804- if (player_setting.state == PlayerSettings::stateShared) {
1805- for (size_t i = 0; i < settings.players.size(); ++i) {
1806- if (i != id_) {
1807- // TODO(GunChleoc): Do not add players that are also shared_in.
1808- const Image* player_image =
1809- playercolor_image(i, "images/players/player_position_menu.png");
1810- assert(player_image);
1811- const std::string player_name =
1812- /** TRANSLATORS: This is an option in multiplayer setup for sharing
1813- another player's starting position. */
1814- (boost::format(_("Shared in Player %u")) % static_cast<unsigned int>(i + 1))
1815- .str();
1816- tribes_dropdown_.add(
1817- player_name, shared_in_as_string(i + 1), player_image, false, player_name);
1818- }
1819- }
1820- int shared_in = 0;
1821- while (shared_in == id_) {
1822- ++shared_in;
1823- }
1824- tribes_dropdown_.select(shared_in_as_string(shared_in + 1));
1825- tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1);
1826- } else {
1827- {
1828- i18n::Textdomain td("tribes");
1829- for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
1830- tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name,
1831- g_gr->images().get(tribeinfo.icon), false,
1832- tribeinfo.tooltip);
1833- }
1834- }
1835- tribes_dropdown_.add(pgettext("tribe", "Random"), "random",
1836- g_gr->images().get("images/ui_fsmenu/random.png"), false,
1837- _("The tribe will be selected at random"));
1838- if (player_setting.random_tribe) {
1839- tribes_dropdown_.select("random");
1840- } else {
1841- tribes_dropdown_.select(player_setting.tribe);
1842- }
1843- }
1844- }
1845- last_player_amount_ = settings.players.size();
1846+ }
1847+
1848+ /// This will update the game settings for the initialization with the value
1849+ /// currently selected in the initialization dropdown.
1850+ void set_init() {
1851+ if (!s->can_change_player_init(id_)) {
1852+ return;
1853+ }
1854+ init_selection_locked_ = true;
1855+ if (init_dropdown_.has_selection()) {
1856+ n->set_init(id_, init_dropdown_.get_selected());
1857+ }
1858+ init_selection_locked_ = false;
1859+ }
1860+
1861+ /// Rebuild the init dropdown from the server settings. This will keep the host and client UIs in
1862+ /// sync.
1863+ void rebuild_init_dropdown(const GameSettings& settings) {
1864+ if (init_selection_locked_) {
1865+ return;
1866+ }
1867+
1868+ init_dropdown_.clear();
1869+ const PlayerSettings& player_setting = settings.players[id_];
1870+ if (settings.scenario) {
1871+ init_dropdown_.set_label(_("Scenario"));
1872+ } else if (settings.savegame) {
1873+ /** Translators: This is a game type */
1874+ init_dropdown_.set_label(_("Saved Game"));
1875+ } else {
1876+ init_dropdown_.set_label("");
1877+ i18n::Textdomain td("tribes"); // for translated initialisation
1878+ const TribeBasicInfo tribeinfo = Widelands::get_tribeinfo(player_setting.tribe);
1879+ for (size_t i = 0; i < tribeinfo.initializations.size(); ++i) {
1880+ const TribeBasicInfo::Initialization& addme = tribeinfo.initializations[i];
1881+ init_dropdown_.add(_(addme.descname), i, nullptr,
1882+ i == player_setting.initialization_index, _(addme.tooltip));
1883+ }
1884+ }
1885+
1886+ init_dropdown_.set_visible(true);
1887+ init_dropdown_.set_enabled(s->can_change_player_init(id_));
1888+ }
1889+
1890+ /// This will update the team settings with the value currently selected in the teams dropdown.
1891+ void set_team() {
1892+ team_selection_locked_ = true;
1893+ if (team_dropdown_.has_selection()) {
1894+ n->set_team(id_, team_dropdown_.get_selected());
1895+ }
1896+ team_selection_locked_ = false;
1897+ }
1898+
1899+ /// Rebuild the team dropdown from the server settings. This will keep the host and client UIs in
1900+ /// sync.
1901+ void rebuild_team_dropdown(const GameSettings& settings) {
1902+ if (team_selection_locked_) {
1903+ return;
1904+ }
1905+ const PlayerSettings& player_setting = settings.players[id_];
1906+ if (player_setting.state == PlayerSettings::State::kShared) {
1907+ team_dropdown_.set_visible(false);
1908+ team_dropdown_.set_enabled(false);
1909+ return;
1910+ }
1911+
1912+ team_dropdown_.clear();
1913+ team_dropdown_.add(_("No Team"), 0, g_gr->images().get("images/players/no_team.png"));
1914+#ifndef NDEBUG
1915+ const size_t no_of_team_colors = sizeof(kTeamColors) / sizeof(kTeamColors[0]);
1916+#endif
1917+ for (Widelands::TeamNumber t = 1; t <= settings.players.size() / 2; ++t) {
1918+ assert(t < no_of_team_colors);
1919+ team_dropdown_.add((boost::format(_("Team %d")) % cast_unsigned(t)).str(), t,
1920+ playercolor_image(kTeamColors[t], "images/players/team.png"));
1921+ }
1922+ team_dropdown_.select(player_setting.team);
1923+ team_dropdown_.set_visible(true);
1924+ team_dropdown_.set_enabled(s->can_change_player_team(id_));
1925 }
1926
1927 /// Refresh all user interfaces
1928@@ -334,121 +506,53 @@
1929 set_visible(true);
1930
1931 const PlayerSettings& player_setting = settings.players[id_];
1932- bool typeaccess = s->can_change_player_state(id_);
1933- bool tribeaccess = s->can_change_player_tribe(id_);
1934- bool const initaccess = s->can_change_player_init(id_);
1935- bool teamaccess = s->can_change_player_team(id_);
1936- type->set_enabled(typeaccess);
1937-
1938- rebuild_tribes_dropdown(settings);
1939-
1940- if (player_setting.state == PlayerSettings::stateClosed) {
1941- type->set_tooltip(_("Closed"));
1942- type->set_pic(g_gr->images().get("images/ui_basic/stop.png"));
1943- team->set_visible(false);
1944- team->set_enabled(false);
1945- tribes_dropdown_.set_visible(false);
1946- tribes_dropdown_.set_enabled(false);
1947- init->set_visible(false);
1948- init->set_enabled(false);
1949- return;
1950- } else if (player_setting.state == PlayerSettings::stateOpen) {
1951- type->set_tooltip(_("Open"));
1952- type->set_pic(g_gr->images().get("images/ui_basic/continue.png"));
1953- team->set_visible(false);
1954- team->set_enabled(false);
1955- tribes_dropdown_.set_visible(false);
1956- tribes_dropdown_.set_enabled(false);
1957- init->set_visible(false);
1958- init->set_enabled(false);
1959- return;
1960- } else if (player_setting.state == PlayerSettings::stateShared) {
1961- type->set_tooltip(_("Shared in"));
1962- type->set_pic(g_gr->images().get("images/ui_fsmenu/shared_in.png"));
1963-
1964- update_tribes_dropdown(player_setting);
1965-
1966- if (tribes_dropdown_.is_enabled() != initaccess) {
1967- tribes_dropdown_.set_enabled(initaccess && !n->tribe_selection_blocked &&
1968- tribes_dropdown_.size() > 1);
1969- }
1970-
1971- team->set_visible(false);
1972- team->set_enabled(false);
1973-
1974+
1975+ rebuild_type_dropdown(player_setting);
1976+
1977+ if (player_setting.state == PlayerSettings::State::kClosed ||
1978+ player_setting.state == PlayerSettings::State::kOpen) {
1979+ team_dropdown_.set_visible(false);
1980+ team_dropdown_.set_enabled(false);
1981+ tribes_dropdown_.set_visible(false);
1982+ tribes_dropdown_.set_enabled(false);
1983+ init_dropdown_.set_visible(false);
1984+ init_dropdown_.set_enabled(false);
1985 } else {
1986- std::string title;
1987- std::string pic = "images/";
1988- if (player_setting.state == PlayerSettings::stateComputer) {
1989- if (player_setting.ai.empty()) {
1990- title = _("Computer");
1991- pic += "novalue.png";
1992- } else {
1993- if (player_setting.random_ai) {
1994- /** TRANSLATORS: This is the name of an AI used in the game setup screens */
1995- title = _("Random AI");
1996- pic += "ai/ai_random.png";
1997- } else {
1998- const ComputerPlayer::Implementation* impl =
1999- ComputerPlayer::get_implementation(player_setting.ai);
2000- title = _(impl->descname);
2001- pic = impl->icon_filename;
2002- }
2003- }
2004- } else { // PlayerSettings::stateHuman
2005- title = _("Human");
2006- pic += "wui/stats/genstats_nrworkers.png";
2007- }
2008- type->set_tooltip(title.c_str());
2009- type->set_pic(g_gr->images().get(pic));
2010-
2011- update_tribes_dropdown(player_setting);
2012-
2013- if (tribes_dropdown_.is_enabled() != tribeaccess) {
2014- tribes_dropdown_.set_enabled(tribeaccess && !n->tribe_selection_blocked);
2015- }
2016-
2017- if (player_setting.team) {
2018- team->set_title(std::to_string(static_cast<unsigned int>(player_setting.team)));
2019- } else {
2020- team->set_title("--");
2021- }
2022- team->set_visible(true);
2023- team->set_enabled(teamaccess);
2024- }
2025- init->set_enabled(initaccess);
2026- init->set_visible(true);
2027-
2028- if (settings.scenario)
2029- init->set_title(_("Scenario"));
2030- else if (settings.savegame)
2031- /** Translators: This is a game type */
2032- init->set_title(_("Saved Game"));
2033- else {
2034- i18n::Textdomain td("tribes"); // for translated initialisation
2035- for (const TribeBasicInfo& tribeinfo : settings.tribes) {
2036- if (tribeinfo.name == player_setting.tribe) {
2037- init->set_title(
2038- _(tribeinfo.initializations.at(player_setting.initialization_index).descname));
2039- init->set_tooltip(
2040- _(tribeinfo.initializations.at(player_setting.initialization_index).tooltip));
2041- break;
2042- }
2043- }
2044- }
2045- last_state_ = player_setting.state;
2046+ rebuild_tribes_dropdown(settings);
2047+ rebuild_init_dropdown(settings);
2048+ rebuild_team_dropdown(settings);
2049+ }
2050+
2051+ // Trigger update for the other players for shared_in mode when slots open and close
2052+ if (last_state_ != player_setting.state) {
2053+ last_state_ = player_setting.state;
2054+ for (PlayerSlot slot = 0; slot < s->settings().players.size(); ++slot) {
2055+ if (slot != id_) {
2056+ Notifications::publish(NoteGameSettings(slot));
2057+ }
2058+ }
2059+ }
2060 }
2061
2062- UI::Icon* player;
2063- UI::Button* type;
2064- UI::Button* init;
2065- UI::Button* team;
2066 GameSettingsProvider* const s;
2067 NetworkPlayerSettingsBackend* const n;
2068- uint8_t const id_;
2069+ PlayerSlot const id_;
2070+
2071+ UI::Button player;
2072+ UI::Dropdown<std::string>
2073+ type_dropdown_; /// Select who owns the slot (human, AI, open, closed, shared-in).
2074 UI::Dropdown<std::string> tribes_dropdown_; /// Select the tribe or shared_in player.
2075- PlayerSettings::State last_state_; /// The dropdown needs updating if this changes
2076- size_t last_player_amount_; /// The dropdown needs rebuilding if this changes
2077+ UI::Dropdown<uintptr_t>
2078+ init_dropdown_; /// Select the initialization (Headquarters, Fortified Village etc.)
2079+ UI::Dropdown<uintptr_t> team_dropdown_; /// Select the team number
2080+ PlayerSettings::State
2081+ last_state_; /// The dropdowns for the other slots need updating if this changes
2082+ /// Lock rebuilding dropdowns so that they can close on selection
2083+ bool tribe_selection_locked_;
2084+ bool init_selection_locked_;
2085+ bool team_selection_locked_;
2086+
2087+ std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_;
2088 };
2089
2090 MultiPlayerSetupGroup::MultiPlayerSetupGroup(UI::Panel* const parent,
2091@@ -457,59 +561,34 @@
2092 int32_t const w,
2093 int32_t const h,
2094 GameSettingsProvider* const settings,
2095- uint32_t /* butw */,
2096 uint32_t buth)
2097 : UI::Panel(parent, x, y, w, h),
2098 s(settings),
2099 npsb(new NetworkPlayerSettingsBackend(s)),
2100- clientbox(this, 0, buth, UI::Box::Vertical, w / 3, h - buth),
2101- playerbox(this, w * 6 / 15, buth, UI::Box::Vertical, w * 9 / 15, h - buth),
2102+ clientbox(this, 0, 0, UI::Box::Vertical, w / 3, h),
2103+ playerbox(this, w * 6 / 15, 0, UI::Box::Vertical, w * 9 / 15, h),
2104 buth_(buth) {
2105- int small_font = UI_FONT_SIZE_SMALL * 3 / 4;
2106-
2107- // Clientbox and labels
2108- labels.push_back(new UI::Textarea(
2109- this, UI::Scrollbar::kSize * 6 / 5, buth / 3, w / 3 - buth - UI::Scrollbar::kSize * 2, buth));
2110- labels.back()->set_text(_("Client name"));
2111- labels.back()->set_fontsize(small_font);
2112-
2113- labels.push_back(new UI::Textarea(
2114- this, w / 3 - buth - UI::Scrollbar::kSize * 6 / 5, buth / 3, buth * 2, buth));
2115- labels.back()->set_text(_("Role"));
2116- labels.back()->set_fontsize(small_font);
2117-
2118- clientbox.set_size(w / 3, h - buth);
2119+ clientbox.set_size(w / 3, h);
2120 clientbox.set_scrolling(true);
2121
2122- // Playerbox and labels
2123- labels.push_back(new UI::Textarea(this, w * 6 / 15, buth / 3, buth, buth));
2124- labels.back()->set_text(_("Start"));
2125- labels.back()->set_fontsize(small_font);
2126-
2127- labels.push_back(new UI::Textarea(this, w * 6 / 15 + buth, buth / 3 - 10, buth, buth));
2128- labels.back()->set_text(_("Type"));
2129- labels.back()->set_fontsize(small_font);
2130-
2131- labels.push_back(new UI::Textarea(this, w * 6 / 15 + buth * 2, buth / 3, buth, buth));
2132- labels.back()->set_text(_("Tribe"));
2133- labels.back()->set_fontsize(small_font);
2134-
2135- labels.push_back(new UI::Textarea(
2136- this, w * 6 / 15 + buth * 3, buth / 3, w * 9 / 15 - 4 * buth, buth, UI::Align::kCenter));
2137- labels.back()->set_text(_("Initialization"));
2138- labels.back()->set_fontsize(small_font);
2139-
2140- labels.push_back(new UI::Textarea(this, w - buth, buth / 3, buth, buth, UI::Align::kRight));
2141- labels.back()->set_text(_("Team"));
2142- labels.back()->set_fontsize(small_font);
2143-
2144- playerbox.set_size(w * 9 / 15, h - buth);
2145+ // Playerbox
2146+ playerbox.set_size(w * 9 / 15, h);
2147 multi_player_player_groups.resize(kMaxPlayers);
2148- for (uint8_t i = 0; i < multi_player_player_groups.size(); ++i) {
2149+ for (PlayerSlot i = 0; i < multi_player_player_groups.size(); ++i) {
2150 multi_player_player_groups.at(i) =
2151- new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get());
2152+ new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth_, s, npsb.get());
2153 playerbox.add(multi_player_player_groups.at(i));
2154 }
2155+ subscriber_ =
2156+ Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings& /* note */) {
2157+ // Keep track of who is visible
2158+ for (PlayerSlot i = 0; i < multi_player_player_groups.size(); ++i) {
2159+ const bool should_be_visible = i < s->settings().players.size();
2160+ if (should_be_visible != multi_player_player_groups.at(i)->is_visible()) {
2161+ multi_player_player_groups.at(i)->set_visible(should_be_visible);
2162+ }
2163+ }
2164+ });
2165 refresh();
2166 }
2167
2168@@ -535,9 +614,4 @@
2169 }
2170 multi_player_client_groups.at(i)->refresh();
2171 }
2172-
2173- // Update player groups
2174- for (uint32_t i = 0; i < kMaxPlayers; ++i) {
2175- multi_player_player_groups.at(i)->refresh();
2176- }
2177 }
2178
2179=== modified file 'src/wui/multiplayersetupgroup.h'
2180--- src/wui/multiplayersetupgroup.h 2017-01-25 18:55:59 +0000
2181+++ src/wui/multiplayersetupgroup.h 2017-08-30 12:04:30 +0000
2182@@ -51,7 +51,6 @@
2183 int32_t w,
2184 int32_t h,
2185 GameSettingsProvider* settings,
2186- uint32_t butw,
2187 uint32_t buth);
2188 ~MultiPlayerSetupGroup();
2189
2190@@ -62,8 +61,9 @@
2191 std::unique_ptr<NetworkPlayerSettingsBackend> npsb;
2192 std::vector<MultiPlayerClientGroup*> multi_player_client_groups; // not owned
2193 std::vector<MultiPlayerPlayerGroup*> multi_player_player_groups; // not owned
2194+ std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_;
2195+
2196 UI::Box clientbox, playerbox;
2197- std::vector<UI::Textarea*> labels;
2198
2199 uint32_t buth_;
2200
2201
2202=== modified file 'src/wui/playerdescrgroup.cc'
2203--- src/wui/playerdescrgroup.cc 2017-01-30 14:40:12 +0000
2204+++ src/wui/playerdescrgroup.cc 2017-08-30 12:04:30 +0000
2205@@ -116,7 +116,7 @@
2206
2207 d->btnEnablePlayer->set_enabled(stateaccess);
2208
2209- if (player.state == PlayerSettings::stateClosed) {
2210+ if (player.state == PlayerSettings::State::kClosed) {
2211 d->btnEnablePlayer->set_state(false);
2212 d->btnPlayerTeam->set_visible(false);
2213 d->btnPlayerTeam->set_enabled(false);
2214@@ -132,7 +132,7 @@
2215 d->btnPlayerType->set_visible(true);
2216 d->btnPlayerType->set_enabled(stateaccess);
2217
2218- if (player.state == PlayerSettings::stateOpen) {
2219+ if (player.state == PlayerSettings::State::kOpen) {
2220 d->btnPlayerType->set_title(_("Open"));
2221 d->btnPlayerTeam->set_visible(false);
2222 d->btnPlayerTeam->set_visible(false);
2223@@ -144,7 +144,7 @@
2224 } else {
2225 std::string title;
2226
2227- if (player.state == PlayerSettings::stateComputer) {
2228+ if (player.state == PlayerSettings::State::kComputer) {
2229 if (player.ai.empty())
2230 title = _("Computer");
2231 else {
2232@@ -156,7 +156,7 @@
2233 title = _(impl->descname);
2234 }
2235 }
2236- } else { // PlayerSettings::stateHuman
2237+ } else { // PlayerSettings::State::stateHuman
2238 title = _("Human");
2239 }
2240 d->btnPlayerType->set_title(title);
2241@@ -215,11 +215,11 @@
2242 return;
2243
2244 if (on) {
2245- if (settings.players[d->plnum].state == PlayerSettings::stateClosed)
2246+ if (settings.players[d->plnum].state == PlayerSettings::State::kClosed)
2247 d->settings->next_player_state(d->plnum);
2248 } else {
2249- if (settings.players[d->plnum].state != PlayerSettings::stateClosed)
2250- d->settings->set_player_state(d->plnum, PlayerSettings::stateClosed);
2251+ if (settings.players[d->plnum].state != PlayerSettings::State::kClosed)
2252+ d->settings->set_player_state(d->plnum, PlayerSettings::State::kClosed);
2253 }
2254 }
2255