Merge lp:~widelands-dev/widelands/multiplayer_dropdowns into lp:widelands
- multiplayer_dropdowns
- Merge into trunk
Status: | Merged | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 8445 | ||||||||||||||||||||||||||||||||
Proposed branch: | lp:~widelands-dev/widelands/multiplayer_dropdowns | ||||||||||||||||||||||||||||||||
Merge into: | lp:widelands | ||||||||||||||||||||||||||||||||
Prerequisite: | lp:~widelands-dev/widelands/multiplayer_dropdowns_2_init_team | ||||||||||||||||||||||||||||||||
Diff against target: |
2785 lines (+1002/-778) 29 files modified
src/ai/computer_player.cc (+7/-6) src/ai/computer_player.h (+17/-0) src/ai/defaultai.h (+21/-18) src/graphic/playercolor.h (+13/-0) src/logic/CMakeLists.txt (+1/-0) src/logic/game.cc (+3/-3) src/logic/game_settings.cc (+38/-1) src/logic/game_settings.h (+47/-3) src/logic/single_player_game_settings_provider.cc (+9/-8) src/logic/single_player_game_settings_provider.h (+1/-1) src/network/CMakeLists.txt (+0/-1) src/network/gameclient.cc (+11/-6) src/network/gameclient.h (+2/-2) src/network/gamehost.cc (+76/-113) src/network/gamehost.h (+1/-1) src/network/network_player_settings_backend.cc (+58/-123) src/network/network_player_settings_backend.h (+9/-20) 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 (+30/-20) src/ui_fsmenu/launch_mpg.h (+1/-0) src/ui_fsmenu/launch_spg.cc (+5/-5) src/wui/multiplayersetupgroup.cc (+528/-409) src/wui/multiplayersetupgroup.h (+6/-6) src/wui/playerdescrgroup.cc (+7/-7) |
||||||||||||||||||||||||||||||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/multiplayer_dropdowns | ||||||||||||||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
SirVer | testing,compile,code-review | Approve | |
kaputtnik (community) | testing | Approve | |
Review via email: mp+326302@code.launchpad.net |
Commit message
For multiplayer game setup, use dropdowns instead of buttons to select player states (open, closed, human, AIs, shared in). This uses 2 new notifications:
- NoteGameSettings to synchronize and update the game setup dropdowns
- NoteDropdown to ensure that only 1 dropdown is ever open at the same time
Also:
- Dropdown lists will now automatically expand upwards rather than downwards if they wouldn't fit on screen.
- Removed the tiny labels in the multiplayer game setup, since the function of each dropdown can be identified per its tooltip
- Pulled out some functions to avoid code duplications and shifted some calculations to GameHost.
Description of the change
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
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 2381. State: passed. Details: https:/
Appveyor build 2209. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
I have now tried this with setting up games between my Linux and Windows machines using the AppVeyor build, and I get none of these problems. Not a single one, all dropdowns work perfectly. So, I have no clue how I could possibly debug this :(
kaputtnik (franku) wrote : | # |
I found what causing this:
Please try to click on the top area of a drop down button, so the tooltip is very near (or overlaps) the button. Then the drop down box disappear after mouse release.
If i click in the bottom area, all works fine.
GunChleoc (gunchleoc) wrote : | # |
Now I get what you mean. Dropdowns will autoclose when you move the mouse away, and the tolerance on the top edge was too small. Should be fixed now.
kaputtnik (franku) wrote : | # |
Yes, it's working now :-)
I am not familiar with the mutliplayer things, so i guess it's ok that the mutliplayer scenarios shows "Suggested teams" but one could not setup a team (e.g. Smugglers). Nevertheless disturbing.
This applies also to the map "Trident of fire" which is not a multiplayer scenario.
As said, i think this is ok, just wanted to mention here.
From what i can see all other things are fine:-)
GunChleoc (gunchleoc) wrote : | # |
Great that it was an easy fix for once :)
Regarding the suggested teams, that's a feature that I've had in my head for years now. Once we have finished overhauling all the screens, I will get to work. Also, once the overhaul is finished, I will make some options for the win conditions configurable, like length of game and fog of war. This will all have to wait for Build 21 though.
kaputtnik (franku) wrote : | # |
Thanks for implementing this. I think this is a great enhancement to the UI :-)
SirVer (sirver) wrote : | # |
Code lgtm. That was a lot of work, thanks for doing this!
I tested it and have a few comments:
- The team UI is rather clever - maybe too clever, we'll see what the users say.
- I found one issue: If I change myself to be dual-control with another player, I can change their team and tribe (expected). If I now change away again from dual-control, I can still change their team and tribe in my UI (but others are not updated, so I think only the button needs to be disabled)
Please see the other code reviews for the base branches for some more comments.
Great feature! Thanks.
GunChleoc (gunchleoc) wrote : | # |
Thanks for the code review and the testing!
I have kicked out the new cast_(un)signed macros and have created 2 bug reports for the remaining issues.
@bunnybot merge
Preview Diff
1 | === added file 'data/images/players/no_team.png' |
2 | Binary files data/images/players/no_team.png 1970-01-01 00:00:00 +0000 and data/images/players/no_team.png 2017-09-02 13:55:49 +0000 differ |
3 | === added file 'data/images/players/team.png' |
4 | Binary files data/images/players/team.png 1970-01-01 00:00:00 +0000 and data/images/players/team.png 2017-09-02 13:55:49 +0000 differ |
5 | === added file 'data/images/players/team_pc.png' |
6 | Binary files data/images/players/team_pc.png 1970-01-01 00:00:00 +0000 and data/images/players/team_pc.png 2017-09-02 13:55:49 +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-09-02 13:55:49 +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-09-02 13:55:49 +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-09-02 13:55:49 +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/graphic/playercolor.h' |
134 | --- src/graphic/playercolor.h 2017-04-28 06:47:01 +0000 |
135 | +++ src/graphic/playercolor.h 2017-09-02 13:55:49 +0000 |
136 | @@ -49,6 +49,19 @@ |
137 | RGBColor(144, 144, 144), // light gray |
138 | }; |
139 | |
140 | +// Hard coded team colors |
141 | +const RGBColor kTeamColors[kMaxPlayers / 2 + 1] = { |
142 | + RGBColor(100, 100, 100), // No team |
143 | + RGBColor(2, 2, 198), // blue |
144 | + RGBColor(255, 41, 0), // red |
145 | + RGBColor(255, 232, 0), // yellow |
146 | + RGBColor(59, 223, 3), // green |
147 | + RGBColor(57, 57, 57), // black/dark gray |
148 | + RGBColor(255, 172, 0), // orange |
149 | + RGBColor(215, 0, 218), // purple |
150 | + RGBColor(255, 255, 255), // white |
151 | +}; |
152 | + |
153 | /// Looks for a player color mask image, and if it finds one, |
154 | /// returns the image with added playercolor. If no player color |
155 | /// image file is found, gets the image from 'image_filename' |
156 | |
157 | === modified file 'src/logic/CMakeLists.txt' |
158 | --- src/logic/CMakeLists.txt 2017-07-11 19:38:04 +0000 |
159 | +++ src/logic/CMakeLists.txt 2017-09-02 13:55:49 +0000 |
160 | @@ -13,6 +13,7 @@ |
161 | io_filesystem |
162 | logic |
163 | logic_constants |
164 | + notifications |
165 | scripting_lua_interface |
166 | scripting_lua_table |
167 | ) |
168 | |
169 | === modified file 'src/logic/game.cc' |
170 | --- src/logic/game.cc 2017-08-20 06:43:11 +0000 |
171 | +++ src/logic/game.cc 2017-09-02 13:55:49 +0000 |
172 | @@ -269,10 +269,10 @@ |
173 | for (uint32_t i = 0; i < settings.players.size(); ++i) { |
174 | const PlayerSettings& playersettings = settings.players[i]; |
175 | |
176 | - if (playersettings.state == PlayerSettings::stateClosed || |
177 | - playersettings.state == PlayerSettings::stateOpen) |
178 | + if (playersettings.state == PlayerSettings::State::kClosed || |
179 | + playersettings.state == PlayerSettings::State::kOpen) |
180 | continue; |
181 | - else if (playersettings.state == PlayerSettings::stateShared) { |
182 | + else if (playersettings.state == PlayerSettings::State::kShared) { |
183 | shared.push_back(playersettings); |
184 | shared_num.push_back(i + 1); |
185 | continue; |
186 | |
187 | === modified file 'src/logic/game_settings.cc' |
188 | --- src/logic/game_settings.cc 2017-06-05 07:48:28 +0000 |
189 | +++ src/logic/game_settings.cc 2017-09-02 13:55:49 +0000 |
190 | @@ -1,1 +1,38 @@ |
191 | -// Dummy to make CMake happy |
192 | +/* |
193 | + * Copyright (C) 2017 by the Widelands Development Team |
194 | + * |
195 | + * This program is free software; you can redistribute it and/or |
196 | + * modify it under the terms of the GNU General Public License |
197 | + * as published by the Free Software Foundation; either version 2 |
198 | + * of the License, or (at your option) any later version. |
199 | + * |
200 | + * This program is distributed in the hope that it will be useful, |
201 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
202 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
203 | + * GNU General Public License for more details. |
204 | + * |
205 | + * You should have received a copy of the GNU General Public License |
206 | + * along with this program; if not, write to the Free Software |
207 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
208 | + * |
209 | + */ |
210 | + |
211 | +#include "logic/game_settings.h" |
212 | + |
213 | +Widelands::PlayerNumber GameSettings::find_shared(PlayerSlot slot) const { |
214 | + Widelands::PlayerNumber result = 1; |
215 | + for (; result <= players.size(); ++result) { |
216 | + if (PlayerSettings::can_be_shared(players.at(result - 1).state) && (result - 1) != slot) { |
217 | + break; |
218 | + } |
219 | + } |
220 | + return result; |
221 | +} |
222 | + |
223 | +bool GameSettings::is_shared_usable(PlayerSlot slot, Widelands::PlayerNumber shared) const { |
224 | + return shared <= players.size() && (shared - 1) != slot; |
225 | +} |
226 | + |
227 | +bool GameSettings::uncloseable(PlayerSlot slot) const { |
228 | + return (scenario && !players.at(slot).closeable) || savegame; |
229 | +} |
230 | |
231 | === modified file 'src/logic/game_settings.h' |
232 | --- src/logic/game_settings.h 2017-08-16 04:31:56 +0000 |
233 | +++ src/logic/game_settings.h 2017-09-02 13:55:49 +0000 |
234 | @@ -28,11 +28,22 @@ |
235 | #include "logic/map_objects/tribes/tribe_basic_info.h" |
236 | #include "logic/player_end_result.h" |
237 | #include "logic/widelands.h" |
238 | +#include "notifications/note_ids.h" |
239 | +#include "notifications/notifications.h" |
240 | #include "scripting/lua_interface.h" |
241 | #include "scripting/lua_table.h" |
242 | |
243 | +// PlayerSlot 0 will give us Widelands::PlayerNumber 1 etc., so we rename it to avoid confusion. |
244 | +// TODO(GunChleoc): Rename all uint8_t to PlayerSlot or Widelands::PlayerNumber |
245 | +using PlayerSlot = Widelands::PlayerNumber; |
246 | + |
247 | struct PlayerSettings { |
248 | - enum State { stateOpen, stateHuman, stateComputer, stateClosed, stateShared }; |
249 | + enum class State { kOpen, kHuman, kComputer, kClosed, kShared }; |
250 | + |
251 | + /// Returns whether the given state allows sharing a slot at all |
252 | + static bool can_be_shared(PlayerSettings::State state) { |
253 | + return state != PlayerSettings::State::kClosed && state != PlayerSettings::State::kShared; |
254 | + } |
255 | |
256 | State state; |
257 | uint8_t initialization_index; |
258 | @@ -71,6 +82,28 @@ |
259 | // not |
260 | }; |
261 | |
262 | +/// The gamehost/gameclient are sending those to notify about status changes, which are then picked |
263 | +/// up by the UI. |
264 | +struct NoteGameSettings { |
265 | + CAN_BE_SENT_AS_NOTE(NoteId::GameSettings) |
266 | + |
267 | + enum class Action { |
268 | + kUser, // A client has picked a different player slot / become an observer |
269 | + kPlayer, // A player slot has changed its status (type, tribe etc.) |
270 | + kMap // A new map/savegame was selected |
271 | + }; |
272 | + |
273 | + Action action; |
274 | + PlayerSlot position; |
275 | + uint8_t usernum; |
276 | + |
277 | + explicit NoteGameSettings(Action init_action, |
278 | + PlayerSlot init_position = std::numeric_limits<uint8_t>::max(), |
279 | + uint8_t init_usernum = UserSettings::none()) |
280 | + : action(init_action), position(init_position), usernum(init_usernum) { |
281 | + } |
282 | +}; |
283 | + |
284 | /** |
285 | * Holds all settings about a game that can be configured before the |
286 | * game actually starts. |
287 | @@ -92,6 +125,14 @@ |
288 | } |
289 | } |
290 | |
291 | + /// Find a player number that the slot could share in. Does not guarantee that a viable slot was |
292 | + /// actually found. |
293 | + Widelands::PlayerNumber find_shared(PlayerSlot slot) const; |
294 | + /// Check if the player number returned by find_shared is usable |
295 | + bool is_shared_usable(PlayerSlot slot, Widelands::PlayerNumber shared) const; |
296 | + /// Savegame slots and certain scenario slots can't be closed |
297 | + bool uncloseable(PlayerSlot slot) const; |
298 | + |
299 | /// Number of player position |
300 | int16_t playernum; |
301 | /// Number of users entry |
302 | @@ -154,7 +195,9 @@ |
303 | bool savegame = false) = 0; |
304 | virtual void set_player_state(uint8_t number, PlayerSettings::State) = 0; |
305 | virtual void set_player_ai(uint8_t number, const std::string&, bool const random_ai = false) = 0; |
306 | - virtual void next_player_state(uint8_t number) = 0; |
307 | + // Multiplayer no longer toggles per button |
308 | + virtual void next_player_state(uint8_t /* number */) { |
309 | + } |
310 | virtual void |
311 | set_player_tribe(uint8_t number, const std::string&, bool const random_tribe = false) = 0; |
312 | virtual void set_player_init(uint8_t number, uint8_t index) = 0; |
313 | @@ -163,10 +206,11 @@ |
314 | virtual void set_player_number(uint8_t number) = 0; |
315 | virtual void set_player_team(uint8_t number, Widelands::TeamNumber team) = 0; |
316 | virtual void set_player_closeable(uint8_t number, bool closeable) = 0; |
317 | - virtual void set_player_shared(uint8_t number, uint8_t shared) = 0; |
318 | + virtual void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) = 0; |
319 | virtual void set_win_condition_script(const std::string& wc) = 0; |
320 | virtual std::string get_win_condition_script() = 0; |
321 | |
322 | + // For retrieving tips texts |
323 | struct NoTribe {}; |
324 | const std::string& get_players_tribe() { |
325 | if (UserSettings::highest_playernum() < settings().playernum) |
326 | |
327 | === modified file 'src/logic/single_player_game_settings_provider.cc' |
328 | --- src/logic/single_player_game_settings_provider.cc 2017-08-09 19:25:04 +0000 |
329 | +++ src/logic/single_player_game_settings_provider.cc 2017-09-02 13:55:49 +0000 |
330 | @@ -81,14 +81,15 @@ |
331 | |
332 | while (oldplayers < maxplayers) { |
333 | PlayerSettings& player = s.players[oldplayers]; |
334 | - player.state = (oldplayers == 0) ? PlayerSettings::stateHuman : PlayerSettings::stateComputer; |
335 | + player.state = |
336 | + (oldplayers == 0) ? PlayerSettings::State::kHuman : PlayerSettings::State::kComputer; |
337 | player.tribe = s.tribes.at(0).name; |
338 | player.random_tribe = false; |
339 | player.initialization_index = 0; |
340 | player.name = (boost::format(_("Player %u")) % (oldplayers + 1)).str(); |
341 | player.team = 0; |
342 | // Set default computerplayer ai type |
343 | - if (player.state == PlayerSettings::stateComputer) { |
344 | + if (player.state == PlayerSettings::State::kComputer) { |
345 | const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations(); |
346 | if (impls.size() > 1) { |
347 | player.ai = impls.at(0)->name; |
348 | @@ -107,8 +108,8 @@ |
349 | if (number == s.playernum || number >= s.players.size()) |
350 | return; |
351 | |
352 | - if (state == PlayerSettings::stateOpen) |
353 | - state = PlayerSettings::stateComputer; |
354 | + if (state == PlayerSettings::State::kOpen) |
355 | + state = PlayerSettings::State::kComputer; |
356 | |
357 | s.players[number].state = state; |
358 | } |
359 | @@ -147,7 +148,7 @@ |
360 | s.players[number].ai = (*it)->name; |
361 | } |
362 | |
363 | - s.players[number].state = PlayerSettings::stateComputer; |
364 | + s.players[number].state = PlayerSettings::State::kComputer; |
365 | } |
366 | |
367 | void SinglePlayerGameSettingsProvider::set_player_tribe(uint8_t const number, |
368 | @@ -199,7 +200,7 @@ |
369 | // nothing to do |
370 | } |
371 | |
372 | -void SinglePlayerGameSettingsProvider::set_player_shared(uint8_t, uint8_t) { |
373 | +void SinglePlayerGameSettingsProvider::set_player_shared(PlayerSlot, Widelands::PlayerNumber) { |
374 | // nothing to do |
375 | } |
376 | |
377 | @@ -219,8 +220,8 @@ |
378 | return; |
379 | PlayerSettings const position = settings().players.at(number); |
380 | PlayerSettings const player = settings().players.at(settings().playernum); |
381 | - if (number < settings().players.size() && (position.state == PlayerSettings::stateOpen || |
382 | - position.state == PlayerSettings::stateComputer)) { |
383 | + if (number < settings().players.size() && (position.state == PlayerSettings::State::kOpen || |
384 | + position.state == PlayerSettings::State::kComputer)) { |
385 | set_player(number, player); |
386 | set_player(settings().playernum, position); |
387 | s.playernum = number; |
388 | |
389 | === modified file 'src/logic/single_player_game_settings_provider.h' |
390 | --- src/logic/single_player_game_settings_provider.h 2017-02-10 14:12:36 +0000 |
391 | +++ src/logic/single_player_game_settings_provider.h 2017-09-02 13:55:49 +0000 |
392 | @@ -54,7 +54,7 @@ |
393 | void set_player_init(uint8_t const number, uint8_t const index) override; |
394 | void set_player_team(uint8_t number, Widelands::TeamNumber team) override; |
395 | void set_player_closeable(uint8_t, bool) override; |
396 | - void set_player_shared(uint8_t, uint8_t) override; |
397 | + void set_player_shared(PlayerSlot, Widelands::PlayerNumber) override; |
398 | void set_player_name(uint8_t const number, const std::string& name) override; |
399 | void set_player(uint8_t const number, const PlayerSettings& ps) override; |
400 | void set_player_number(uint8_t const number) override; |
401 | |
402 | === modified file 'src/network/CMakeLists.txt' |
403 | --- src/network/CMakeLists.txt 2017-05-14 21:06:23 +0000 |
404 | +++ src/network/CMakeLists.txt 2017-09-02 13:55:49 +0000 |
405 | @@ -33,7 +33,6 @@ |
406 | build_info |
407 | chat |
408 | game_io |
409 | - graphic_playercolor |
410 | helper |
411 | io_fileread |
412 | io_filesystem |
413 | |
414 | === modified file 'src/network/gameclient.cc' |
415 | --- src/network/gameclient.cc 2017-08-09 17:55:34 +0000 |
416 | +++ src/network/gameclient.cc 2017-09-02 13:55:49 +0000 |
417 | @@ -356,25 +356,25 @@ |
418 | // client is not allowed to do this |
419 | } |
420 | |
421 | -void GameClient::set_player_shared(uint8_t number, uint8_t player) { |
422 | +void GameClient::set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) { |
423 | if ((number != d->settings.playernum)) |
424 | return; |
425 | |
426 | SendPacket s; |
427 | s.unsigned_8(NETCMD_SETTING_CHANGESHARED); |
428 | s.unsigned_8(number); |
429 | - s.unsigned_8(player); |
430 | + s.unsigned_8(shared); |
431 | d->net->send(s); |
432 | } |
433 | |
434 | -void GameClient::set_player_init(uint8_t number, uint8_t) { |
435 | +void GameClient::set_player_init(uint8_t number, uint8_t initialization_index) { |
436 | if ((number != d->settings.playernum)) |
437 | return; |
438 | |
439 | - // Host will decide what to change, therefore the init is not send, just the request to change |
440 | SendPacket s; |
441 | s.unsigned_8(NETCMD_SETTING_CHANGEINIT); |
442 | s.unsigned_8(number); |
443 | + s.unsigned_8(initialization_index); |
444 | d->net->send(s); |
445 | } |
446 | |
447 | @@ -404,8 +404,8 @@ |
448 | return; |
449 | // Same if the player is not selectable |
450 | if (number < d->settings.players.size() && |
451 | - (d->settings.players.at(number).state == PlayerSettings::stateClosed || |
452 | - d->settings.players.at(number).state == PlayerSettings::stateComputer)) |
453 | + (d->settings.players.at(number).state == PlayerSettings::State::kClosed || |
454 | + d->settings.players.at(number).state == PlayerSettings::State::kComputer)) |
455 | return; |
456 | |
457 | // Send request |
458 | @@ -459,6 +459,7 @@ |
459 | player.random_ai = packet.unsigned_8() == 1; |
460 | player.team = packet.unsigned_8(); |
461 | player.shared_in = packet.unsigned_8(); |
462 | + Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kPlayer, number)); |
463 | } |
464 | |
465 | void GameClient::receive_one_user(uint32_t const number, StreamRead& packet) { |
466 | @@ -473,10 +474,13 @@ |
467 | d->settings.users.at(number).name = packet.string(); |
468 | d->settings.users.at(number).position = packet.signed_32(); |
469 | d->settings.users.at(number).ready = packet.unsigned_8() == 1; |
470 | + |
471 | if (static_cast<int32_t>(number) == d->settings.usernum) { |
472 | d->localplayername = d->settings.users.at(number).name; |
473 | d->settings.playernum = d->settings.users.at(number).position; |
474 | } |
475 | + Notifications::publish( |
476 | + NoteGameSettings(NoteGameSettings::Action::kUser, d->settings.playernum, number)); |
477 | } |
478 | |
479 | void GameClient::send(const std::string& msg) { |
480 | @@ -567,6 +571,7 @@ |
481 | // New map was set, so we clean up the buffer of a previously requested file |
482 | if (file_) |
483 | delete file_; |
484 | + Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kMap)); |
485 | break; |
486 | } |
487 | |
488 | |
489 | === modified file 'src/network/gameclient.h' |
490 | --- src/network/gameclient.h 2017-07-05 19:21:57 +0000 |
491 | +++ src/network/gameclient.h 2017-09-02 13:55:49 +0000 |
492 | @@ -84,13 +84,13 @@ |
493 | virtual void set_player_tribe(uint8_t number, |
494 | const std::string& tribe, |
495 | bool const random_tribe = false) override; |
496 | - void set_player_init(uint8_t number, uint8_t index) override; |
497 | + void set_player_init(uint8_t number, uint8_t initialization_index) override; |
498 | void set_player_name(uint8_t number, const std::string& name) override; |
499 | void set_player(uint8_t number, const PlayerSettings& ps) override; |
500 | void set_player_number(uint8_t number) override; |
501 | void set_player_team(uint8_t number, Widelands::TeamNumber team) override; |
502 | void set_player_closeable(uint8_t number, bool closeable) override; |
503 | - void set_player_shared(uint8_t number, uint8_t shared) override; |
504 | + void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) override; |
505 | void set_win_condition_script(const std::string&) override; |
506 | std::string get_win_condition_script() override; |
507 | |
508 | |
509 | === modified file 'src/network/gamehost.cc' |
510 | --- src/network/gamehost.cc 2017-08-19 23:24:28 +0000 |
511 | +++ src/network/gamehost.cc 2017-09-02 13:55:49 +0000 |
512 | @@ -81,13 +81,16 @@ |
513 | return true; |
514 | } |
515 | bool can_change_player_state(uint8_t const number) override { |
516 | + if (number >= settings().players.size()) { |
517 | + return false; |
518 | + } |
519 | if (settings().savegame) |
520 | - return settings().players.at(number).state != PlayerSettings::stateClosed; |
521 | + return settings().players.at(number).state != PlayerSettings::State::kClosed; |
522 | else if (settings().scenario) |
523 | - return ((settings().players.at(number).state == PlayerSettings::stateOpen || |
524 | - settings().players.at(number).state == PlayerSettings::stateHuman) && |
525 | + return ((settings().players.at(number).state == PlayerSettings::State::kOpen || |
526 | + settings().players.at(number).state == PlayerSettings::State::kHuman) && |
527 | settings().players.at(number).closeable) || |
528 | - settings().players.at(number).state == PlayerSettings::stateClosed; |
529 | + settings().players.at(number).state == PlayerSettings::State::kClosed; |
530 | return true; |
531 | } |
532 | bool can_change_player_tribe(uint8_t const number) override { |
533 | @@ -105,7 +108,7 @@ |
534 | return false; |
535 | if (number == settings().playernum) |
536 | return true; |
537 | - return settings().players.at(number).state == PlayerSettings::stateComputer; |
538 | + return settings().players.at(number).state == PlayerSettings::State::kComputer; |
539 | } |
540 | |
541 | bool can_launch() override { |
542 | @@ -124,83 +127,6 @@ |
543 | |
544 | host_->set_player_state(number, state); |
545 | } |
546 | - void next_player_state(uint8_t const number) override { |
547 | - if (number > settings().players.size()) |
548 | - return; |
549 | - |
550 | - PlayerSettings::State newstate = PlayerSettings::stateClosed; |
551 | - switch (host_->settings().players.at(number).state) { |
552 | - case PlayerSettings::stateClosed: |
553 | - // In savegames : closed players can not be changed. |
554 | - assert(!host_->settings().savegame); |
555 | - newstate = PlayerSettings::stateOpen; |
556 | - break; |
557 | - case PlayerSettings::stateOpen: |
558 | - case PlayerSettings::stateHuman: |
559 | - if (host_->settings().scenario) { |
560 | - assert(host_->settings().players.at(number).closeable); |
561 | - newstate = PlayerSettings::stateClosed; |
562 | - break; |
563 | - } // else fall through |
564 | - FALLS_THROUGH; |
565 | - case PlayerSettings::stateComputer: { |
566 | - const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations(); |
567 | - ComputerPlayer::ImplementationVector::const_iterator it = impls.begin(); |
568 | - if (host_->settings().players.at(number).ai.empty()) { |
569 | - set_player_ai(number, (*it)->name); |
570 | - newstate = PlayerSettings::stateComputer; |
571 | - break; |
572 | - } |
573 | - do { |
574 | - ++it; |
575 | - if ((*(it - 1))->name == host_->settings().players.at(number).ai) |
576 | - break; |
577 | - } while (it != impls.end()); |
578 | - if (settings().players.at(number).random_ai) { |
579 | - set_player_ai(number, std::string()); |
580 | - set_player_name(number, std::string()); |
581 | - // Do not share a player in savegames or scenarios |
582 | - if (host_->settings().scenario || host_->settings().savegame) |
583 | - newstate = PlayerSettings::stateOpen; |
584 | - else { |
585 | - uint8_t shared = 0; |
586 | - for (; shared < settings().players.size(); ++shared) { |
587 | - if (settings().players.at(shared).state != PlayerSettings::stateClosed && |
588 | - settings().players.at(shared).state != PlayerSettings::stateShared) |
589 | - break; |
590 | - } |
591 | - if (shared < settings().players.size()) { |
592 | - newstate = PlayerSettings::stateShared; |
593 | - set_player_shared(number, shared + 1); |
594 | - } else |
595 | - newstate = PlayerSettings::stateClosed; |
596 | - } |
597 | - } else if (it == impls.end()) { |
598 | - do { |
599 | - uint8_t random = (std::rand() % impls.size()); // Choose a random AI |
600 | - it = impls.begin() + random; |
601 | - } while ((*it)->type == ComputerPlayer::Implementation::Type::kEmpty); |
602 | - set_player_ai(number, (*it)->name, true); |
603 | - newstate = PlayerSettings::stateComputer; |
604 | - break; |
605 | - } else { |
606 | - set_player_ai(number, (*it)->name); |
607 | - newstate = PlayerSettings::stateComputer; |
608 | - } |
609 | - break; |
610 | - } |
611 | - case PlayerSettings::stateShared: { |
612 | - // Do not close a player in savegames or scenarios |
613 | - if (host_->settings().scenario || host_->settings().savegame) |
614 | - newstate = PlayerSettings::stateOpen; |
615 | - else |
616 | - newstate = PlayerSettings::stateClosed; |
617 | - break; |
618 | - } |
619 | - } |
620 | - |
621 | - host_->set_player_state(number, newstate, true); |
622 | - } |
623 | |
624 | void |
625 | set_player_tribe(uint8_t number, const std::string& tribe, bool const random_tribe) override { |
626 | @@ -208,9 +134,10 @@ |
627 | return; |
628 | |
629 | if (number == settings().playernum || |
630 | - settings().players.at(number).state == PlayerSettings::stateComputer || |
631 | - settings().players.at(number).state == PlayerSettings::stateShared || |
632 | - settings().players.at(number).state == PlayerSettings::stateOpen) // For savegame loading |
633 | + settings().players.at(number).state == PlayerSettings::State::kComputer || |
634 | + settings().players.at(number).state == PlayerSettings::State::kShared || |
635 | + settings().players.at(number).state == |
636 | + PlayerSettings::State::kOpen) // For savegame loading |
637 | host_->set_player_tribe(number, tribe, random_tribe); |
638 | } |
639 | |
640 | @@ -219,7 +146,7 @@ |
641 | return; |
642 | |
643 | if (number == settings().playernum || |
644 | - settings().players.at(number).state == PlayerSettings::stateComputer) |
645 | + settings().players.at(number).state == PlayerSettings::State::kComputer) |
646 | host_->set_player_team(number, team); |
647 | } |
648 | |
649 | @@ -229,7 +156,7 @@ |
650 | host_->set_player_closeable(number, closeable); |
651 | } |
652 | |
653 | - void set_player_shared(uint8_t number, uint8_t shared) override { |
654 | + void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) override { |
655 | if (number >= host_->settings().players.size()) |
656 | return; |
657 | host_->set_player_shared(number, shared); |
658 | @@ -1019,9 +946,9 @@ |
659 | // but not all should be closed! |
660 | bool one_not_closed = false; |
661 | for (PlayerSettings& setting : d->settings.players) { |
662 | - if (setting.state != PlayerSettings::stateClosed) |
663 | + if (setting.state != PlayerSettings::State::kClosed) |
664 | one_not_closed = true; |
665 | - if (setting.state == PlayerSettings::stateOpen) |
666 | + if (setting.state == PlayerSettings::State::kOpen) |
667 | return false; |
668 | } |
669 | return one_not_closed; |
670 | @@ -1071,7 +998,7 @@ |
671 | |
672 | while (oldplayers < maxplayers) { |
673 | PlayerSettings& player = d->settings.players.at(oldplayers); |
674 | - player.state = PlayerSettings::stateOpen; |
675 | + player.state = PlayerSettings::State::kOpen; |
676 | player.name = ""; |
677 | player.tribe = d->settings.tribes.at(0).name; |
678 | player.random_tribe = false; |
679 | @@ -1146,9 +1073,7 @@ |
680 | if (player.state == state) |
681 | return; |
682 | |
683 | - SendPacket s; |
684 | - |
685 | - if (player.state == PlayerSettings::stateHuman) { |
686 | + if (player.state == PlayerSettings::State::kHuman) { |
687 | // 0 is host and has no client |
688 | if (d->settings.users.at(0).position == number) { |
689 | d->settings.users.at(0).position = UserSettings::none(); |
690 | @@ -1168,13 +1093,6 @@ |
691 | break; |
692 | } |
693 | } |
694 | - |
695 | - // broadcast change |
696 | - s.unsigned_8(NETCMD_SETTING_USER); |
697 | - s.unsigned_32(i); |
698 | - write_setting_user(s, i); |
699 | - broadcast(s); |
700 | - |
701 | break; |
702 | } |
703 | } |
704 | @@ -1182,15 +1100,53 @@ |
705 | |
706 | player.state = state; |
707 | |
708 | - if (player.state == PlayerSettings::stateComputer) |
709 | + // Make sure that shared slots have a player number to share in |
710 | + if (player.state == PlayerSettings::State::kShared) { |
711 | + const PlayerSlot shared = d->settings.find_shared(number); |
712 | + if (d->settings.is_shared_usable(number, shared)) { |
713 | + set_player_shared(number, shared); |
714 | + } else { |
715 | + player.state = PlayerSettings::State::kClosed; |
716 | + } |
717 | + } |
718 | + |
719 | + // Update shared positions for other players |
720 | + for (size_t i = 0; i < d->settings.players.size(); ++i) { |
721 | + if (i == number) { |
722 | + // Don't set own state |
723 | + continue; |
724 | + } |
725 | + if (d->settings.players.at(i).state == PlayerSettings::State::kShared) { |
726 | + const PlayerSlot shared = d->settings.find_shared(i); |
727 | + if (d->settings.is_shared_usable(i, shared)) { |
728 | + set_player_shared(i, shared); |
729 | + } else { |
730 | + set_player_state(i, PlayerSettings::State::kClosed, host); |
731 | + } |
732 | + } |
733 | + } |
734 | + |
735 | + // Make sure that slots that are not closeable stay open |
736 | + if (player.state == PlayerSettings::State::kClosed && d->settings.uncloseable(number)) { |
737 | + player.state = PlayerSettings::State::kOpen; |
738 | + } |
739 | + |
740 | + if (player.state == PlayerSettings::State::kComputer) |
741 | player.name = get_computer_player_name(number); |
742 | |
743 | - // Broadcast change |
744 | + // Broadcast change to player |
745 | + SendPacket s; |
746 | s.reset(); |
747 | s.unsigned_8(NETCMD_SETTING_PLAYER); |
748 | s.unsigned_8(number); |
749 | write_setting_player(s, number); |
750 | broadcast(s); |
751 | + |
752 | + // Let clients know whether their slot has changed |
753 | + s.reset(); |
754 | + s.unsigned_8(NETCMD_SETTING_ALLUSERS); |
755 | + write_setting_all_users(s); |
756 | + broadcast(s); |
757 | } |
758 | |
759 | void GameHost::set_player_tribe(uint8_t const number, |
760 | @@ -1312,7 +1268,7 @@ |
761 | // uses it. |
762 | } |
763 | |
764 | -void GameHost::set_player_shared(uint8_t number, uint8_t shared) { |
765 | +void GameHost::set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared) { |
766 | if (number >= d->settings.players.size()) |
767 | return; |
768 | |
769 | @@ -1322,8 +1278,8 @@ |
770 | return; |
771 | |
772 | PlayerSettings& sharedplr = d->settings.players.at(shared - 1); |
773 | - assert(sharedplr.state != PlayerSettings::stateClosed && |
774 | - sharedplr.state != PlayerSettings::stateShared); |
775 | + assert(PlayerSettings::can_be_shared(sharedplr.state)); |
776 | + assert(d->settings.is_shared_usable(number, shared)); |
777 | |
778 | player.shared_in = shared; |
779 | player.tribe = sharedplr.tribe; |
780 | @@ -1367,8 +1323,8 @@ |
781 | |
782 | void GameHost::switch_to_player(uint32_t user, uint8_t number) { |
783 | if (number < d->settings.players.size() && |
784 | - (d->settings.players.at(number).state != PlayerSettings::stateOpen && |
785 | - d->settings.players.at(number).state != PlayerSettings::stateHuman)) |
786 | + (d->settings.players.at(number).state != PlayerSettings::State::kOpen && |
787 | + d->settings.players.at(number).state != PlayerSettings::State::kHuman)) |
788 | return; |
789 | |
790 | uint32_t old = d->settings.users.at(user).position; |
791 | @@ -1383,14 +1339,14 @@ |
792 | temp2 = temp2.erase(op.name.find(temp), temp.size()); |
793 | set_player_name(old, temp2); |
794 | if (temp2.empty()) |
795 | - set_player_state(old, PlayerSettings::stateOpen); |
796 | + set_player_state(old, PlayerSettings::State::kOpen); |
797 | } |
798 | |
799 | if (number < d->settings.players.size()) { |
800 | // Add clients name to new player slot |
801 | PlayerSettings& op = d->settings.players.at(number); |
802 | - if (op.state == PlayerSettings::stateOpen) { |
803 | - set_player_state(number, PlayerSettings::stateHuman); |
804 | + if (op.state == PlayerSettings::State::kOpen) { |
805 | + set_player_state(number, PlayerSettings::State::kHuman); |
806 | set_player_name(number, " " + name + " "); |
807 | } else |
808 | set_player_name(number, op.name + " " + name + " "); |
809 | @@ -1477,6 +1433,7 @@ |
810 | packet.string(d->settings.mapfilename); |
811 | packet.unsigned_8(d->settings.savegame ? 1 : 0); |
812 | packet.unsigned_8(d->settings.scenario ? 1 : 0); |
813 | + Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kMap)); |
814 | } |
815 | |
816 | void GameHost::write_setting_player(SendPacket& packet, uint8_t const number) { |
817 | @@ -1490,6 +1447,7 @@ |
818 | packet.unsigned_8(player.random_ai ? 1 : 0); |
819 | packet.unsigned_8(player.team); |
820 | packet.unsigned_8(player.shared_in); |
821 | + Notifications::publish(NoteGameSettings(NoteGameSettings::Action::kPlayer, number)); |
822 | } |
823 | |
824 | void GameHost::write_setting_all_players(SendPacket& packet) { |
825 | @@ -1502,6 +1460,8 @@ |
826 | packet.string(d->settings.users.at(number).name); |
827 | packet.signed_32(d->settings.users.at(number).position); |
828 | packet.unsigned_8(d->settings.users.at(number).ready ? 1 : 0); |
829 | + Notifications::publish(NoteGameSettings( |
830 | + NoteGameSettings::Action::kUser, d->settings.users.at(number).position, number)); |
831 | } |
832 | |
833 | void GameHost::write_setting_all_users(SendPacket& packet) { |
834 | @@ -1673,7 +1633,7 @@ |
835 | |
836 | // Check if there is an unoccupied player left and if, assign. |
837 | for (uint8_t i = 0; i < d->settings.players.size(); ++i) |
838 | - if (d->settings.players.at(i).state == PlayerSettings::stateOpen) { |
839 | + if (d->settings.players.at(i).state == PlayerSettings::State::kOpen) { |
840 | switch_to_player(client.usernum, i); |
841 | break; |
842 | } |
843 | @@ -2096,10 +2056,13 @@ |
844 | |
845 | case NETCMD_SETTING_CHANGEINIT: |
846 | if (!d->game) { |
847 | + // TODO(GunChleoc): For some nebulous reason, we don't receive the num that the client is |
848 | + // sending when a player changes slot. So, keeping the access to the client off for now. |
849 | + // Would be nice to have though. |
850 | uint8_t num = r.unsigned_8(); |
851 | if (num != client.playernum) |
852 | throw DisconnectException("NO_ACCESS_TO_PLAYER"); |
853 | - d->npsb.toggle_init(num); |
854 | + set_player_init(num, r.unsigned_8()); |
855 | } |
856 | break; |
857 | |
858 | @@ -2267,7 +2230,7 @@ |
859 | } |
860 | } |
861 | |
862 | - set_player_state(number, PlayerSettings::stateOpen); |
863 | + set_player_state(number, PlayerSettings::State::kOpen); |
864 | if (d->game) |
865 | init_computer_player(number + 1); |
866 | } |
867 | |
868 | === modified file 'src/network/gamehost.h' |
869 | --- src/network/gamehost.h 2017-07-02 21:09:23 +0000 |
870 | +++ src/network/gamehost.h 2017-09-02 13:55:49 +0000 |
871 | @@ -76,7 +76,7 @@ |
872 | void set_player_number(uint8_t number); |
873 | void set_player_team(uint8_t number, Widelands::TeamNumber team); |
874 | void set_player_closeable(uint8_t number, bool closeable); |
875 | - void set_player_shared(uint8_t number, uint8_t shared); |
876 | + void set_player_shared(PlayerSlot number, Widelands::PlayerNumber shared); |
877 | void switch_to_player(uint32_t user, uint8_t number); |
878 | void set_win_condition_script(const std::string& wc); |
879 | |
880 | |
881 | === modified file 'src/network/network_player_settings_backend.cc' |
882 | --- src/network/network_player_settings_backend.cc 2017-02-14 10:18:15 +0000 |
883 | +++ src/network/network_player_settings_backend.cc 2017-09-02 13:55:49 +0000 |
884 | @@ -19,134 +19,69 @@ |
885 | |
886 | #include "network/network_player_settings_backend.h" |
887 | |
888 | -#include "base/i18n.h" |
889 | -#include "base/log.h" |
890 | -#include "base/wexception.h" |
891 | -#include "logic/game_settings.h" |
892 | -#include "logic/map_objects/tribes/tribe_descr.h" |
893 | -#include "logic/player.h" |
894 | - |
895 | -/// Toggle through the types |
896 | -void NetworkPlayerSettingsBackend::toggle_type(uint8_t id) { |
897 | - if (id >= s->settings().players.size()) |
898 | - return; |
899 | - |
900 | - s->next_player_state(id); |
901 | -} |
902 | - |
903 | -void NetworkPlayerSettingsBackend::set_tribe(uint8_t id, const std::string& tribename) { |
904 | +#include "ai/computer_player.h" |
905 | + |
906 | +void NetworkPlayerSettingsBackend::set_player_state(PlayerSlot id, PlayerSettings::State state) { |
907 | + if (id >= s->settings().players.size()) { |
908 | + return; |
909 | + } |
910 | + s->set_player_state(id, state); |
911 | +} |
912 | + |
913 | +void NetworkPlayerSettingsBackend::set_player_ai(PlayerSlot id, |
914 | + const std::string& name, |
915 | + bool random_ai) { |
916 | + if (id >= s->settings().players.size()) { |
917 | + return; |
918 | + } |
919 | + if (random_ai) { |
920 | + const ComputerPlayer::ImplementationVector& impls = ComputerPlayer::get_implementations(); |
921 | + ComputerPlayer::ImplementationVector::const_iterator it = impls.begin(); |
922 | + if (impls.size() > 1) { |
923 | + do { |
924 | + size_t random = (std::rand() % impls.size()); // Choose a random AI |
925 | + it = impls.begin() + random; |
926 | + } while ((*it)->type == ComputerPlayer::Implementation::Type::kEmpty); |
927 | + } |
928 | + s->set_player_ai(id, (*it)->name, random_ai); |
929 | + } else { |
930 | + s->set_player_ai(id, name, random_ai); |
931 | + } |
932 | +} |
933 | + |
934 | +void NetworkPlayerSettingsBackend::set_player_tribe(PlayerSlot id, const std::string& tribename) { |
935 | const GameSettings& settings = s->settings(); |
936 | - |
937 | - if (id >= settings.players.size() || tribename.empty()) |
938 | + if (id >= settings.players.size() || tribename.empty()) { |
939 | return; |
940 | - |
941 | - if (settings.players.at(id).state != PlayerSettings::stateShared) { |
942 | + } |
943 | + if (settings.players.at(id).state != PlayerSettings::State::kShared) { |
944 | s->set_player_tribe(id, tribename, tribename == "random"); |
945 | } |
946 | } |
947 | |
948 | /// Set the shared in player for the given id |
949 | -void NetworkPlayerSettingsBackend::set_shared_in(uint8_t id, uint8_t shared_in) { |
950 | - const GameSettings& settings = s->settings(); |
951 | - if (id > settings.players.size() || shared_in > settings.players.size()) |
952 | - return; |
953 | - if (settings.players.at(id).state == PlayerSettings::stateShared) { |
954 | - s->set_player_shared(id, shared_in); |
955 | - } |
956 | -} |
957 | - |
958 | -/// Toggle through shared in players |
959 | -void NetworkPlayerSettingsBackend::toggle_shared_in(uint8_t id) { |
960 | - const GameSettings& settings = s->settings(); |
961 | - |
962 | - if (id >= settings.players.size() || |
963 | - settings.players.at(id).state != PlayerSettings::stateShared) |
964 | - return; |
965 | - |
966 | - uint8_t sharedplr = settings.players.at(id).shared_in; |
967 | - for (; sharedplr < settings.players.size(); ++sharedplr) { |
968 | - if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed && |
969 | - settings.players.at(sharedplr).state != PlayerSettings::stateShared) |
970 | - break; |
971 | - } |
972 | - if (sharedplr < settings.players.size()) { |
973 | - // We have already found the next player |
974 | - set_shared_in(id, sharedplr + 1); |
975 | - return; |
976 | - } |
977 | - sharedplr = 0; |
978 | - for (; sharedplr < settings.players.at(id).shared_in; ++sharedplr) { |
979 | - if (settings.players.at(sharedplr).state != PlayerSettings::stateClosed && |
980 | - settings.players.at(sharedplr).state != PlayerSettings::stateShared) |
981 | - break; |
982 | - } |
983 | - if (sharedplr < settings.players.at(id).shared_in) { |
984 | - // We have found the next player |
985 | - set_shared_in(id, sharedplr + 1); |
986 | - return; |
987 | - } else { |
988 | - // No fitting player found |
989 | - return toggle_type(id); |
990 | - } |
991 | -} |
992 | - |
993 | -/// Toggle through the initializations |
994 | -void NetworkPlayerSettingsBackend::toggle_init(uint8_t id) { |
995 | - const GameSettings& settings = s->settings(); |
996 | - |
997 | - if (id >= settings.players.size()) |
998 | - return; |
999 | - |
1000 | - const PlayerSettings& player = settings.players[id]; |
1001 | - for (const TribeBasicInfo& temp_tribeinfo : settings.tribes) { |
1002 | - if (temp_tribeinfo.name == player.tribe) { |
1003 | - return s->set_player_init( |
1004 | - id, (player.initialization_index + 1) % temp_tribeinfo.initializations.size()); |
1005 | - } |
1006 | - } |
1007 | - NEVER_HERE(); |
1008 | -} |
1009 | - |
1010 | -/// Toggle through the teams |
1011 | -void NetworkPlayerSettingsBackend::toggle_team(uint8_t id) { |
1012 | - const GameSettings& settings = s->settings(); |
1013 | - |
1014 | - if (id >= settings.players.size()) |
1015 | - return; |
1016 | - |
1017 | - Widelands::TeamNumber currentteam = settings.players.at(id).team; |
1018 | - Widelands::TeamNumber maxteam = settings.players.size() / 2; |
1019 | - Widelands::TeamNumber newteam; |
1020 | - |
1021 | - if (currentteam >= maxteam) |
1022 | - newteam = 0; |
1023 | - else |
1024 | - newteam = currentteam + 1; |
1025 | - |
1026 | - s->set_player_team(id, newteam); |
1027 | -} |
1028 | - |
1029 | -/// Check if all settings for the player are still valid |
1030 | -void NetworkPlayerSettingsBackend::refresh(uint8_t id) { |
1031 | - const GameSettings& settings = s->settings(); |
1032 | - |
1033 | - if (id >= settings.players.size()) |
1034 | - return; |
1035 | - |
1036 | - const PlayerSettings& player = settings.players[id]; |
1037 | - |
1038 | - if (player.state == PlayerSettings::stateShared) { |
1039 | - // ensure that the shared_in player is able to use this starting position |
1040 | - if (player.shared_in > settings.players.size()) |
1041 | - toggle_shared_in(id); |
1042 | - if (settings.players.at(player.shared_in - 1).state == PlayerSettings::stateClosed || |
1043 | - settings.players.at(player.shared_in - 1).state == PlayerSettings::stateShared) |
1044 | - toggle_shared_in(id); |
1045 | - |
1046 | - if (shared_in_tribe[id] != settings.players.at(player.shared_in - 1).tribe) { |
1047 | - s->set_player_tribe(id, settings.players.at(player.shared_in - 1).tribe, |
1048 | - settings.players.at(player.shared_in - 1).random_tribe); |
1049 | - shared_in_tribe[id] = settings.players.at(id).tribe; |
1050 | - } |
1051 | - } |
1052 | +void NetworkPlayerSettingsBackend::set_player_shared(PlayerSlot id, |
1053 | + Widelands::PlayerNumber shared) { |
1054 | + const GameSettings& settings = s->settings(); |
1055 | + if (id >= settings.players.size() || shared > settings.players.size()) |
1056 | + return; |
1057 | + if (settings.players.at(id).state == PlayerSettings::State::kShared) { |
1058 | + s->set_player_shared(id, shared); |
1059 | + } |
1060 | +} |
1061 | + |
1062 | +/// Sets the initialization for the player slot (Headquarters, Fortified Village etc.) |
1063 | +void NetworkPlayerSettingsBackend::set_player_init(PlayerSlot id, uint8_t initialization_index) { |
1064 | + if (id >= s->settings().players.size()) { |
1065 | + return; |
1066 | + } |
1067 | + s->set_player_init(id, initialization_index); |
1068 | +} |
1069 | + |
1070 | +/// Sets the team for the player slot |
1071 | +void NetworkPlayerSettingsBackend::set_player_team(PlayerSlot id, Widelands::TeamNumber team) { |
1072 | + if (id >= s->settings().players.size()) { |
1073 | + return; |
1074 | + } |
1075 | + s->set_player_team(id, team); |
1076 | } |
1077 | |
1078 | === modified file 'src/network/network_player_settings_backend.h' |
1079 | --- src/network/network_player_settings_backend.h 2017-08-16 10:14:29 +0000 |
1080 | +++ src/network/network_player_settings_backend.h 2017-09-02 13:55:49 +0000 |
1081 | @@ -20,33 +20,22 @@ |
1082 | #ifndef WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H |
1083 | #define WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H |
1084 | |
1085 | -#include "graphic/playercolor.h" |
1086 | #include "logic/game_settings.h" |
1087 | +#include "logic/widelands.h" |
1088 | |
1089 | struct NetworkPlayerSettingsBackend { |
1090 | |
1091 | explicit NetworkPlayerSettingsBackend(GameSettingsProvider* const settings) : s(settings) { |
1092 | - for (uint8_t i = 0; i < kMaxPlayers; ++i) |
1093 | - shared_in_tribe[i] = std::string(); |
1094 | - } |
1095 | - |
1096 | - void toggle_type(uint8_t id); |
1097 | - void set_shared_in(uint8_t id, uint8_t shared_in); |
1098 | - void set_tribe(uint8_t id, const std::string& tribename); |
1099 | - void set_block_tribe_selection(bool blocked) { |
1100 | - tribe_selection_blocked = blocked; |
1101 | - } |
1102 | - |
1103 | - void toggle_init(uint8_t id); |
1104 | - void toggle_team(uint8_t id); |
1105 | - void refresh(uint8_t id); |
1106 | + } |
1107 | + |
1108 | + void set_player_state(PlayerSlot id, PlayerSettings::State state); |
1109 | + void set_player_ai(PlayerSlot id, const std::string& name, bool random_ai); |
1110 | + void set_player_shared(PlayerSlot id, Widelands::PlayerNumber shared); |
1111 | + void set_player_tribe(PlayerSlot id, const std::string& tribename); |
1112 | + void set_player_init(PlayerSlot id, uint8_t initialization_index); |
1113 | + void set_player_team(PlayerSlot id, Widelands::TeamNumber team); |
1114 | |
1115 | GameSettingsProvider* const s; |
1116 | - std::string shared_in_tribe[kMaxPlayers]; |
1117 | - bool tribe_selection_blocked = false; |
1118 | - |
1119 | -private: |
1120 | - void toggle_shared_in(uint8_t id); |
1121 | }; |
1122 | |
1123 | #endif // end of include guard: WL_NETWORK_NETWORK_PLAYER_SETTINGS_BACKEND_H |
1124 | |
1125 | === modified file 'src/network/network_protocol.h' |
1126 | --- src/network/network_protocol.h 2017-05-06 11:00:19 +0000 |
1127 | +++ src/network/network_protocol.h 2017-09-02 13:55:49 +0000 |
1128 | @@ -395,9 +395,9 @@ |
1129 | * \li unsigned_8: new shared player |
1130 | * |
1131 | * \note The client must not assume that the host will accept this |
1132 | - * request. Change of team number only becomes effective when/if the host |
1133 | + * request. Change of the initialization only becomes effective when/if the host |
1134 | * replies with a \ref NETCMD_SETTING_PLAYER or |
1135 | - * \ref NETCMD_SETTING_ALLPLAYERS indicating the changed team. |
1136 | + * \ref NETCMD_SETTING_ALLPLAYERS indicating the changed initialization. |
1137 | */ |
1138 | NETCMD_SETTING_CHANGESHARED = 27, |
1139 | |
1140 | @@ -406,6 +406,7 @@ |
1141 | * client wants to change a player's initialisation. |
1142 | * |
1143 | * \li unsigned_8: number of the player |
1144 | + * \li unsigned_8: index of the initialization |
1145 | * |
1146 | * \note The client must not assume that the host will accept this |
1147 | * request. Change of team number only becomes effective when/if the host |
1148 | |
1149 | === modified file 'src/notifications/note_ids.h' |
1150 | --- src/notifications/note_ids.h 2017-08-30 07:45:52 +0000 |
1151 | +++ src/notifications/note_ids.h 2017-09-02 13:55:49 +0000 |
1152 | @@ -39,7 +39,9 @@ |
1153 | Economy, |
1154 | GraphicResolutionChanged, |
1155 | NoteExpeditionCanceled, |
1156 | - Sound |
1157 | + Sound, |
1158 | + Dropdown, |
1159 | + GameSettings |
1160 | }; |
1161 | |
1162 | #endif // end of include guard: WL_NOTIFICATIONS_NOTE_IDS_H |
1163 | |
1164 | === modified file 'src/ui_basic/dropdown.cc' |
1165 | --- src/ui_basic/dropdown.cc 2017-06-02 08:14:40 +0000 |
1166 | +++ src/ui_basic/dropdown.cc 2017-09-02 13:55:49 +0000 |
1167 | @@ -43,6 +43,8 @@ |
1168 | |
1169 | namespace UI { |
1170 | |
1171 | +int BaseDropdown::next_id_ = 0; |
1172 | + |
1173 | BaseDropdown::BaseDropdown(UI::Panel* parent, |
1174 | int32_t x, |
1175 | int32_t y, |
1176 | @@ -56,11 +58,14 @@ |
1177 | : UI::Panel(parent, |
1178 | x, |
1179 | y, |
1180 | - type == DropdownType::kTextual ? w : button_dimension, |
1181 | + type == DropdownType::kPictorial ? button_dimension : w, |
1182 | // Height only to fit the button, so we can use this in Box layout. |
1183 | base_height(button_dimension)), |
1184 | + id_(next_id_++), |
1185 | max_list_height_(h - 2 * get_h()), |
1186 | list_width_(w), |
1187 | + list_offset_x_(0), |
1188 | + list_offset_y_(0), |
1189 | button_dimension_(button_dimension), |
1190 | mouse_tolerance_(50), |
1191 | button_box_(this, 0, 0, UI::Box::Horizontal, w, h), |
1192 | @@ -79,13 +84,23 @@ |
1193 | "dropdown_label", |
1194 | 0, |
1195 | 0, |
1196 | - type == DropdownType::kTextual ? w - button_dimension : button_dimension, |
1197 | + type == DropdownType::kTextual ? |
1198 | + w - button_dimension : |
1199 | + type == DropdownType::kTextualNarrow ? w : button_dimension, |
1200 | get_h(), |
1201 | background, |
1202 | label), |
1203 | label_(label), |
1204 | type_(type), |
1205 | is_enabled_(true) { |
1206 | + |
1207 | + // Close whenever another dropdown is opened |
1208 | + subscriber_ = Notifications::subscribe<NoteDropdown>([this](const NoteDropdown& note) { |
1209 | + if (id_ != note.id) { |
1210 | + close(); |
1211 | + } |
1212 | + }); |
1213 | + |
1214 | assert(max_list_height_ > 0); |
1215 | // Hook into highest parent that we can get so that we can drop down outside the panel. |
1216 | // Positioning breaks down with TabPanels, so we exclude them. |
1217 | @@ -125,26 +140,42 @@ |
1218 | |
1219 | void BaseDropdown::layout() { |
1220 | const int base_h = base_height(button_dimension_); |
1221 | - const int w = type_ == DropdownType::kTextual ? get_w() : button_dimension_; |
1222 | + const int w = type_ == DropdownType::kPictorial ? button_dimension_ : get_w(); |
1223 | button_box_.set_size(w, base_h); |
1224 | display_button_.set_desired_size( |
1225 | type_ == DropdownType::kTextual ? w - button_dimension_ : w, base_h); |
1226 | int new_list_height = |
1227 | std::min(static_cast<int>(list_->size()) * list_->get_lineheight(), max_list_height_); |
1228 | - list_->set_size(type_ == DropdownType::kTextual ? w : list_width_, new_list_height); |
1229 | + list_->set_size(type_ != DropdownType::kPictorial ? w : list_width_, new_list_height); |
1230 | set_desired_size(w, base_h); |
1231 | |
1232 | // Update list position. The list is hooked into the highest parent that we can get so that we |
1233 | // can drop down outside the panel. Positioning breaks down with TabPanels, so we exclude them. |
1234 | UI::Panel* parent = get_parent(); |
1235 | - int new_list_y = get_y() + get_h() + parent->get_y(); |
1236 | + int new_list_y = get_y() + parent->get_y(); |
1237 | int new_list_x = get_x() + parent->get_x(); |
1238 | while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) { |
1239 | parent = parent->get_parent(); |
1240 | new_list_y += parent->get_y(); |
1241 | new_list_x += parent->get_x(); |
1242 | } |
1243 | - list_->set_pos(Vector2i(new_list_x, new_list_y)); |
1244 | + |
1245 | + // Drop up instead of down if it doesn't fit |
1246 | + if (new_list_y + list_->get_h() > g_gr->get_yres()) { |
1247 | + list_offset_y_ = -list_->get_h(); |
1248 | + } else { |
1249 | + list_offset_y_ = display_button_.get_h(); |
1250 | + } |
1251 | + |
1252 | + // Right align instead of left align if it doesn't fit |
1253 | + if (new_list_x + list_->get_w() > g_gr->get_xres()) { |
1254 | + list_offset_x_ = display_button_.get_w() - list_->get_w(); |
1255 | + if (push_button_ != nullptr) { |
1256 | + list_offset_x_ += push_button_->get_w(); |
1257 | + } |
1258 | + } |
1259 | + |
1260 | + list_->set_pos(Vector2i(new_list_x + list_offset_x_, new_list_y + list_offset_y_)); |
1261 | } |
1262 | |
1263 | void BaseDropdown::add(const std::string& name, |
1264 | @@ -178,16 +209,29 @@ |
1265 | |
1266 | void BaseDropdown::set_label(const std::string& text) { |
1267 | label_ = text; |
1268 | - if (type_ == DropdownType::kTextual) { |
1269 | + if (type_ != DropdownType::kPictorial) { |
1270 | display_button_.set_title(label_); |
1271 | } |
1272 | } |
1273 | |
1274 | +void BaseDropdown::set_image(const Image* image) { |
1275 | + display_button_.set_pic(image); |
1276 | +} |
1277 | + |
1278 | void BaseDropdown::set_tooltip(const std::string& text) { |
1279 | tooltip_ = text; |
1280 | display_button_.set_tooltip(tooltip_); |
1281 | } |
1282 | |
1283 | +void BaseDropdown::set_errored(const std::string& error_message) { |
1284 | + set_tooltip((boost::format(_("%1%: %2%")) % _("Error") % error_message).str()); |
1285 | + if (type_ != DropdownType::kPictorial) { |
1286 | + set_label(_("Error")); |
1287 | + } else { |
1288 | + set_image(g_gr->images().get("images/ui_basic/different.png")); |
1289 | + } |
1290 | +} |
1291 | + |
1292 | void BaseDropdown::set_enabled(bool on) { |
1293 | is_enabled_ = on; |
1294 | set_can_focus(on); |
1295 | @@ -213,6 +257,7 @@ |
1296 | } |
1297 | |
1298 | void BaseDropdown::clear() { |
1299 | + close(); |
1300 | list_->clear(); |
1301 | current_selection_ = list_->selection_index(); |
1302 | list_->set_size(list_->get_w(), 0); |
1303 | @@ -239,7 +284,7 @@ |
1304 | /** TRANSLATORS: Selection in Dropdown menus. */ |
1305 | pgettext("dropdown", "Not Selected"); |
1306 | |
1307 | - if (type_ == DropdownType::kTextual) { |
1308 | + if (type_ != DropdownType::kPictorial) { |
1309 | if (label_.empty()) { |
1310 | display_button_.set_title(name); |
1311 | } else { |
1312 | @@ -268,19 +313,29 @@ |
1313 | return; |
1314 | } |
1315 | list_->set_visible(!list_->is_visible()); |
1316 | + if (type_ != DropdownType::kTextual) { |
1317 | + display_button_.set_perm_pressed(list_->is_visible()); |
1318 | + } |
1319 | if (list_->is_visible()) { |
1320 | list_->move_to_top(); |
1321 | focus(); |
1322 | + Notifications::publish(NoteDropdown(id_)); |
1323 | } |
1324 | // Make sure that the list covers and deactivates the elements below it |
1325 | set_layout_toplevel(list_->is_visible()); |
1326 | } |
1327 | |
1328 | +void BaseDropdown::close() { |
1329 | + if (is_expanded()) { |
1330 | + toggle_list(); |
1331 | + } |
1332 | +} |
1333 | + |
1334 | bool BaseDropdown::is_mouse_away() const { |
1335 | - return (get_mouse_position().x + mouse_tolerance_) < 0 || |
1336 | - get_mouse_position().x > (list_->get_w() + mouse_tolerance_) || |
1337 | - (get_mouse_position().y + mouse_tolerance_ / 2) < 0 || |
1338 | - get_mouse_position().y > (get_h() + list_->get_h() + mouse_tolerance_); |
1339 | + return (get_mouse_position().x + mouse_tolerance_) < list_offset_x_ || |
1340 | + get_mouse_position().x > (list_offset_x_ + list_->get_w() + mouse_tolerance_) || |
1341 | + (get_mouse_position().y + mouse_tolerance_) < list_offset_y_ || |
1342 | + get_mouse_position().y > (list_offset_y_ + get_h() + list_->get_h() + mouse_tolerance_); |
1343 | } |
1344 | |
1345 | bool BaseDropdown::handle_key(bool down, SDL_Keysym code) { |
1346 | |
1347 | === modified file 'src/ui_basic/dropdown.h' |
1348 | --- src/ui_basic/dropdown.h 2017-04-21 09:56:42 +0000 |
1349 | +++ src/ui_basic/dropdown.h 2017-09-02 13:55:49 +0000 |
1350 | @@ -27,14 +27,26 @@ |
1351 | |
1352 | #include "graphic/graphic.h" |
1353 | #include "graphic/image.h" |
1354 | +#include "notifications/note_ids.h" |
1355 | +#include "notifications/notifications.h" |
1356 | #include "ui_basic/box.h" |
1357 | #include "ui_basic/button.h" |
1358 | #include "ui_basic/listselect.h" |
1359 | #include "ui_basic/panel.h" |
1360 | |
1361 | namespace UI { |
1362 | - |
1363 | -enum class DropdownType { kTextual, kPictorial }; |
1364 | +// We use this to make sure that only 1 dropdown is open at the same time. |
1365 | +struct NoteDropdown { |
1366 | + CAN_BE_SENT_AS_NOTE(NoteId::Dropdown) |
1367 | + |
1368 | + int id; |
1369 | + |
1370 | + explicit NoteDropdown(int init_id) : id(init_id) { |
1371 | + } |
1372 | +}; |
1373 | + |
1374 | +/// The narrow textual dropdown omits the extra push button |
1375 | +enum class DropdownType { kTextual, kTextualNarrow, kPictorial }; |
1376 | |
1377 | /// Implementation for a dropdown menu that lets the user select a value. |
1378 | class BaseDropdown : public Panel { |
1379 | @@ -63,6 +75,7 @@ |
1380 | ~BaseDropdown(); |
1381 | |
1382 | public: |
1383 | + /// An entry was selected |
1384 | boost::signals2::signal<void()> selected; |
1385 | |
1386 | /// \return true if an element has been selected from the list |
1387 | @@ -72,9 +85,15 @@ |
1388 | /// and displayed on the display button. |
1389 | void set_label(const std::string& text); |
1390 | |
1391 | + /// Sets the image for the display button (for pictorial dropdowns). |
1392 | + void set_image(const Image* image); |
1393 | + |
1394 | /// Sets the tooltip for the display button. |
1395 | void set_tooltip(const std::string& text); |
1396 | |
1397 | + /// Displays an error message on the button instead of the current selection. |
1398 | + void set_errored(const std::string& error_message); |
1399 | + |
1400 | /// Enables/disables the dropdown selection. |
1401 | void set_enabled(bool on); |
1402 | |
1403 | @@ -142,12 +161,22 @@ |
1404 | void set_value(); |
1405 | /// Toggles the dropdown list on and off. |
1406 | void toggle_list(); |
1407 | + /// Toggle the list closed if the dropdown is currently expanded. |
1408 | + void close(); |
1409 | |
1410 | /// Returns true if the mouse pointer left the vicinity of the dropdown. |
1411 | bool is_mouse_away() const; |
1412 | |
1413 | + /// Give each dropdown a unique ID |
1414 | + static int next_id_; |
1415 | + const int id_; |
1416 | + std::unique_ptr<Notifications::Subscriber<NoteDropdown>> subscriber_; |
1417 | + |
1418 | + // Dimensions |
1419 | int max_list_height_; |
1420 | int list_width_; |
1421 | + int list_offset_x_; |
1422 | + int list_offset_y_; |
1423 | int button_dimension_; |
1424 | const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse |
1425 | UI::Box button_box_; |
1426 | |
1427 | === modified file 'src/ui_basic/listselect.cc' |
1428 | --- src/ui_basic/listselect.cc 2017-06-15 16:34:58 +0000 |
1429 | +++ src/ui_basic/listselect.cc 2017-09-02 13:55:49 +0000 |
1430 | @@ -359,9 +359,9 @@ |
1431 | if (selection_mode_ == ListselectLayout::kDropdown) { |
1432 | RGBAColor black(0, 0, 0, 255); |
1433 | // top edge |
1434 | - dst.brighten_rect(Recti(0, 0, get_w(), 2), BUTTON_EDGE_BRIGHT_FACTOR / 4); |
1435 | + dst.brighten_rect(Recti(0, 0, get_w(), 2), BUTTON_EDGE_BRIGHT_FACTOR); |
1436 | // left edge |
1437 | - dst.brighten_rect(Recti(0, 0, 2, get_h()), BUTTON_EDGE_BRIGHT_FACTOR); |
1438 | + dst.brighten_rect(Recti(0, 2, 2, get_h()), BUTTON_EDGE_BRIGHT_FACTOR); |
1439 | // bottom edge |
1440 | dst.fill_rect(Recti(2, get_h() - 2, get_eff_w() - 2, 1), black); |
1441 | dst.fill_rect(Recti(1, get_h() - 1, get_eff_w() - 1, 1), black); |
1442 | |
1443 | === modified file 'src/ui_fsmenu/launch_game.cc' |
1444 | --- src/ui_fsmenu/launch_game.cc 2017-05-18 06:52:04 +0000 |
1445 | +++ src/ui_fsmenu/launch_game.cc 2017-09-02 13:55:49 +0000 |
1446 | @@ -167,7 +167,7 @@ |
1447 | t->get_string("description")); |
1448 | } |
1449 | } catch (LuaTableKeyError& e) { |
1450 | - log("LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(), |
1451 | + log("Launch Game: Error loading win condition: %s %s\n", win_condition_script.c_str(), |
1452 | e.what()); |
1453 | } |
1454 | } |
1455 | @@ -177,9 +177,8 @@ |
1456 | "could not be loaded.")) % |
1457 | settings_->settings().mapfilename) |
1458 | .str(); |
1459 | - win_condition_dropdown_.set_label(_("Error")); |
1460 | - win_condition_dropdown_.set_tooltip(error_message); |
1461 | - log("LaunchSPG: Exception: %s %s\n", error_message.c_str(), e.what()); |
1462 | + win_condition_dropdown_.set_errored(error_message); |
1463 | + log("Launch Game: Exception: %s %s\n", error_message.c_str(), e.what()); |
1464 | } |
1465 | } |
1466 | |
1467 | @@ -202,8 +201,8 @@ |
1468 | } |
1469 | } |
1470 | } catch (LuaTableKeyError& e) { |
1471 | - log( |
1472 | - "LaunchSPG: Error loading win condition: %s %s\n", win_condition_script.c_str(), e.what()); |
1473 | + log("Launch Game: Error loading win condition: %s %s\n", win_condition_script.c_str(), |
1474 | + e.what()); |
1475 | } |
1476 | if (!is_usable) { |
1477 | t.reset(nullptr); |
1478 | |
1479 | === modified file 'src/ui_fsmenu/launch_mpg.cc' |
1480 | --- src/ui_fsmenu/launch_mpg.cc 2017-05-18 06:52:04 +0000 |
1481 | +++ src/ui_fsmenu/launch_mpg.cc 2017-09-02 13:55:49 +0000 |
1482 | @@ -181,7 +181,7 @@ |
1483 | map_info_.set_text(_("The host has not yet selected a map or saved game.")); |
1484 | |
1485 | mpsg_ = new MultiPlayerSetupGroup( |
1486 | - this, get_w() / 50, get_h() / 8, get_w() * 57 / 80, get_h(), settings, butw_, buth_); |
1487 | + this, get_w() / 50, change_map_or_save_.get_y(), get_w() * 57 / 80, get_h(), settings, buth_); |
1488 | |
1489 | // If we are the host, open the map or save selection menu at startup |
1490 | if (settings_->settings().usernum == 0 && settings_->settings().mapname.empty()) { |
1491 | @@ -355,6 +355,8 @@ |
1492 | * buttons and text. |
1493 | */ |
1494 | void FullscreenMenuLaunchMPG::refresh() { |
1495 | + // TODO(GunChleoc): Investigate what we can handle with NoteGameSettings. Maybe we can get rid of |
1496 | + // refresh() and thus think(). |
1497 | const GameSettings& settings = settings_->settings(); |
1498 | |
1499 | if (settings.mapfilename != filename_proof_) { |
1500 | @@ -425,8 +427,6 @@ |
1501 | } |
1502 | win_condition_dropdown_.set_enabled(false); |
1503 | } |
1504 | - // Update the multi player setup group |
1505 | - mpsg_->refresh(); |
1506 | } |
1507 | |
1508 | /** |
1509 | @@ -443,16 +443,21 @@ |
1510 | map.set_filename(settings.mapfilename); |
1511 | ml->preload_map(true); |
1512 | Widelands::PlayerNumber const nrplayers = map.get_nrplayers(); |
1513 | + if (settings.players.size() != nrplayers) { |
1514 | + // Due to asynchronous notifications, the client can crash when an update is missing and the |
1515 | + // number of players is wrong. |
1516 | + return; |
1517 | + } |
1518 | for (uint8_t i = 0; i < nrplayers; ++i) { |
1519 | settings_->set_player_tribe(i, map.get_scenario_player_tribe(i + 1)); |
1520 | settings_->set_player_closeable(i, map.get_scenario_player_closeable(i + 1)); |
1521 | std::string ai(map.get_scenario_player_ai(i + 1)); |
1522 | if (!ai.empty()) { |
1523 | - settings_->set_player_state(i, PlayerSettings::stateComputer); |
1524 | + settings_->set_player_state(i, PlayerSettings::State::kComputer); |
1525 | settings_->set_player_ai(i, ai); |
1526 | - } else if (settings.players.at(i).state != PlayerSettings::stateHuman && |
1527 | - settings.players.at(i).state != PlayerSettings::stateOpen) { |
1528 | - settings_->set_player_state(i, PlayerSettings::stateOpen); |
1529 | + } else if (settings.players.at(i).state != PlayerSettings::State::kHuman && |
1530 | + settings.players.at(i).state != PlayerSettings::State::kOpen) { |
1531 | + settings_->set_player_state(i, PlayerSettings::State::kOpen); |
1532 | } |
1533 | } |
1534 | } |
1535 | @@ -470,22 +475,25 @@ |
1536 | std::string player_save_tribe[kMaxPlayers]; |
1537 | std::string player_save_ai[kMaxPlayers]; |
1538 | |
1539 | - uint8_t i = 1; |
1540 | - for (; i <= nr_players_; ++i) { |
1541 | + for (uint8_t i = 1; i <= nr_players_; ++i) { |
1542 | + Section* s = prof.get_section((boost::format("player_%u") % static_cast<unsigned int>(i)).str()); |
1543 | + if (s == nullptr) { |
1544 | + // Due to asynchronous notifications, the client can crash on savegame change when number |
1545 | + // of players goes down. So, we abort if the section does not exist to prevent crashes. |
1546 | + return; |
1547 | + } |
1548 | + player_save_name[i - 1] = s->get_string("name"); |
1549 | + player_save_tribe[i - 1] = s->get_string("tribe"); |
1550 | + player_save_ai[i - 1] = s->get_string("ai"); |
1551 | + |
1552 | infotext += "\n* "; |
1553 | - Section& s = |
1554 | - prof.get_safe_section((boost::format("player_%u") % static_cast<unsigned int>(i)).str()); |
1555 | - player_save_name[i - 1] = s.get_string("name"); |
1556 | - player_save_tribe[i - 1] = s.get_string("tribe"); |
1557 | - player_save_ai[i - 1] = s.get_string("ai"); |
1558 | - |
1559 | infotext += (boost::format(_("Player %u")) % static_cast<unsigned int>(i)).str(); |
1560 | if (player_save_tribe[i - 1].empty()) { |
1561 | std::string closed_string = (boost::format("<%s>") % _("closed")).str(); |
1562 | infotext += ":\n "; |
1563 | infotext += closed_string; |
1564 | // Close the player |
1565 | - settings_->set_player_state(i - 1, PlayerSettings::stateClosed); |
1566 | + settings_->set_player_state(i - 1, PlayerSettings::State::kClosed); |
1567 | continue; // if tribe is empty, the player does not exist |
1568 | } |
1569 | |
1570 | @@ -495,10 +503,10 @@ |
1571 | |
1572 | if (player_save_ai[i - 1].empty()) { |
1573 | // Assure that player is open |
1574 | - if (settings_->settings().players.at(i - 1).state != PlayerSettings::stateHuman) |
1575 | - settings_->set_player_state(i - 1, PlayerSettings::stateOpen); |
1576 | + if (settings_->settings().players.at(i - 1).state != PlayerSettings::State::kHuman) |
1577 | + settings_->set_player_state(i - 1, PlayerSettings::State::kOpen); |
1578 | } else { |
1579 | - settings_->set_player_state(i - 1, PlayerSettings::stateComputer); |
1580 | + settings_->set_player_state(i - 1, PlayerSettings::State::kComputer); |
1581 | settings_->set_player_ai(i - 1, player_save_ai[i - 1]); |
1582 | } |
1583 | |
1584 | @@ -563,7 +571,9 @@ |
1585 | "\n"; |
1586 | infotext += |
1587 | std::string("• ") + |
1588 | - (boost::format(ngettext("%u Player", "%u Players", nr_players_)) % nr_players_).str() + "\n"; |
1589 | + (boost::format(ngettext("%u Player", "%u Players", nr_players_)) % static_cast<unsigned int>(nr_players_)) |
1590 | + .str() + |
1591 | + "\n"; |
1592 | if (settings_->settings().scenario) |
1593 | infotext += std::string("• ") + (boost::format(_("Scenario mode selected"))).str() + "\n"; |
1594 | infotext += "\n"; |
1595 | |
1596 | === modified file 'src/ui_fsmenu/launch_mpg.h' |
1597 | --- src/ui_fsmenu/launch_mpg.h 2017-02-10 14:12:36 +0000 |
1598 | +++ src/ui_fsmenu/launch_mpg.h 2017-09-02 13:55:49 +0000 |
1599 | @@ -23,6 +23,7 @@ |
1600 | #include <memory> |
1601 | #include <string> |
1602 | |
1603 | +#include "logic/game_settings.h" |
1604 | #include "ui_basic/button.h" |
1605 | #include "ui_basic/dropdown.h" |
1606 | #include "ui_basic/multilinetextarea.h" |
1607 | |
1608 | === modified file 'src/ui_fsmenu/launch_spg.cc' |
1609 | --- src/ui_fsmenu/launch_spg.cc 2017-05-04 05:08:14 +0000 |
1610 | +++ src/ui_fsmenu/launch_spg.cc 2017-09-02 13:55:49 +0000 |
1611 | @@ -210,8 +210,8 @@ |
1612 | for (uint8_t i = 0; i < nr_players_; ++i) { |
1613 | pos_[i]->set_visible(true); |
1614 | const PlayerSettings& player = settings.players[i]; |
1615 | - pos_[i]->set_enabled(!is_scenario_ && (player.state == PlayerSettings::stateOpen || |
1616 | - player.state == PlayerSettings::stateComputer)); |
1617 | + pos_[i]->set_enabled(!is_scenario_ && (player.state == PlayerSettings::State::kOpen || |
1618 | + player.state == PlayerSettings::State::kComputer)); |
1619 | } |
1620 | for (uint32_t i = nr_players_; i < kMaxPlayers; ++i) |
1621 | pos_[i]->set_visible(false); |
1622 | @@ -291,14 +291,14 @@ |
1623 | // Check if a still valid place is open. |
1624 | for (uint8_t i = 0; i < newplayernumber; ++i) { |
1625 | PlayerSettings position = settings.players.at(i); |
1626 | - if (position.state == PlayerSettings::stateOpen) { |
1627 | + if (position.state == PlayerSettings::State::kOpen) { |
1628 | switch_to_position(i); |
1629 | return; |
1630 | } |
1631 | } |
1632 | |
1633 | // Kick player 1 and take the position |
1634 | - settings_->set_player_state(0, PlayerSettings::stateClosed); |
1635 | - settings_->set_player_state(0, PlayerSettings::stateOpen); |
1636 | + settings_->set_player_state(0, PlayerSettings::State::kClosed); |
1637 | + settings_->set_player_state(0, PlayerSettings::State::kOpen); |
1638 | switch_to_position(0); |
1639 | } |
1640 | |
1641 | === modified file 'src/wui/multiplayersetupgroup.cc' |
1642 | --- src/wui/multiplayersetupgroup.cc 2017-05-03 07:24:06 +0000 |
1643 | +++ src/wui/multiplayersetupgroup.cc 2017-09-02 13:55:49 +0000 |
1644 | @@ -19,8 +19,10 @@ |
1645 | |
1646 | #include "wui/multiplayersetupgroup.h" |
1647 | |
1648 | +#include <memory> |
1649 | #include <string> |
1650 | |
1651 | +#include <boost/algorithm/string.hpp> |
1652 | #include <boost/format.hpp> |
1653 | #include <boost/lexical_cast.hpp> |
1654 | |
1655 | @@ -36,419 +38,550 @@ |
1656 | #include "logic/map_objects/tribes/tribe_descr.h" |
1657 | #include "logic/map_objects/tribes/tribes.h" |
1658 | #include "logic/player.h" |
1659 | +#include "logic/widelands.h" |
1660 | #include "ui_basic/button.h" |
1661 | -#include "ui_basic/checkbox.h" |
1662 | #include "ui_basic/dropdown.h" |
1663 | -#include "ui_basic/icon.h" |
1664 | +#include "ui_basic/mouse_constants.h" |
1665 | #include "ui_basic/scrollbar.h" |
1666 | #include "ui_basic/textarea.h" |
1667 | |
1668 | +#define AI_NAME_PREFIX "ai" AI_NAME_SEPARATOR |
1669 | + |
1670 | +constexpr int kPadding = 4; |
1671 | + |
1672 | +/// Holds the info and dropdown menu for a connected client |
1673 | struct MultiPlayerClientGroup : public UI::Box { |
1674 | MultiPlayerClientGroup(UI::Panel* const parent, |
1675 | - uint8_t id, |
1676 | - int32_t const /* x */, |
1677 | - int32_t const /* y */, |
1678 | int32_t const w, |
1679 | int32_t const h, |
1680 | + PlayerSlot id, |
1681 | GameSettingsProvider* const settings) |
1682 | - : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h), |
1683 | - type_icon(nullptr), |
1684 | - type(nullptr), |
1685 | + : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h, kPadding), |
1686 | + slot_dropdown_(this, 0, 0, h, 200, h, _("Role"), UI::DropdownType::kPictorial), |
1687 | + // Name needs to be initialized after the dropdown, otherwise the layout function will |
1688 | + // crash. |
1689 | + name(this, 0, 0, w - h - UI::Scrollbar::kSize * 11 / 5, h), |
1690 | s(settings), |
1691 | id_(id), |
1692 | - save_(-2) { |
1693 | + slot_selection_locked_(false) { |
1694 | set_size(w, h); |
1695 | - name = new UI::Textarea(this, 0, 0, w - h - UI::Scrollbar::kSize * 11 / 5, h); |
1696 | - add(name); |
1697 | - // Either Button if changeable OR text if not |
1698 | - if (id == settings->settings().usernum) { // Our Client |
1699 | - type = new UI::Button( |
1700 | - this, "client_type", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), ""); |
1701 | - type->sigclicked.connect( |
1702 | - boost::bind(&MultiPlayerClientGroup::toggle_type, boost::ref(*this))); |
1703 | - add(type); |
1704 | - } else { // just a shown client |
1705 | - type_icon = new UI::Icon( |
1706 | - this, 0, 0, h, h, g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png")); |
1707 | - add(type_icon); |
1708 | - } |
1709 | - } |
1710 | - |
1711 | - /// Switch human players and spectator |
1712 | - void toggle_type() { |
1713 | - UserSettings us = s->settings().users.at(id_); |
1714 | - int16_t p = us.position; |
1715 | - if (p == UserSettings::none()) |
1716 | - p = -1; |
1717 | - |
1718 | - for (++p; p < static_cast<int16_t>(s->settings().players.size()); ++p) { |
1719 | - if (s->settings().players.at(p).state == PlayerSettings::stateHuman || |
1720 | - s->settings().players.at(p).state == PlayerSettings::stateOpen) { |
1721 | - s->set_player_number(p); |
1722 | - return; |
1723 | - } |
1724 | - } |
1725 | - s->set_player_number(UserSettings::none()); |
1726 | - } |
1727 | - |
1728 | - /// Care about visibility and current values |
1729 | - void refresh() { |
1730 | - UserSettings us = s->settings().users.at(id_); |
1731 | - if (us.position == UserSettings::not_connected()) { |
1732 | - name->set_text((boost::format("<%s>") % _("free")).str()); |
1733 | - if (type) |
1734 | - type->set_visible(false); |
1735 | - else |
1736 | - type_icon->set_visible(false); |
1737 | - } else { |
1738 | - name->set_text(us.name); |
1739 | - if (save_ != us.position) { |
1740 | - const Image* position_image; |
1741 | - std::string temp_tooltip; |
1742 | - if (us.position < UserSettings::highest_playernum()) { |
1743 | - position_image = |
1744 | - playercolor_image(us.position, "images/players/genstats_player.png"); |
1745 | - temp_tooltip = |
1746 | - (boost::format(_("Player %u")) % static_cast<unsigned int>(us.position + 1)) |
1747 | - .str(); |
1748 | - } else { |
1749 | - position_image = g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png"); |
1750 | - temp_tooltip = _("Spectator"); |
1751 | - } |
1752 | - |
1753 | - // Either Button if changeable OR text if not |
1754 | - if (id_ == s->settings().usernum) { |
1755 | - type->set_pic(position_image); |
1756 | - type->set_tooltip(temp_tooltip); |
1757 | - type->set_visible(true); |
1758 | - } else { |
1759 | - type_icon->set_icon(position_image); |
1760 | - type_icon->set_tooltip(temp_tooltip); |
1761 | - type_icon->set_visible(true); |
1762 | - } |
1763 | - save_ = us.position; |
1764 | - } |
1765 | - } |
1766 | - } |
1767 | - |
1768 | - UI::Textarea* name; |
1769 | - UI::Icon* type_icon; |
1770 | - UI::Button* type; |
1771 | + |
1772 | + add(&slot_dropdown_); |
1773 | + add(&name, UI::Box::Resizing::kAlign, UI::Align::kCenter); |
1774 | + |
1775 | + slot_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat); |
1776 | + slot_dropdown_.selected.connect( |
1777 | + boost::bind(&MultiPlayerClientGroup::set_slot, boost::ref(*this))); |
1778 | + |
1779 | + update(); |
1780 | + layout(); |
1781 | + |
1782 | + subscriber_ = |
1783 | + Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings& note) { |
1784 | + switch (note.action) { |
1785 | + case NoteGameSettings::Action::kMap: |
1786 | + /// In case the client gets kicked off its slot due to number of player slots in the |
1787 | + /// map |
1788 | + update(); |
1789 | + break; |
1790 | + case NoteGameSettings::Action::kUser: |
1791 | + /// Player slot might have been closed, bumping the client to observer status. Also, |
1792 | + /// take note if another player changed their position. |
1793 | + if (id_ == note.usernum || note.usernum == UserSettings::none()) { |
1794 | + update(); |
1795 | + } |
1796 | + break; |
1797 | + case NoteGameSettings::Action::kPlayer: |
1798 | + break; |
1799 | + } |
1800 | + }); |
1801 | + } |
1802 | + |
1803 | + /// Update dropdown sizes |
1804 | + void layout() override { |
1805 | + UI::Box::layout(); |
1806 | + slot_dropdown_.set_height(g_gr->get_yres() * 3 / 4); |
1807 | + } |
1808 | + |
1809 | + /// This will update the client's player slot with the value currently selected in the slot |
1810 | + /// dropdown. |
1811 | + void set_slot() { |
1812 | + const GameSettings& settings = s->settings(); |
1813 | + if (id_ != settings.usernum) { |
1814 | + return; |
1815 | + } |
1816 | + slot_selection_locked_ = true; |
1817 | + if (slot_dropdown_.has_selection()) { |
1818 | + const uint8_t new_slot = slot_dropdown_.get_selected(); |
1819 | + if (new_slot != settings.users.at(id_).position) { |
1820 | + s->set_player_number(slot_dropdown_.get_selected()); |
1821 | + } |
1822 | + } |
1823 | + slot_selection_locked_ = false; |
1824 | + } |
1825 | + |
1826 | + /// Rebuild the slot dropdown from the server settings. This will keep the host and client UIs in |
1827 | + /// sync. |
1828 | + void rebuild_slot_dropdown(const GameSettings& settings) { |
1829 | + if (slot_selection_locked_) { |
1830 | + return; |
1831 | + } |
1832 | + const UserSettings& user_setting = settings.users.at(id_); |
1833 | + |
1834 | + slot_dropdown_.clear(); |
1835 | + for (PlayerSlot slot = 0; slot < settings.players.size(); ++slot) { |
1836 | + if (settings.players.at(slot).state == PlayerSettings::State::kHuman || |
1837 | + settings.players.at(slot).state == PlayerSettings::State::kOpen) { |
1838 | + slot_dropdown_.add((boost::format(_("Player %u")) % static_cast<unsigned int>(slot + 1)).str(), |
1839 | + slot, playercolor_image(slot, "images/players/genstats_player.png"), |
1840 | + slot == user_setting.position); |
1841 | + } |
1842 | + } |
1843 | + slot_dropdown_.add(_("Spectator"), UserSettings::none(), |
1844 | + g_gr->images().get("images/wui/fieldaction/menu_tab_watch.png"), |
1845 | + user_setting.position == UserSettings::none()); |
1846 | + slot_dropdown_.set_visible(true); |
1847 | + slot_dropdown_.set_enabled(id_ == settings.usernum); |
1848 | + } |
1849 | + |
1850 | + /// Take care of visibility and current values |
1851 | + void update() { |
1852 | + const GameSettings& settings = s->settings(); |
1853 | + const UserSettings& user_setting = settings.users.at(id_); |
1854 | + |
1855 | + if (user_setting.position == UserSettings::not_connected()) { |
1856 | + set_visible(false); |
1857 | + return; |
1858 | + } |
1859 | + |
1860 | + name.set_text(user_setting.name); |
1861 | + rebuild_slot_dropdown(settings); |
1862 | + } |
1863 | + |
1864 | + UI::Dropdown<uintptr_t> slot_dropdown_; /// Select the player slot. |
1865 | + UI::Textarea name; /// Client nick name |
1866 | GameSettingsProvider* const s; |
1867 | - uint8_t const id_; |
1868 | - int16_t save_; // saved position to check rewrite need. |
1869 | + uint8_t const id_; /// User number |
1870 | + bool slot_selection_locked_; // Ensure that dropdowns will close on selection. |
1871 | + std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_; |
1872 | }; |
1873 | |
1874 | +/// Holds the dropdown menus for a player slot |
1875 | struct MultiPlayerPlayerGroup : public UI::Box { |
1876 | MultiPlayerPlayerGroup(UI::Panel* const parent, |
1877 | - uint8_t id, |
1878 | - int32_t const /* x */, |
1879 | - int32_t const /* y */, |
1880 | int32_t const w, |
1881 | int32_t const h, |
1882 | + PlayerSlot id, |
1883 | GameSettingsProvider* const settings, |
1884 | NetworkPlayerSettingsBackend* const npsb) |
1885 | - : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h), |
1886 | - player(nullptr), |
1887 | - type(nullptr), |
1888 | - init(nullptr), |
1889 | + : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h, kPadding / 2), |
1890 | s(settings), |
1891 | n(npsb), |
1892 | id_(id), |
1893 | + player(this, |
1894 | + "player", |
1895 | + 0, |
1896 | + 0, |
1897 | + h, |
1898 | + h, |
1899 | + g_gr->images().get("images/ui_basic/but1.png"), |
1900 | + playercolor_image(id, "images/players/player_position_menu.png"), |
1901 | + (boost::format(_("Player %u")) % static_cast<unsigned int>(id_ + 1)).str(), |
1902 | + UI::Button::Style::kFlat), |
1903 | + type_dropdown_(this, 0, 0, 50, 200, h, _("Type"), UI::DropdownType::kPictorial), |
1904 | tribes_dropdown_(this, 0, 0, 50, 200, h, _("Tribe"), UI::DropdownType::kPictorial), |
1905 | - last_state_(PlayerSettings::stateClosed), |
1906 | - last_player_amount_(0) { |
1907 | + init_dropdown_( |
1908 | + this, 0, 0, w - 4 * h - 3 * kPadding, 200, h, "", UI::DropdownType::kTextualNarrow), |
1909 | + team_dropdown_(this, 0, 0, h, 200, h, _("Team"), UI::DropdownType::kPictorial), |
1910 | + last_state_(PlayerSettings::State::kClosed), |
1911 | + type_selection_locked_(false), |
1912 | + tribe_selection_locked_(false), |
1913 | + init_selection_locked_(false), |
1914 | + team_selection_locked_(false) { |
1915 | set_size(w, h); |
1916 | - tribes_dropdown_.set_visible(false); |
1917 | - tribes_dropdown_.set_enabled(false); |
1918 | + |
1919 | + player.set_disable_style(UI::ButtonDisableStyle::kFlat); |
1920 | + player.set_enabled(false); |
1921 | + |
1922 | + type_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat); |
1923 | + tribes_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat); |
1924 | + init_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat); |
1925 | + team_dropdown_.set_disable_style(UI::ButtonDisableStyle::kFlat); |
1926 | + |
1927 | + type_dropdown_.selected.connect( |
1928 | + boost::bind(&MultiPlayerPlayerGroup::set_type, boost::ref(*this))); |
1929 | tribes_dropdown_.selected.connect( |
1930 | boost::bind(&MultiPlayerPlayerGroup::set_tribe_or_shared_in, boost::ref(*this))); |
1931 | + init_dropdown_.selected.connect( |
1932 | + boost::bind(&MultiPlayerPlayerGroup::set_init, boost::ref(*this))); |
1933 | + team_dropdown_.selected.connect( |
1934 | + boost::bind(&MultiPlayerPlayerGroup::set_team, boost::ref(*this))); |
1935 | |
1936 | - const Image* player_image = playercolor_image(id, "images/players/player_position_menu.png"); |
1937 | - assert(player_image); |
1938 | - player = new UI::Icon(this, 0, 0, h, h, player_image); |
1939 | - add(player); |
1940 | - type = new UI::Button( |
1941 | - this, "player_type", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), ""); |
1942 | - type->sigclicked.connect( |
1943 | - boost::bind(&MultiPlayerPlayerGroup::toggle_type, boost::ref(*this))); |
1944 | - add(type); |
1945 | + add_space(0); |
1946 | + add(&player); |
1947 | + add(&type_dropdown_); |
1948 | add(&tribes_dropdown_); |
1949 | - init = new UI::Button(this, "player_init", 0, 0, w - 4 * h, h, |
1950 | - g_gr->images().get("images/ui_basic/but1.png"), ""); |
1951 | - init->sigclicked.connect( |
1952 | - boost::bind(&MultiPlayerPlayerGroup::toggle_init, boost::ref(*this))); |
1953 | - add(init); |
1954 | - team = new UI::Button( |
1955 | - this, "player_team", 0, 0, h, h, g_gr->images().get("images/ui_basic/but1.png"), ""); |
1956 | - team->sigclicked.connect( |
1957 | - boost::bind(&MultiPlayerPlayerGroup::toggle_team, boost::ref(*this))); |
1958 | - add(team); |
1959 | - } |
1960 | - |
1961 | - /// Toggle through the types |
1962 | - void toggle_type() { |
1963 | - n->toggle_type(id_); |
1964 | + add(&init_dropdown_); |
1965 | + add(&team_dropdown_); |
1966 | + add_space(0); |
1967 | + |
1968 | + subscriber_ = |
1969 | + Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings& note) { |
1970 | + switch (note.action) { |
1971 | + case NoteGameSettings::Action::kMap: |
1972 | + // We don't care about map updates, since we receive enough notifications for the |
1973 | + // slots. |
1974 | + break; |
1975 | + default: |
1976 | + if (s->settings().players.empty()) { |
1977 | + // No map/savegame yet |
1978 | + return; |
1979 | + } |
1980 | + if (id_ == note.position || |
1981 | + s->settings().players[id_].state == PlayerSettings::State::kShared) { |
1982 | + update(); |
1983 | + } |
1984 | + } |
1985 | + }); |
1986 | + |
1987 | + // Init dropdowns |
1988 | + update(); |
1989 | + layout(); |
1990 | + } |
1991 | + |
1992 | + /// Update dropdown sizes |
1993 | + void layout() override { |
1994 | + const int max_height = g_gr->get_yres() * 3 / 4; |
1995 | + type_dropdown_.set_height(max_height); |
1996 | + tribes_dropdown_.set_height(max_height); |
1997 | + init_dropdown_.set_height(max_height); |
1998 | + team_dropdown_.set_height(max_height); |
1999 | + UI::Box::layout(); |
2000 | + } |
2001 | + |
2002 | + /// This will update the game settings for the type with the value |
2003 | + /// currently selected in the type dropdown. |
2004 | + void set_type() { |
2005 | + if (!s->can_change_player_state(id_)) { |
2006 | + return; |
2007 | + } |
2008 | + type_selection_locked_ = true; |
2009 | + if (type_dropdown_.has_selection()) { |
2010 | + const std::string& selected = type_dropdown_.get_selected(); |
2011 | + PlayerSettings::State state = PlayerSettings::State::kComputer; |
2012 | + if (selected == "closed") { |
2013 | + state = PlayerSettings::State::kClosed; |
2014 | + } else if (selected == "open") { |
2015 | + state = PlayerSettings::State::kOpen; |
2016 | + } else if (selected == "shared_in") { |
2017 | + state = PlayerSettings::State::kShared; |
2018 | + } else { |
2019 | + if (selected == AI_NAME_PREFIX "random") { |
2020 | + n->set_player_ai(id_, "", true); |
2021 | + } else { |
2022 | + if (boost::starts_with(selected, AI_NAME_PREFIX)) { |
2023 | + std::vector<std::string> parts; |
2024 | + boost::split(parts, selected, boost::is_any_of(AI_NAME_SEPARATOR)); |
2025 | + assert(parts.size() == 2); |
2026 | + n->set_player_ai(id_, parts[1], false); |
2027 | + } else { |
2028 | + throw wexception("Unknown player state: %s\n", selected.c_str()); |
2029 | + } |
2030 | + } |
2031 | + } |
2032 | + n->set_player_state(id_, state); |
2033 | + } |
2034 | + type_selection_locked_ = false; |
2035 | + } |
2036 | + |
2037 | + /// Rebuild the type dropdown from the server settings. This will keep the host and client UIs in |
2038 | + /// sync. |
2039 | + void rebuild_type_dropdown(const GameSettings& settings) { |
2040 | + if (type_selection_locked_) { |
2041 | + return; |
2042 | + } |
2043 | + type_dropdown_.clear(); |
2044 | + // AIs |
2045 | + for (const auto* impl : ComputerPlayer::get_implementations()) { |
2046 | + type_dropdown_.add(_(impl->descname), |
2047 | + (boost::format(AI_NAME_PREFIX "%s") % impl->name).str(), |
2048 | + g_gr->images().get(impl->icon_filename), false, _(impl->descname)); |
2049 | + } |
2050 | + /** TRANSLATORS: This is the name of an AI used in the game setup screens */ |
2051 | + type_dropdown_.add(_("Random AI"), AI_NAME_PREFIX "random", |
2052 | + g_gr->images().get("images/ai/ai_random.png"), false, _("Random AI")); |
2053 | + |
2054 | + // Slot state. Only add shared_in if there are viable slots |
2055 | + if (settings.is_shared_usable(id_, settings.find_shared(id_))) { |
2056 | + type_dropdown_.add(_("Shared in"), "shared_in", |
2057 | + g_gr->images().get("images/ui_fsmenu/shared_in.png"), false, |
2058 | + _("Shared in")); |
2059 | + } |
2060 | + |
2061 | + // Do not close a player in savegames or scenarios |
2062 | + if (!settings.uncloseable(id_)) { |
2063 | + type_dropdown_.add(_("Closed"), "closed", g_gr->images().get("images/ui_basic/stop.png"), |
2064 | + false, _("Closed")); |
2065 | + } |
2066 | + |
2067 | + type_dropdown_.add( |
2068 | + _("Open"), "open", g_gr->images().get("images/ui_basic/continue.png"), false, _("Open")); |
2069 | + |
2070 | + type_dropdown_.set_enabled(s->can_change_player_state(id_)); |
2071 | + |
2072 | + // Now select the entry according to server settings |
2073 | + const PlayerSettings& player_setting = settings.players[id_]; |
2074 | + if (player_setting.state == PlayerSettings::State::kHuman) { |
2075 | + type_dropdown_.set_image(g_gr->images().get("images/wui/stats/genstats_nrworkers.png")); |
2076 | + type_dropdown_.set_tooltip((boost::format(_("%1%: %2%")) % _("Type") % _("Human")).str()); |
2077 | + } else if (player_setting.state == PlayerSettings::State::kClosed) { |
2078 | + type_dropdown_.select("closed"); |
2079 | + } else if (player_setting.state == PlayerSettings::State::kOpen) { |
2080 | + type_dropdown_.select("open"); |
2081 | + } else if (player_setting.state == PlayerSettings::State::kShared) { |
2082 | + type_dropdown_.select("shared_in"); |
2083 | + } else { |
2084 | + if (player_setting.state == PlayerSettings::State::kComputer) { |
2085 | + if (player_setting.ai.empty()) { |
2086 | + type_dropdown_.set_errored(_("No AI")); |
2087 | + } else { |
2088 | + if (player_setting.random_ai) { |
2089 | + type_dropdown_.select(AI_NAME_PREFIX "random"); |
2090 | + } else { |
2091 | + const ComputerPlayer::Implementation* impl = |
2092 | + ComputerPlayer::get_implementation(player_setting.ai); |
2093 | + type_dropdown_.select((boost::format(AI_NAME_PREFIX "%s") % impl->name).str()); |
2094 | + } |
2095 | + } |
2096 | + } |
2097 | + } |
2098 | + } |
2099 | + |
2100 | + /// Whether the client who is running the UI is allowed to change the tribe for this player slot. |
2101 | + bool has_tribe_access() { |
2102 | + return s->settings().players[id_].state == PlayerSettings::State::kShared ? |
2103 | + s->can_change_player_init(id_) : |
2104 | + s->can_change_player_tribe(id_); |
2105 | } |
2106 | |
2107 | /// This will update the game settings for the tribe or shared_in with the value |
2108 | /// currently selected in the tribes dropdown. |
2109 | void set_tribe_or_shared_in() { |
2110 | - n->set_block_tribe_selection(true); |
2111 | - tribes_dropdown_.set_disable_style(s->settings().players[id_].state == |
2112 | - PlayerSettings::stateShared ? |
2113 | + if (!has_tribe_access()) { |
2114 | + return; |
2115 | + } |
2116 | + const PlayerSettings& player_settings = s->settings().players[id_]; |
2117 | + tribe_selection_locked_ = true; |
2118 | + tribes_dropdown_.set_disable_style(player_settings.state == PlayerSettings::State::kShared ? |
2119 | UI::ButtonDisableStyle::kPermpressed : |
2120 | - UI::ButtonDisableStyle::kMonochrome); |
2121 | + UI::ButtonDisableStyle::kFlat); |
2122 | if (tribes_dropdown_.has_selection()) { |
2123 | - if (s->settings().players[id_].state == PlayerSettings::stateShared) { |
2124 | - n->set_shared_in( |
2125 | + if (player_settings.state == PlayerSettings::State::kShared) { |
2126 | + n->set_player_shared( |
2127 | id_, boost::lexical_cast<unsigned int>(tribes_dropdown_.get_selected())); |
2128 | } else { |
2129 | - n->set_tribe(id_, tribes_dropdown_.get_selected()); |
2130 | - } |
2131 | - } |
2132 | - n->set_block_tribe_selection(false); |
2133 | - } |
2134 | - |
2135 | - /// Toggle through the initializations |
2136 | - void toggle_init() { |
2137 | - n->toggle_init(id_); |
2138 | - } |
2139 | - |
2140 | - /// Toggle through the teams |
2141 | - void toggle_team() { |
2142 | - n->toggle_team(id_); |
2143 | - } |
2144 | - |
2145 | - /// Helper function to cast shared_in for use in the dropdown. |
2146 | - const std::string shared_in_as_string(uint8_t shared_in) { |
2147 | - return boost::lexical_cast<std::string>(static_cast<unsigned int>(shared_in)); |
2148 | - } |
2149 | - |
2150 | - /// Update the tribes dropdown from the server settings if the server setting changed. |
2151 | - /// This will keep the host and client UIs in sync. |
2152 | - void update_tribes_dropdown(const PlayerSettings& player_setting) { |
2153 | - if (player_setting.state == PlayerSettings::stateClosed || |
2154 | - player_setting.state == PlayerSettings::stateOpen) { |
2155 | + n->set_player_tribe(id_, tribes_dropdown_.get_selected()); |
2156 | + } |
2157 | + } |
2158 | + tribe_selection_locked_ = false; |
2159 | + } |
2160 | + |
2161 | + /// Rebuild the tribes dropdown from the server settings. This will keep the host and client UIs |
2162 | + /// in sync. |
2163 | + void rebuild_tribes_dropdown(const GameSettings& settings) { |
2164 | + if (tribe_selection_locked_) { |
2165 | + return; |
2166 | + } |
2167 | + const PlayerSettings& player_setting = settings.players[id_]; |
2168 | + tribes_dropdown_.clear(); |
2169 | + if (player_setting.state == PlayerSettings::State::kShared) { |
2170 | + for (size_t i = 0; i < settings.players.size(); ++i) { |
2171 | + if (i != id_) { |
2172 | + // Do not add players that are also shared_in or closed. |
2173 | + const PlayerSettings& other_setting = settings.players[i]; |
2174 | + if (!PlayerSettings::can_be_shared(other_setting.state)) { |
2175 | + continue; |
2176 | + } |
2177 | + |
2178 | + const Image* player_image = |
2179 | + playercolor_image(i, "images/players/player_position_menu.png"); |
2180 | + assert(player_image); |
2181 | + const std::string player_name = |
2182 | + /** TRANSLATORS: This is an option in multiplayer setup for sharing |
2183 | + another player's starting position. */ |
2184 | + (boost::format(_("Shared in Player %u")) % static_cast<unsigned int>(i + 1)).str(); |
2185 | + tribes_dropdown_.add(player_name, |
2186 | + boost::lexical_cast<std::string>(static_cast<unsigned int>(i + 1)), |
2187 | + player_image, (i + 1) == player_setting.shared_in, player_name); |
2188 | + } |
2189 | + } |
2190 | + tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1); |
2191 | + } else { |
2192 | + { |
2193 | + i18n::Textdomain td("tribes"); |
2194 | + for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) { |
2195 | + tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name, |
2196 | + g_gr->images().get(tribeinfo.icon), false, tribeinfo.tooltip); |
2197 | + } |
2198 | + } |
2199 | + tribes_dropdown_.add(pgettext("tribe", "Random"), "random", |
2200 | + g_gr->images().get("images/ui_fsmenu/random.png"), false, |
2201 | + _("The tribe will be selected at random")); |
2202 | + if (player_setting.random_tribe) { |
2203 | + tribes_dropdown_.select("random"); |
2204 | + } else { |
2205 | + tribes_dropdown_.select(player_setting.tribe); |
2206 | + } |
2207 | + } |
2208 | + const bool has_access = has_tribe_access(); |
2209 | + if (tribes_dropdown_.is_enabled() != has_access) { |
2210 | + tribes_dropdown_.set_enabled(has_access && tribes_dropdown_.size() > 1); |
2211 | + } |
2212 | + if (player_setting.state == PlayerSettings::State::kClosed || |
2213 | + player_setting.state == PlayerSettings::State::kOpen) { |
2214 | return; |
2215 | } |
2216 | if (!tribes_dropdown_.is_visible()) { |
2217 | tribes_dropdown_.set_visible(true); |
2218 | } |
2219 | - if (!tribes_dropdown_.is_expanded() && !n->tribe_selection_blocked && |
2220 | - tribes_dropdown_.has_selection()) { |
2221 | - const std::string selected_tribe = tribes_dropdown_.get_selected(); |
2222 | - if (player_setting.state == PlayerSettings::stateShared) { |
2223 | - const std::string shared_in = shared_in_as_string(player_setting.shared_in); |
2224 | - if (shared_in != selected_tribe) { |
2225 | - tribes_dropdown_.select(shared_in); |
2226 | - } |
2227 | - } else { |
2228 | - if (player_setting.random_tribe) { |
2229 | - if (selected_tribe != "random") { |
2230 | - tribes_dropdown_.select("random"); |
2231 | - } |
2232 | - } else if (selected_tribe != player_setting.tribe) { |
2233 | - tribes_dropdown_.select(player_setting.tribe); |
2234 | - } |
2235 | - } |
2236 | - } |
2237 | - } |
2238 | - |
2239 | - /// If the map was changed or the selection mode changed between shared_in and tribe, rebuild the |
2240 | - /// dropdown. |
2241 | - void rebuild_tribes_dropdown(const GameSettings& settings) { |
2242 | - const PlayerSettings& player_setting = settings.players[id_]; |
2243 | - |
2244 | - if (player_setting.state == PlayerSettings::stateClosed || |
2245 | - player_setting.state == PlayerSettings::stateOpen) { |
2246 | - return; |
2247 | - } |
2248 | - |
2249 | - if (tribes_dropdown_.empty() || last_player_amount_ != settings.players.size() || |
2250 | - ((player_setting.state == PlayerSettings::stateShared || |
2251 | - last_state_ == PlayerSettings::stateShared) && |
2252 | - player_setting.state != last_state_)) { |
2253 | - tribes_dropdown_.clear(); |
2254 | - |
2255 | - // We need to see the playercolor if setting shared_in is disabled |
2256 | - tribes_dropdown_.set_disable_style(player_setting.state == PlayerSettings::stateShared ? |
2257 | - UI::ButtonDisableStyle::kPermpressed : |
2258 | - UI::ButtonDisableStyle::kMonochrome); |
2259 | - |
2260 | - if (player_setting.state == PlayerSettings::stateShared) { |
2261 | - for (size_t i = 0; i < settings.players.size(); ++i) { |
2262 | - if (i != id_) { |
2263 | - // TODO(GunChleoc): Do not add players that are also shared_in. |
2264 | - const Image* player_image = |
2265 | - playercolor_image(i, "images/players/player_position_menu.png"); |
2266 | - assert(player_image); |
2267 | - const std::string player_name = |
2268 | - /** TRANSLATORS: This is an option in multiplayer setup for sharing |
2269 | - another player's starting position. */ |
2270 | - (boost::format(_("Shared in Player %u")) % static_cast<unsigned int>(i + 1)) |
2271 | - .str(); |
2272 | - tribes_dropdown_.add( |
2273 | - player_name, shared_in_as_string(i + 1), player_image, false, player_name); |
2274 | - } |
2275 | - } |
2276 | - int shared_in = 0; |
2277 | - while (shared_in == id_) { |
2278 | - ++shared_in; |
2279 | - } |
2280 | - tribes_dropdown_.select(shared_in_as_string(shared_in + 1)); |
2281 | - tribes_dropdown_.set_enabled(tribes_dropdown_.size() > 1); |
2282 | - } else { |
2283 | - { |
2284 | - i18n::Textdomain td("tribes"); |
2285 | - for (const TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) { |
2286 | - tribes_dropdown_.add(_(tribeinfo.descname), tribeinfo.name, |
2287 | - g_gr->images().get(tribeinfo.icon), false, |
2288 | - tribeinfo.tooltip); |
2289 | - } |
2290 | - } |
2291 | - tribes_dropdown_.add(pgettext("tribe", "Random"), "random", |
2292 | - g_gr->images().get("images/ui_fsmenu/random.png"), false, |
2293 | - _("The tribe will be selected at random")); |
2294 | - if (player_setting.random_tribe) { |
2295 | - tribes_dropdown_.select("random"); |
2296 | - } else { |
2297 | - tribes_dropdown_.select(player_setting.tribe); |
2298 | - } |
2299 | - } |
2300 | - } |
2301 | - last_player_amount_ = settings.players.size(); |
2302 | + } |
2303 | + |
2304 | + /// This will update the game settings for the initialization with the value |
2305 | + /// currently selected in the initialization dropdown. |
2306 | + void set_init() { |
2307 | + if (!s->can_change_player_init(id_)) { |
2308 | + return; |
2309 | + } |
2310 | + init_selection_locked_ = true; |
2311 | + if (init_dropdown_.has_selection()) { |
2312 | + n->set_player_init(id_, init_dropdown_.get_selected()); |
2313 | + } |
2314 | + init_selection_locked_ = false; |
2315 | + } |
2316 | + |
2317 | + /// Rebuild the init dropdown from the server settings. This will keep the host and client UIs in |
2318 | + /// sync. |
2319 | + void rebuild_init_dropdown(const GameSettings& settings) { |
2320 | + if (init_selection_locked_) { |
2321 | + return; |
2322 | + } |
2323 | + |
2324 | + init_dropdown_.clear(); |
2325 | + const PlayerSettings& player_setting = settings.players[id_]; |
2326 | + if (settings.scenario) { |
2327 | + init_dropdown_.set_label(_("Scenario")); |
2328 | + } else if (settings.savegame) { |
2329 | + /** Translators: This is a game type */ |
2330 | + init_dropdown_.set_label(_("Saved Game")); |
2331 | + } else { |
2332 | + init_dropdown_.set_label(""); |
2333 | + i18n::Textdomain td("tribes"); // for translated initialisation |
2334 | + const TribeBasicInfo tribeinfo = Widelands::get_tribeinfo(player_setting.tribe); |
2335 | + for (size_t i = 0; i < tribeinfo.initializations.size(); ++i) { |
2336 | + const TribeBasicInfo::Initialization& addme = tribeinfo.initializations[i]; |
2337 | + init_dropdown_.add(_(addme.descname), i, nullptr, |
2338 | + i == player_setting.initialization_index, _(addme.tooltip)); |
2339 | + } |
2340 | + } |
2341 | + |
2342 | + init_dropdown_.set_visible(true); |
2343 | + init_dropdown_.set_enabled(s->can_change_player_init(id_)); |
2344 | + } |
2345 | + |
2346 | + /// This will update the team settings with the value currently selected in the teams dropdown. |
2347 | + void set_team() { |
2348 | + team_selection_locked_ = true; |
2349 | + if (team_dropdown_.has_selection()) { |
2350 | + n->set_player_team(id_, team_dropdown_.get_selected()); |
2351 | + } |
2352 | + team_selection_locked_ = false; |
2353 | + } |
2354 | + |
2355 | + /// Rebuild the team dropdown from the server settings. This will keep the host and client UIs in |
2356 | + /// sync. |
2357 | + void rebuild_team_dropdown(const GameSettings& settings) { |
2358 | + if (team_selection_locked_) { |
2359 | + return; |
2360 | + } |
2361 | + const PlayerSettings& player_setting = settings.players[id_]; |
2362 | + if (player_setting.state == PlayerSettings::State::kShared) { |
2363 | + team_dropdown_.set_visible(false); |
2364 | + team_dropdown_.set_enabled(false); |
2365 | + return; |
2366 | + } |
2367 | + |
2368 | + team_dropdown_.clear(); |
2369 | + team_dropdown_.add(_("No Team"), 0, g_gr->images().get("images/players/no_team.png")); |
2370 | +#ifndef NDEBUG |
2371 | + const size_t no_of_team_colors = sizeof(kTeamColors) / sizeof(kTeamColors[0]); |
2372 | +#endif |
2373 | + for (Widelands::TeamNumber t = 1; t <= settings.players.size() / 2; ++t) { |
2374 | + assert(t < no_of_team_colors); |
2375 | + team_dropdown_.add((boost::format(_("Team %d")) % static_cast<unsigned int>(t)).str(), t, |
2376 | + playercolor_image(kTeamColors[t], "images/players/team.png")); |
2377 | + } |
2378 | + team_dropdown_.select(player_setting.team); |
2379 | + team_dropdown_.set_visible(true); |
2380 | + team_dropdown_.set_enabled(s->can_change_player_team(id_)); |
2381 | } |
2382 | |
2383 | /// Refresh all user interfaces |
2384 | - void refresh() { |
2385 | + void update() { |
2386 | const GameSettings& settings = s->settings(); |
2387 | - |
2388 | if (id_ >= settings.players.size()) { |
2389 | set_visible(false); |
2390 | return; |
2391 | } |
2392 | |
2393 | - n->refresh(id_); |
2394 | - |
2395 | + const PlayerSettings& player_setting = settings.players[id_]; |
2396 | + rebuild_type_dropdown(settings); |
2397 | set_visible(true); |
2398 | |
2399 | - const PlayerSettings& player_setting = settings.players[id_]; |
2400 | - bool typeaccess = s->can_change_player_state(id_); |
2401 | - bool tribeaccess = s->can_change_player_tribe(id_); |
2402 | - bool const initaccess = s->can_change_player_init(id_); |
2403 | - bool teamaccess = s->can_change_player_team(id_); |
2404 | - type->set_enabled(typeaccess); |
2405 | - |
2406 | - rebuild_tribes_dropdown(settings); |
2407 | - |
2408 | - if (player_setting.state == PlayerSettings::stateClosed) { |
2409 | - type->set_tooltip(_("Closed")); |
2410 | - type->set_pic(g_gr->images().get("images/ui_basic/stop.png")); |
2411 | - team->set_visible(false); |
2412 | - team->set_enabled(false); |
2413 | - tribes_dropdown_.set_visible(false); |
2414 | - tribes_dropdown_.set_enabled(false); |
2415 | - init->set_visible(false); |
2416 | - init->set_enabled(false); |
2417 | - return; |
2418 | - } else if (player_setting.state == PlayerSettings::stateOpen) { |
2419 | - type->set_tooltip(_("Open")); |
2420 | - type->set_pic(g_gr->images().get("images/ui_basic/continue.png")); |
2421 | - team->set_visible(false); |
2422 | - team->set_enabled(false); |
2423 | - tribes_dropdown_.set_visible(false); |
2424 | - tribes_dropdown_.set_enabled(false); |
2425 | - init->set_visible(false); |
2426 | - init->set_enabled(false); |
2427 | - return; |
2428 | - } else if (player_setting.state == PlayerSettings::stateShared) { |
2429 | - type->set_tooltip(_("Shared in")); |
2430 | - type->set_pic(g_gr->images().get("images/ui_fsmenu/shared_in.png")); |
2431 | - |
2432 | - update_tribes_dropdown(player_setting); |
2433 | - |
2434 | - if (tribes_dropdown_.is_enabled() != initaccess) { |
2435 | - tribes_dropdown_.set_enabled(initaccess && !n->tribe_selection_blocked && |
2436 | - tribes_dropdown_.size() > 1); |
2437 | - } |
2438 | - |
2439 | - team->set_visible(false); |
2440 | - team->set_enabled(false); |
2441 | - |
2442 | + if (player_setting.state == PlayerSettings::State::kClosed || |
2443 | + player_setting.state == PlayerSettings::State::kOpen) { |
2444 | + team_dropdown_.set_visible(false); |
2445 | + team_dropdown_.set_enabled(false); |
2446 | + tribes_dropdown_.set_visible(false); |
2447 | + tribes_dropdown_.set_enabled(false); |
2448 | + init_dropdown_.set_visible(false); |
2449 | + init_dropdown_.set_enabled(false); |
2450 | } else { |
2451 | - std::string title; |
2452 | - std::string pic = "images/"; |
2453 | - if (player_setting.state == PlayerSettings::stateComputer) { |
2454 | - if (player_setting.ai.empty()) { |
2455 | - title = _("Computer"); |
2456 | - pic += "novalue.png"; |
2457 | - } else { |
2458 | - if (player_setting.random_ai) { |
2459 | - /** TRANSLATORS: This is the name of an AI used in the game setup screens */ |
2460 | - title = _("Random AI"); |
2461 | - pic += "ai/ai_random.png"; |
2462 | - } else { |
2463 | - const ComputerPlayer::Implementation* impl = |
2464 | - ComputerPlayer::get_implementation(player_setting.ai); |
2465 | - title = _(impl->descname); |
2466 | - pic = impl->icon_filename; |
2467 | - } |
2468 | - } |
2469 | - } else { // PlayerSettings::stateHuman |
2470 | - title = _("Human"); |
2471 | - pic += "wui/stats/genstats_nrworkers.png"; |
2472 | - } |
2473 | - type->set_tooltip(title.c_str()); |
2474 | - type->set_pic(g_gr->images().get(pic)); |
2475 | - |
2476 | - update_tribes_dropdown(player_setting); |
2477 | - |
2478 | - if (tribes_dropdown_.is_enabled() != tribeaccess) { |
2479 | - tribes_dropdown_.set_enabled(tribeaccess && !n->tribe_selection_blocked); |
2480 | - } |
2481 | - |
2482 | - if (player_setting.team) { |
2483 | - team->set_title(std::to_string(static_cast<unsigned int>(player_setting.team))); |
2484 | - } else { |
2485 | - team->set_title("--"); |
2486 | - } |
2487 | - team->set_visible(true); |
2488 | - team->set_enabled(teamaccess); |
2489 | - } |
2490 | - init->set_enabled(initaccess); |
2491 | - init->set_visible(true); |
2492 | - |
2493 | - if (settings.scenario) |
2494 | - init->set_title(_("Scenario")); |
2495 | - else if (settings.savegame) |
2496 | - /** Translators: This is a game type */ |
2497 | - init->set_title(_("Saved Game")); |
2498 | - else { |
2499 | - i18n::Textdomain td("tribes"); // for translated initialisation |
2500 | - for (const TribeBasicInfo& tribeinfo : settings.tribes) { |
2501 | - if (tribeinfo.name == player_setting.tribe) { |
2502 | - init->set_title( |
2503 | - _(tribeinfo.initializations.at(player_setting.initialization_index).descname)); |
2504 | - init->set_tooltip( |
2505 | - _(tribeinfo.initializations.at(player_setting.initialization_index).tooltip)); |
2506 | - break; |
2507 | - } |
2508 | - } |
2509 | - } |
2510 | - last_state_ = player_setting.state; |
2511 | + rebuild_tribes_dropdown(settings); |
2512 | + rebuild_init_dropdown(settings); |
2513 | + rebuild_team_dropdown(settings); |
2514 | + } |
2515 | + |
2516 | + // Trigger update for the other players for shared_in mode when slots open and close |
2517 | + if (last_state_ != player_setting.state) { |
2518 | + last_state_ = player_setting.state; |
2519 | + for (PlayerSlot slot = 0; slot < s->settings().players.size(); ++slot) { |
2520 | + if (slot != id_) { |
2521 | + n->set_player_state(slot, settings.players[slot].state); |
2522 | + } |
2523 | + } |
2524 | + } |
2525 | } |
2526 | |
2527 | - UI::Icon* player; |
2528 | - UI::Button* type; |
2529 | - UI::Button* init; |
2530 | - UI::Button* team; |
2531 | GameSettingsProvider* const s; |
2532 | NetworkPlayerSettingsBackend* const n; |
2533 | - uint8_t const id_; |
2534 | + PlayerSlot const id_; |
2535 | + |
2536 | + UI::Button player; |
2537 | + UI::Dropdown<std::string> |
2538 | + type_dropdown_; /// Select who owns the slot (human, AI, open, closed, shared-in). |
2539 | UI::Dropdown<std::string> tribes_dropdown_; /// Select the tribe or shared_in player. |
2540 | - PlayerSettings::State last_state_; /// The dropdown needs updating if this changes |
2541 | - size_t last_player_amount_; /// The dropdown needs rebuilding if this changes |
2542 | + UI::Dropdown<uintptr_t> |
2543 | + init_dropdown_; /// Select the initialization (Headquarters, Fortified Village etc.) |
2544 | + UI::Dropdown<uintptr_t> team_dropdown_; /// Select the team number |
2545 | + PlayerSettings::State |
2546 | + last_state_; /// The dropdowns for the other slots need updating if this changes |
2547 | + /// Lock rebuilding dropdowns so that they can close on selection |
2548 | + bool type_selection_locked_; |
2549 | + bool tribe_selection_locked_; |
2550 | + bool init_selection_locked_; |
2551 | + bool team_selection_locked_; |
2552 | + |
2553 | + std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_; |
2554 | }; |
2555 | |
2556 | MultiPlayerSetupGroup::MultiPlayerSetupGroup(UI::Panel* const parent, |
2557 | @@ -457,69 +590,41 @@ |
2558 | int32_t const w, |
2559 | int32_t const h, |
2560 | GameSettingsProvider* const settings, |
2561 | - uint32_t /* butw */, |
2562 | uint32_t buth) |
2563 | - : UI::Panel(parent, x, y, w, h), |
2564 | + : UI::Box(parent, x, y, UI::Box::Horizontal, w, h, 8 * kPadding), |
2565 | s(settings), |
2566 | npsb(new NetworkPlayerSettingsBackend(s)), |
2567 | - clientbox(this, 0, buth, UI::Box::Vertical, w / 3, h - buth), |
2568 | - playerbox(this, w * 6 / 15, buth, UI::Box::Vertical, w * 9 / 15, h - buth), |
2569 | + clientbox(this, 0, 0, UI::Box::Vertical), |
2570 | + playerbox(this, 0, 0, UI::Box::Vertical, w * 9 / 15, h, kPadding), |
2571 | buth_(buth) { |
2572 | - int small_font = UI_FONT_SIZE_SMALL * 3 / 4; |
2573 | - |
2574 | - // Clientbox and labels |
2575 | - labels.push_back(new UI::Textarea( |
2576 | - this, UI::Scrollbar::kSize * 6 / 5, buth / 3, w / 3 - buth - UI::Scrollbar::kSize * 2, buth)); |
2577 | - labels.back()->set_text(_("Client name")); |
2578 | - labels.back()->set_fontsize(small_font); |
2579 | - |
2580 | - labels.push_back(new UI::Textarea( |
2581 | - this, w / 3 - buth - UI::Scrollbar::kSize * 6 / 5, buth / 3, buth * 2, buth)); |
2582 | - labels.back()->set_text(_("Role")); |
2583 | - labels.back()->set_fontsize(small_font); |
2584 | - |
2585 | - clientbox.set_size(w / 3, h - buth); |
2586 | + clientbox.set_size(w / 3, h); |
2587 | clientbox.set_scrolling(true); |
2588 | |
2589 | - // Playerbox and labels |
2590 | - labels.push_back(new UI::Textarea(this, w * 6 / 15, buth / 3, buth, buth)); |
2591 | - labels.back()->set_text(_("Start")); |
2592 | - labels.back()->set_fontsize(small_font); |
2593 | - |
2594 | - labels.push_back(new UI::Textarea(this, w * 6 / 15 + buth, buth / 3 - 10, buth, buth)); |
2595 | - labels.back()->set_text(_("Type")); |
2596 | - labels.back()->set_fontsize(small_font); |
2597 | - |
2598 | - labels.push_back(new UI::Textarea(this, w * 6 / 15 + buth * 2, buth / 3, buth, buth)); |
2599 | - labels.back()->set_text(_("Tribe")); |
2600 | - labels.back()->set_fontsize(small_font); |
2601 | - |
2602 | - labels.push_back(new UI::Textarea( |
2603 | - this, w * 6 / 15 + buth * 3, buth / 3, w * 9 / 15 - 4 * buth, buth, UI::Align::kCenter)); |
2604 | - labels.back()->set_text(_("Initialization")); |
2605 | - labels.back()->set_fontsize(small_font); |
2606 | - |
2607 | - labels.push_back(new UI::Textarea(this, w - buth, buth / 3, buth, buth, UI::Align::kRight)); |
2608 | - labels.back()->set_text(_("Team")); |
2609 | - labels.back()->set_fontsize(small_font); |
2610 | - |
2611 | - playerbox.set_size(w * 9 / 15, h - buth); |
2612 | + add(&clientbox, UI::Box::Resizing::kExpandBoth); |
2613 | + add(&playerbox); |
2614 | + |
2615 | + // Playerbox |
2616 | + playerbox.set_size(w * 9 / 15, h); |
2617 | + playerbox.add_space(0); |
2618 | multi_player_player_groups.resize(kMaxPlayers); |
2619 | - for (uint8_t i = 0; i < multi_player_player_groups.size(); ++i) { |
2620 | + for (PlayerSlot i = 0; i < multi_player_player_groups.size(); ++i) { |
2621 | multi_player_player_groups.at(i) = |
2622 | - new MultiPlayerPlayerGroup(&playerbox, i, 0, 0, playerbox.get_w(), buth, s, npsb.get()); |
2623 | + new MultiPlayerPlayerGroup(&playerbox, playerbox.get_w(), buth_, i, s, npsb.get()); |
2624 | playerbox.add(multi_player_player_groups.at(i)); |
2625 | } |
2626 | - refresh(); |
2627 | + playerbox.add_space(0); |
2628 | + |
2629 | + subscriber_ = |
2630 | + Notifications::subscribe<NoteGameSettings>([this](const NoteGameSettings&) { update(); }); |
2631 | + set_size(w, h); |
2632 | + update(); |
2633 | } |
2634 | |
2635 | MultiPlayerSetupGroup::~MultiPlayerSetupGroup() { |
2636 | } |
2637 | |
2638 | -/** |
2639 | - * Update display and enabled buttons based on current settings. |
2640 | - */ |
2641 | -void MultiPlayerSetupGroup::refresh() { |
2642 | +/// Update which slots are available based on current settings. |
2643 | +void MultiPlayerSetupGroup::update() { |
2644 | const GameSettings& settings = s->settings(); |
2645 | |
2646 | // Update / initialize client groups |
2647 | @@ -529,15 +634,29 @@ |
2648 | for (uint32_t i = 0; i < settings.users.size(); ++i) { |
2649 | if (!multi_player_client_groups.at(i)) { |
2650 | multi_player_client_groups.at(i) = |
2651 | - new MultiPlayerClientGroup(&clientbox, i, 0, 0, clientbox.get_w(), buth_, s); |
2652 | - clientbox.add( |
2653 | - &*multi_player_client_groups.at(i), UI::Box::Resizing::kAlign, UI::Align::kCenter); |
2654 | - } |
2655 | - multi_player_client_groups.at(i)->refresh(); |
2656 | - } |
2657 | - |
2658 | - // Update player groups |
2659 | - for (uint32_t i = 0; i < kMaxPlayers; ++i) { |
2660 | - multi_player_player_groups.at(i)->refresh(); |
2661 | + new MultiPlayerClientGroup(&clientbox, clientbox.get_w(), buth_, i, s); |
2662 | + clientbox.add(multi_player_client_groups.at(i), UI::Box::Resizing::kFullSize); |
2663 | + multi_player_client_groups.at(i)->layout(); |
2664 | + } |
2665 | + multi_player_client_groups.at(i)->set_visible(true); |
2666 | + } |
2667 | + |
2668 | + // Keep track of which player slots are visible |
2669 | + for (PlayerSlot i = 0; i < multi_player_player_groups.size(); ++i) { |
2670 | + const bool should_be_visible = i < settings.players.size(); |
2671 | + if (should_be_visible != multi_player_player_groups.at(i)->is_visible()) { |
2672 | + multi_player_player_groups.at(i)->set_visible(should_be_visible); |
2673 | + } |
2674 | + } |
2675 | +} |
2676 | + |
2677 | +void MultiPlayerSetupGroup::draw(RenderTarget& dst) { |
2678 | + for (MultiPlayerPlayerGroup* player_group : multi_player_player_groups) { |
2679 | + if (player_group->is_visible()) { |
2680 | + dst.brighten_rect( |
2681 | + Recti(playerbox.get_x(), playerbox.get_y() + player_group->get_y() - kPadding / 2, |
2682 | + playerbox.get_w() + kPadding, player_group->get_h() + kPadding), |
2683 | + -MOUSE_OVER_BRIGHT_FACTOR); |
2684 | + } |
2685 | } |
2686 | } |
2687 | |
2688 | === modified file 'src/wui/multiplayersetupgroup.h' |
2689 | --- src/wui/multiplayersetupgroup.h 2017-01-25 18:55:59 +0000 |
2690 | +++ src/wui/multiplayersetupgroup.h 2017-09-02 13:55:49 +0000 |
2691 | @@ -33,7 +33,6 @@ |
2692 | #include "ui_basic/textarea.h" |
2693 | |
2694 | struct GameSettingsProvider; |
2695 | -struct MultiPlayerSetupGroupOptions; |
2696 | struct MultiPlayerClientGroup; |
2697 | struct MultiPlayerPlayerGroup; |
2698 | |
2699 | @@ -44,26 +43,27 @@ |
2700 | * clients, computers and closed players. |
2701 | * |
2702 | */ |
2703 | -struct MultiPlayerSetupGroup : public UI::Panel { |
2704 | +struct MultiPlayerSetupGroup : public UI::Box { |
2705 | MultiPlayerSetupGroup(UI::Panel* parent, |
2706 | int32_t x, |
2707 | int32_t y, |
2708 | int32_t w, |
2709 | int32_t h, |
2710 | GameSettingsProvider* settings, |
2711 | - uint32_t butw, |
2712 | uint32_t buth); |
2713 | ~MultiPlayerSetupGroup(); |
2714 | |
2715 | - void refresh(); |
2716 | - |
2717 | private: |
2718 | + void update(); |
2719 | + void draw(RenderTarget& dst) override; |
2720 | + |
2721 | GameSettingsProvider* const s; |
2722 | std::unique_ptr<NetworkPlayerSettingsBackend> npsb; |
2723 | std::vector<MultiPlayerClientGroup*> multi_player_client_groups; // not owned |
2724 | std::vector<MultiPlayerPlayerGroup*> multi_player_player_groups; // not owned |
2725 | + std::unique_ptr<Notifications::Subscriber<NoteGameSettings>> subscriber_; |
2726 | + |
2727 | UI::Box clientbox, playerbox; |
2728 | - std::vector<UI::Textarea*> labels; |
2729 | |
2730 | uint32_t buth_; |
2731 | |
2732 | |
2733 | === modified file 'src/wui/playerdescrgroup.cc' |
2734 | --- src/wui/playerdescrgroup.cc 2017-01-30 14:40:12 +0000 |
2735 | +++ src/wui/playerdescrgroup.cc 2017-09-02 13:55:49 +0000 |
2736 | @@ -116,7 +116,7 @@ |
2737 | |
2738 | d->btnEnablePlayer->set_enabled(stateaccess); |
2739 | |
2740 | - if (player.state == PlayerSettings::stateClosed) { |
2741 | + if (player.state == PlayerSettings::State::kClosed) { |
2742 | d->btnEnablePlayer->set_state(false); |
2743 | d->btnPlayerTeam->set_visible(false); |
2744 | d->btnPlayerTeam->set_enabled(false); |
2745 | @@ -132,7 +132,7 @@ |
2746 | d->btnPlayerType->set_visible(true); |
2747 | d->btnPlayerType->set_enabled(stateaccess); |
2748 | |
2749 | - if (player.state == PlayerSettings::stateOpen) { |
2750 | + if (player.state == PlayerSettings::State::kOpen) { |
2751 | d->btnPlayerType->set_title(_("Open")); |
2752 | d->btnPlayerTeam->set_visible(false); |
2753 | d->btnPlayerTeam->set_visible(false); |
2754 | @@ -144,7 +144,7 @@ |
2755 | } else { |
2756 | std::string title; |
2757 | |
2758 | - if (player.state == PlayerSettings::stateComputer) { |
2759 | + if (player.state == PlayerSettings::State::kComputer) { |
2760 | if (player.ai.empty()) |
2761 | title = _("Computer"); |
2762 | else { |
2763 | @@ -156,7 +156,7 @@ |
2764 | title = _(impl->descname); |
2765 | } |
2766 | } |
2767 | - } else { // PlayerSettings::stateHuman |
2768 | + } else { // PlayerSettings::State::stateHuman |
2769 | title = _("Human"); |
2770 | } |
2771 | d->btnPlayerType->set_title(title); |
2772 | @@ -215,11 +215,11 @@ |
2773 | return; |
2774 | |
2775 | if (on) { |
2776 | - if (settings.players[d->plnum].state == PlayerSettings::stateClosed) |
2777 | + if (settings.players[d->plnum].state == PlayerSettings::State::kClosed) |
2778 | d->settings->next_player_state(d->plnum); |
2779 | } else { |
2780 | - if (settings.players[d->plnum].state != PlayerSettings::stateClosed) |
2781 | - d->settings->set_player_state(d->plnum, PlayerSettings::stateClosed); |
2782 | + if (settings.players[d->plnum].state != PlayerSettings::State::kClosed) |
2783 | + d->settings->set_player_state(d->plnum, PlayerSettings::State::kClosed); |
2784 | } |
2785 | } |
2786 |
Does sometimes work, sometimes not (used the LAN/Direct IP setup):
Mostly the drop down box appear for a very short time after the mouse button is released.
Sometimes the drop down box appears when releasing the mouse button.
When the drop down box appears for one button, most of the other buttons shows the drop down box.
The described behave is unspecific to a particular button. E.g. sometimes the Tribe selection works, sometimes not.
Used the 'Nile' map for testing.