MP Staging/Join Game: unified player list handling and added player icons

Previously, MP Staging was using connect_engine::connected_users() to fill in the user list.
However, I noticed that the server was already sending the user list to all clients, including
the host, so I could unify the handling between Staging and Join Game by just using the server
data for both.

I've also added appropriate indicators for host, observer, and self in the list for both dialogs.
Additional host= and observer= status keys are sent by the server for that purpose.

I've also made the server dispatch the player list to the host when a game is created. This is
slightly redundant, since the host is the only player at that point, but it's easier than creating
a user config locally, or using connected_users(), not to mention easier to maintain.

The wesnothd::game::send_user_list function no longer exists early id `description_` is null.
No idea why it did that. It's not even used in the function. Anyway, it needed to be removed in
order for the above change to work.

Speaking of the host's copy of the player list, I haven't touched that. It's still needed for
managing things in the connect_engine. Might simplify things further in the future by delegating
more handling to the server, since it has a lot of the data needed already, but that's a different
project.
This commit is contained in:
Charles Dang 2017-12-14 11:12:49 +11:00
parent 9c0b304a53
commit 002b1a3c87
13 changed files with 288 additions and 239 deletions

View file

@ -0,0 +1,111 @@
#
# Common player list layout used in MP Staging and MP Join Game
#
#define _GUI_MP_PLAYER_LIST_PANEL_LINKED_GROUPS
[linked_group]
id = "player_type_icon"
fixed_width = true
[/linked_group]
#enddef
#define _GUI_MP_PLAYER_LIST_PANEL
[panel]
definition = "box_display_no_blur_no_border"
[grid]
[row]
grow_factor = 0
[column]
border = "all"
border_size = 5
horizontal_grow = true
[label]
definition = "gold_small"
label = _ "Connected Players"
[/label]
[/column]
[/row]
[row]
grow_factor = 1
[column]
horizontal_grow = true
vertical_grow = true
[listbox]
id = "player_list"
definition = "default"
horizontal_scrollbar_mode = "never"
[list_definition]
[row]
[column]
horizontal_grow = true
[toggle_panel]
id = "panel"
definition = "fancy"
[grid]
[row]
[column]
grow_factor = 0
border = "all"
border_size = 5
horizontal_grow = true
[image]
id = "player_type_icon"
definition = "centered"
linked_group = "player_type_icon"
[/image]
[/column]
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_grow = true
[label]
id = "player_name"
definition = "default"
[/label]
[/column]
[/row]
[/grid]
[/toggle_panel]
[/column]
[/row]
[/list_definition]
[/listbox]
[/column]
[/row]
[/grid]
[/panel]
#enddef

View file

@ -312,89 +312,7 @@
horizontal_grow = true
vertical_grow = true
[panel]
definition = "box_display_no_blur_no_border"
[grid]
[row]
grow_factor = 0
[column]
border = "all"
border_size = 5
horizontal_grow = true
[label]
definition = "gold_small"
label = _ "Connected Players"
[/label]
[/column]
[/row]
[row]
grow_factor = 1
[column]
horizontal_grow = true
vertical_grow = true
[listbox]
id = "player_list"
definition = "default"
horizontal_scrollbar_mode = "never"
[list_definition]
[row]
[column]
horizontal_grow = true
[toggle_panel]
id = "panel"
definition = "default"
[grid]
[row]
[column]
border = "all"
border_size = 5
horizontal_grow = true
[label]
id = "player_name"
definition = "default"
[/label]
[/column]
[/row]
[/grid]
[/toggle_panel]
[/column]
[/row]
[/list_definition]
[/listbox]
[/column]
[/row]
[/grid]
[/panel]
{_GUI_MP_PLAYER_LIST_PANEL}
[/column]
@ -454,6 +372,8 @@
fixed_width = true
[/linked_group]
{_GUI_MP_PLAYER_LIST_PANEL_LINKED_GROUPS}
[tooltip]
id = "tooltip"
[/tooltip]

View file

@ -393,92 +393,6 @@
[/tree_view]
#enddef
#define _GUI_PLAYER_LIST
[panel]
definition = "box_display_no_blur_no_border"
[grid]
[row]
grow_factor = 0
[column]
border = "all"
border_size = 5
horizontal_grow = true
[label]
definition = "gold_small"
label = _ "Connected Players"
[/label]
[/column]
[/row]
[row]
grow_factor = 1
[column]
horizontal_grow = true
vertical_grow = true
[listbox]
id = "player_list"
definition = "default"
horizontal_scrollbar_mode = "never"
[list_definition]
[row]
[column]
horizontal_grow = true
[toggle_panel]
id = "panel"
definition = "default"
[grid]
[row]
[column]
border = "all"
border_size = 5
horizontal_grow = true
[label]
id = "player_name"
definition = "default"
[/label]
[/column]
[/row]
[/grid]
[/toggle_panel]
[/column]
[/row]
[/list_definition]
[/listbox]
[/column]
[/row]
[/grid]
[/panel]
#enddef
#define _GUI_CONTROL_AREA
[grid]
@ -591,6 +505,8 @@
fixed_width = true
[/linked_group]
{_GUI_MP_PLAYER_LIST_PANEL_LINKED_GROUPS}
[tooltip]
id = "tooltip"
[/tooltip]
@ -722,7 +638,7 @@
border = "all"
border_size = 5
{_GUI_PLAYER_LIST}
{_GUI_MP_PLAYER_LIST_PANEL}
[/column]
[/row]
@ -778,6 +694,8 @@
fixed_width = true
[/linked_group]
{_GUI_MP_PLAYER_LIST_PANEL_LINKED_GROUPS}
[tooltip]
id = "tooltip"
[/tooltip]
@ -911,7 +829,7 @@
horizontal_grow = true
vertical_grow = true
{_GUI_PLAYER_LIST}
{_GUI_MP_PLAYER_LIST_PANEL}
[/column]
[/row]
@ -944,4 +862,3 @@
#undef _GUI_CONTROL_AREA
#undef _GUI_SIDE_LIST
#undef _GUI_PLAYER_LIST

View file

@ -1888,6 +1888,13 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\multiplayer\player_list_helper.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\multiplayer\synced_choice_wait.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)Gui\Dialogs\Multiplayer\</ObjectFileName>
@ -3790,6 +3797,7 @@
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\mp_options_helper.hpp" />
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\mp_staging.hpp" />
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\player_info.hpp" />
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\player_list_helper.hpp" />
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\plugin_executor.hpp" />
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\synced_choice_wait.hpp" />
<ClInclude Include="..\..\src\gui\dialogs\network_transmission.hpp" />

View file

@ -1555,6 +1555,9 @@
<ClCompile Include="..\..\src\gui\core\static_registry.cpp">
<Filter>Gui\Core</Filter>
</ClCompile>
<ClCompile Include="..\..\src\gui\dialogs\multiplayer\player_list_helper.cpp">
<Filter>Gui\Dialogs\Multiplayer</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\addon\client.hpp">
@ -3019,6 +3022,9 @@
<ClInclude Include="..\..\src\gui\core\static_registry.hpp">
<Filter>Gui\Core</Filter>
</ClInclude>
<ClInclude Include="..\..\src\gui\dialogs\multiplayer\player_list_helper.hpp">
<Filter>Gui\Dialogs\Multiplayer</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="..\..\src\tests\test_sdl_utils.hpp">

View file

@ -222,6 +222,7 @@ gui/dialogs/multiplayer/mp_method_selection.cpp
gui/dialogs/multiplayer/mp_options_helper.cpp
gui/dialogs/multiplayer/mp_staging.cpp
gui/dialogs/multiplayer/player_info.cpp
gui/dialogs/multiplayer/player_list_helper.cpp
gui/dialogs/multiplayer/synced_choice_wait.cpp
gui/dialogs/network_transmission.cpp
gui/dialogs/outro.cpp

View file

@ -72,6 +72,7 @@ mp_join_game::mp_join_game(saved_game& state, mp::lobby_info& lobby_info, wesnot
, first_scenario_(first_scenario)
, observe_game_(observe_game)
, stop_updates_(false)
, player_list_(nullptr)
{
set_show_even_without_video(true);
}
@ -287,6 +288,11 @@ void mp_join_game::pre_show(window& window)
chat.room_window_open("this game", true, false); // TODO: better title?
chat.active_window_changed();
//
// Set up player list
//
player_list_.reset(new player_list_helper(&window));
//
// Set up the network handling
//
@ -416,23 +422,6 @@ void mp_join_game::generate_side_list(window& window)
}
}
void mp_join_game::update_player_list(window& window, const config::const_child_itors& users)
{
listbox& player_list = find_widget<listbox>(&window, "player_list", false);
player_list.clear();
for(const auto& user : users) {
std::map<std::string, string_map> data;
string_map item;
item["label"] = user["name"];
data.emplace("player_name", item);
player_list.add_row(data);
}
}
void mp_join_game::network_handler(window& window)
{
// If the game has already started, close the dialog immediately.
@ -484,9 +473,8 @@ void mp_join_game::network_handler(window& window)
}
// Update player list
// TODO: optimally, it wouldn't regenerate the entire list every single refresh cycle
if(data.has_child("user")) {
update_player_list(window, data.child_range("user"));
if(data.has_child("userlist")) {
player_list_->update_list(data.child("userlist").child_range("user"));
}
}

View file

@ -14,12 +14,12 @@
#pragma once
#include "ai/configuration.hpp"
#include "gui/dialogs/modal_dialog.hpp"
#include "game_initialization/lobby_info.hpp"
#include "gui/dialogs/multiplayer/plugin_executor.hpp"
#include "game_initialization/connect_engine.hpp"
#include "game_initialization/lobby_info.hpp"
#include "game_initialization/multiplayer.hpp"
#include "gui/dialogs/modal_dialog.hpp"
#include "gui/dialogs/multiplayer/player_list_helper.hpp"
#include "gui/dialogs/multiplayer/plugin_executor.hpp"
#include "mp_game_settings.hpp"
class config;
@ -54,8 +54,6 @@ private:
void generate_side_list(window& window);
void update_player_list(window& window, const config::const_child_itors& users);
void network_handler(window& window);
config& get_scenario();
@ -76,6 +74,8 @@ private:
bool stop_updates_;
std::map<std::string, tree_view_node*> team_tree_map_;
std::unique_ptr<player_list_helper> player_list_;
};
} // namespace dialogs

View file

@ -58,6 +58,7 @@ mp_staging::mp_staging(ng::connect_engine& connect_engine, mp::lobby_info& lobby
, state_changed_(false)
, team_tree_map_()
, side_tree_map_()
, player_list_(nullptr)
{
set_show_even_without_video(true);
@ -112,15 +113,13 @@ void mp_staging::pre_show(window& window)
//
// Set up player list
//
update_player_list(window);
player_list_.reset(new player_list_helper(&window));
//
// Set up the network handling
//
update_timer_ = add_timer(game_config::lobby_network_timer, std::bind(&mp_staging::network_handler, this, std::ref(window)), true);
network_handler(window);
//
// Set up the Lua plugin context
//
@ -309,23 +308,6 @@ void mp_staging::add_side_node(window& window, ng::side_engine_ptr side)
}
}
void mp_staging::update_player_list(window& window)
{
listbox& player_list = find_widget<listbox>(&window, "player_list", false);
player_list.clear();
for(const auto& player : connect_engine_.connected_users()) {
std::map<std::string, string_map> data;
string_map item;
item["label"] = player;
data.emplace("player_name", item);
player_list.add_row(data);
}
}
void mp_staging::on_controller_select(ng::side_engine_ptr side, grid& row_grid)
{
menu_button& ai_selection = find_widget<menu_button>(&row_grid, "ai_controller", false);
@ -497,8 +479,9 @@ void mp_staging::network_handler(window& window)
}
// Update player list
// TODO: optimally, it wouldn't regenerate the entire list every single refresh cycle
update_player_list(window);
if(data.has_child("userlist")) {
player_list_->update_list(data.child("userlist").child_range("user"));
}
// Update status label and buttons
update_status_label_and_buttons(window);

View file

@ -14,12 +14,12 @@
#pragma once
#include "ai/configuration.hpp"
#include "gui/dialogs/modal_dialog.hpp"
#include "game_initialization/lobby_info.hpp"
#include "gui/dialogs/multiplayer/plugin_executor.hpp"
#include "game_initialization/connect_engine.hpp"
#include "game_initialization/lobby_info.hpp"
#include "game_initialization/multiplayer.hpp"
#include "gui/dialogs/modal_dialog.hpp"
#include "gui/dialogs/multiplayer/player_list_helper.hpp"
#include "gui/dialogs/multiplayer/plugin_executor.hpp"
#include "mp_game_settings.hpp"
class config;
@ -63,7 +63,6 @@ private:
void select_leader_callback(ng::side_engine_ptr side, grid& row_grid);
void update_player_list(window& window);
void update_leader_display(ng::side_engine_ptr side, grid& row_grid);
void update_status_label_and_buttons(window& window);
@ -88,6 +87,8 @@ private:
std::map<std::string, tree_view_node*> team_tree_map_;
std::map<ng::side_engine_ptr, tree_view_node*> side_tree_map_;
std::unique_ptr<player_list_helper> player_list_;
};
} // namespace dialogs

View file

@ -0,0 +1,67 @@
/*
Copyright (C) 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "gui/dialogs/multiplayer/player_list_helper.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/widgets/listbox.hpp"
#include "gui/widgets/window.hpp"
#include "preferences/credentials.hpp"
namespace gui2
{
player_list_helper::player_list_helper(window* window)
: list_(find_widget<listbox>(window, "player_list", false))
{
}
void player_list_helper::update_list(const config::const_child_itors& users)
{
list_.clear();
unsigned i = 0;
for(const config& user : users) {
std::map<std::string, string_map> data;
string_map item;
const std::string name = user["name"];
const bool is_you = name == preferences::login();
std::string icon;
if(user["host"].to_bool()) {
icon = "misc/leader-crown.png~CROP(12, 1, 15, 15)";
} else if(user["observer"].to_bool()) {
icon = "misc/eye.png";
} else if(is_you) {
icon = "lobby/status-lobby-s.png";
} else {
icon = "lobby/status-lobby-n.png";
}
item["label"] = icon;
data.emplace("player_type_icon", item);
item["label"] = name;
data.emplace("player_name", item);
list_.add_row(data);
if(is_you) {
list_.select_row(i);
}
++i;
}
}
} // namespace gui2

View file

@ -0,0 +1,34 @@
/*
Copyright (C) 2017 by the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "config.hpp"
namespace gui2
{
class listbox;
class window;
class player_list_helper
{
public:
explicit player_list_helper(window* window);
void update_list(const config::const_child_itors& users);
private:
listbox& list_;
};
} // end namespace gui2

View file

@ -114,6 +114,11 @@ game::game(player_connections& player_connections, socket_ptr host,
// Mark the host as unavailable in the lobby.
iter->info().mark_available(id_, name_);
iter->info().set_status(player::PLAYING);
// Send player list to the host. At this point, it only contains the host themelves.
// This *could* therefor be handled on the client side, but having the server send it
// means we can have the same code handle the player list UI in multiple dialogs.
send_user_list();
}
game::~game()
@ -1320,22 +1325,30 @@ bool game::remove_player(const socket_ptr player, const bool disconnect, const b
return false;
}
void game::send_user_list(const socket_ptr exclude) const {
//if the game hasn't started yet, then send all players a list
//of the users in the game
if (started_ || description_ == nullptr) return;
/** @todo Should be renamed to userlist. */
void game::send_user_list(const socket_ptr exclude) const
{
// If the game hasn't started yet, then send all players a list of the users in the game.
if(started_ /*|| description_ == nullptr*/) {
return;
}
simple_wml::document cfg;
cfg.root().add_child("gamelist");
user_vector users = all_game_users();
for(user_vector::const_iterator p = users.begin(); p != users.end(); ++p) {
const auto pl = player_connections_.find(*p);
simple_wml::node& list = cfg.root().add_child("userlist");
for(const socket_ptr& user_ptr : all_game_users()) {
const auto pl = player_connections_.find(user_ptr);
if(pl != player_connections_.end()) {
//don't need to duplicate pl->second.name().c_str() because the
simple_wml::node& user = list.add_child("user");
// Don't need to duplicate pl->second.name().c_str() because the
// document will be destroyed by the end of the function
cfg.root().add_child("user").set_attr("name", pl->info().name().c_str());
user.set_attr("name", pl->info().name().c_str());
user.set_attr("host", is_owner(user_ptr) ? "yes" : "no");
user.set_attr("observer", is_observer(user_ptr) ? "yes" : "no");
}
}
send_data(cfg, exclude);
}