diff --git a/data/gui/window/mp_join_game.cfg b/data/gui/window/mp_join_game.cfg new file mode 100644 index 00000000000..f8aaf5c2f45 --- /dev/null +++ b/data/gui/window/mp_join_game.cfg @@ -0,0 +1,505 @@ +#textdomain wesnoth-lib +### +### Definition of the mp game staging screen +### + +#define _GUI_SIDE_LIST + [listbox] + id = "side_list" + definition = "default" + + vertical_scrollbar_mode = "always" + horizontal_scrollbar_mode = "never" + + [list_definition] + + [row] + + [column] + horizontal_grow = "true" + vertical_grow = "true" + + [toggle_panel] + id = "panel" + definition = "default" + + [grid] + + [row] + + [column] + grow_factor = 0 + border = "all" + border_size = 10 + horizontal_grow = "true" + vertical_alignment = "top" + + [label] + id = "side_number" + definition = "default_large" + linked_group = "side_number" + [/label] + [/column] + + [column] + grow_factor = 1 + horizontal_grow = "true" + #vertical_grow = "true" + + [grid] + linked_group = "leader" + + [row] + + [column] + grow_factor = 0 + horizontal_grow = "true" + border = "all" + border_size = 5 + + [image] + id = "leader_image" + definition = "default" + [/image] + [/column] + + [column] + grow_factor = 1 + horizontal_grow = "true" + vertical_grow = "true" + + [grid] + + [row] + grow_factor = 1 + + [column] + border = "all" + border_size = 5 + horizontal_grow = "true" + vertical_grow = "true" + + [label] + id = "leader_type" + definition = "default_large" + use_markup = "true" + [/label] + + [/column] + + [/row] + + [row] + grow_factor = 0 + + [column] + horizontal_grow = "true" + #vertical_grow = "true" + + [grid] + + [row] + + [column] + grow_factor = 0 + border = "left,bottom" + border_size = 5 + + [label] + definition = "default_small" + label = _ "Faction:" + use_markup = "true" + [/label] + + [/column] + + [column] + grow_factor = 1 + border = "left,bottom,right" + border_size = 5 + horizontal_grow = "true" + vertical_grow = "true" + + [label] + id = "leader_faction" + definition = "default_small" + use_markup = "true" + [/label] + + [/column] + + [/row] + + [row] + + [column] + grow_factor = 0 + border = "left,bottom" + border_size = 5 + + [label] + definition = "default_small" + label = _ "Gender:" + use_markup = "true" + [/label] + + [/column] + + [column] + grow_factor = 1 + border = "left,bottom,right" + border_size = 5 + horizontal_grow = "true" + vertical_grow = "true" + + [image] + id = "leader_gender" + definition = "default_small" + [/image] + + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [/grid] + + [/column] + + [column] + grow_factor = 0 + border = "all" + border_size = 5 + horizontal_grow = "true" + + [label] + id = "side_team" + definition = "default" + [/label] + [/column] + + [column] + grow_factor = 0 + border = "all" + border_size = 5 + horizontal_grow = "true" + + [label] + id = "side_gold" + definition = "default" + [/label] + [/column] + + [column] + grow_factor = 0 + border = "all" + border_size = 5 + horizontal_grow = "true" + + [label] + id = "side_income" + definition = "default" + [/label] + [/column] + + [column] + grow_factor = 0 + border = "all" + border_size = 5 + horizontal_grow = "true" + + [label] + id = "side_color" + definition = "default" + [/label] + [/column] + + [/row] + + [/grid] + + [/toggle_panel] + + [/column] + + [/row] + + [/list_definition] + + [/listbox] +#enddef + +#define _GUI_CONTROL_AREA + [grid] + + [row] + grow_factor = 1 + + [column] + border = "all" + border_size = 5 + 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] + + [row] + grow_factor = 0 + + [column] + + [grid] + + [row] + + [column] + border = "all" + border_size = 5 + + [button] + id = "ok" + definition = "large" + label = _ "I’m Ready" + [/button] + [/column] + + [column] + border = "all" + border_size = 5 + + [button] + id = "cancel" + definition = "large" + label = _ "Cancel" + [/button] + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [/grid] +#enddef + +[window] + id = "mp_join_game" + description = "MP join game." + + [resolution] + definition = "borderless" + + {GUI_WINDOW_FULLSCREEN} + + [linked_group] + id = "side_number" + fixed_width = "true" + [/linked_group] + + [linked_group] + id = "controller" + fixed_width = "true" + [/linked_group] + + [linked_group] + id = "leader" + fixed_width = "true" + [/linked_group] + + [linked_group] + id = "team_and_color" + fixed_width = "true" + [/linked_group] + + [linked_group] + id = "gold_and_income" + fixed_width = "true" + [/linked_group] + + [tooltip] + id = "tooltip" + [/tooltip] + + [helptip] + id = "tooltip" + [/helptip] + + [grid] + + [row] + grow_factor = 0 + + [column] + grow_factor = 1 + horizontal_alignment = "left" + border = "all" + border_size = 5 + + [label] + id = "title" + definition = "title" + label = _ "Game Lobby" + [/label] + [/column] + + [/row] + + [row] + grow_factor = 1 + + [column] + horizontal_grow = "true" + vertical_grow = "true" + + [grid] + + [row] + + [column] + grow_factor = 1 + vertical_grow = "true" + horizontal_grow = "true" + + [grid] + + [row] + grow_factor = 1 + + [column] + horizontal_grow = "true" + vertical_grow = "true" + + {GUI_FORCE_WIDGET_MINIMUM_SIZE 0 "((screen_height * 55) / 100)" ( + border = "all" + border_size = 5 + {_GUI_SIDE_LIST} + )} + [/column] + + [/row] + + [row] + grow_factor = 0 + + [column] + grow_factor = 1 + horizontal_grow = "true" + border = "all" + border_size = 5 + + [label] + id = "status_label" + definition = "default_small" + label = _ "Waiting for players to join..." + [/label] + [/column] + + [/row] + + [row] + grow_factor = 0 + + [column] + horizontal_grow = "true" + vertical_grow = "true" + + {GUI_FORCE_WIDGET_MINIMUM_SIZE 0 "((screen_height * 25 / 100))" ( + [chatbox] + id = "chat" + [/chatbox] + )} + [/column] + + [/row] + + [/grid] + + [/column] + + [column] + grow_factor = 0 + horizontal_grow = "true" + vertical_grow = "true" + + {_GUI_CONTROL_AREA} + [/column] + + [/row] + + [/grid] + + [/column] + + [/row] + + [/grid] + + [/resolution] + +[/window] + +#undef _GUI_CONTROL_AREA +#undef _GUI_SIDE_LIST diff --git a/projectfiles/CodeBlocks/wesnoth.cbp b/projectfiles/CodeBlocks/wesnoth.cbp index b2ed28803df..d2d85f849aa 100644 --- a/projectfiles/CodeBlocks/wesnoth.cbp +++ b/projectfiles/CodeBlocks/wesnoth.cbp @@ -607,6 +607,8 @@ + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16c69473b1a..df35a9bb843 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -829,6 +829,7 @@ set(wesnoth-main_SRC gui/dialogs/multiplayer/mp_create_game.cpp gui/dialogs/multiplayer/mp_create_game_set_password.cpp gui/dialogs/multiplayer/mp_host_game_prompt.cpp + gui/dialogs/multiplayer/mp_join_game.cpp gui/dialogs/multiplayer/mp_join_game_password_prompt.cpp gui/dialogs/multiplayer/mp_login.cpp gui/dialogs/multiplayer/mp_method_selection.cpp diff --git a/src/SConscript b/src/SConscript index 3bc93a7fd40..ec87dcea798 100644 --- a/src/SConscript +++ b/src/SConscript @@ -404,6 +404,7 @@ wesnoth_sources = Split(""" gui/dialogs/multiplayer/mp_create_game_set_password.cpp gui/dialogs/multiplayer/mp_create_game.cpp gui/dialogs/multiplayer/mp_host_game_prompt.cpp + gui/dialogs/multiplayer/mp_join_game.cpp gui/dialogs/multiplayer/mp_join_game_password_prompt.cpp gui/dialogs/multiplayer/mp_login.cpp gui/dialogs/multiplayer/mp_method_selection.cpp diff --git a/src/game_initialization/multiplayer.cpp b/src/game_initialization/multiplayer.cpp index c4f1a01f5a7..63a902debd4 100644 --- a/src/game_initialization/multiplayer.cpp +++ b/src/game_initialization/multiplayer.cpp @@ -25,6 +25,7 @@ #include "gui/dialogs/message.hpp" #include "gui/dialogs/multiplayer/mp_connect.hpp" #include "gui/dialogs/multiplayer/mp_create_game.hpp" +#include "gui/dialogs/multiplayer/mp_join_game.hpp" #include "gui/dialogs/multiplayer/mp_login.hpp" #include "gui/dialogs/multiplayer/mp_staging.hpp" #include "gui/dialogs/network_transmission.hpp" @@ -405,17 +406,6 @@ static std::unique_ptr open_connection(CVideo& video, cons // The functions enter_(screen)_mode are simple functions that take care of // creating the dialogs, then, according to the dialog result, of calling other // of those screen functions. - -static config& get_scenario(config& c) -{ - if(config& scenario = c.child("scenario")) - return scenario; - else if(config& snapshot = c.child("snapshot")) - return snapshot; - else - return c; -} - static void enter_wait_mode(CVideo& video, const config& game_config, saved_game& state, twesnothd_connection* wesnothd_connection, lobby_info& li, bool observe, int current_turn = 0) { @@ -435,131 +425,8 @@ static void enter_wait_mode(CVideo& video, const config& game_config, saved_game { if(preferences::new_lobby()) { - bool download_res = true; - assert(wesnothd_connection); - config level; - DBG_MP << "download_level_data()\n"; - - if(!true) { - // Ask for the next scenario data. - wesnothd_connection->send_data(config("load_next_scenario")); - } - - bool has_scenario_and_controllers = false; - while(!has_scenario_and_controllers) { - config revc; - bool data_res = gui2::tnetwork_transmission::wesnothd_receive_dialog( - video, "download level data", revc, *wesnothd_connection); - - if(!data_res) { - DBG_MP << "download_level_data bad results\n"; - download_res = false; - } - - mp::check_response(data_res, revc); - - if(revc.child("leave_game")) { - download_res = false; - } else if(config& next_scenario = revc.child("next_scenario")) { - level.swap(next_scenario); - } else if(revc.has_attribute("version")) { - level.swap(revc); - has_scenario_and_controllers = true; - } else if(config& controllers = revc.child("controllers")) { - int index = 0; - for(const config& controller : controllers.child_range("controller")) { - if(config& side = get_scenario(level).child("side", index)) { - side["is_local"] = controller["is_local"]; - } - ++index; - } - has_scenario_and_controllers = true; - } - - } - - std::cerr << "download_level_data() success.\n"; - - if(!download_res) { - DBG_MP << "mp wait: could not download level data, quitting..."; - //set_result(QUIT); - return; - } else if(level["started"].to_bool()) { - //set_result(PLAY); - return; - } - - if(true) { - state = saved_game(); - state.classification() = game_classification(level); - - if(state.classification().campaign_type != game_classification::CAMPAIGN_TYPE::MULTIPLAYER) { - //ERR_MP << "Mp wait recieved a game that is not a multiplayer game\n"; - } - // Make sure that we have the same config as host, if possible. - game_config_manager::get()->load_game_config_for_game(state.classification()); - } - - // Add the map name to the title. - //append_to_title(": " + get_scenario(level)["name"].t_str()); - - game_config::add_color_info(get_scenario(level)); - if(!observe) { - //search for an appropriate vacant slot. If a description is set - //(i.e. we're loading from a saved game), then prefer to get the side - //with the same description as our login. Otherwise just choose the first - //available side. - const config *side_choice = nullptr; - //int side_num = -1, nb_sides = 0; - for(const config &sd : get_scenario(level).child_range("side")) { - //std::cerr << "*** side " << nb_sides << "***\n" << sd.debug() << "***\n"; - - if(sd["controller"] == "reserved" && sd["current_player"] == preferences::login()) { - side_choice = &sd; - //side_num = nb_sides; - break; - } - - if(sd["controller"] == "human" && sd["player_id"].empty()) { - if(!side_choice) { // found the first empty side - side_choice = &sd; - //side_num = nb_sides; - } - - if(sd["current_player"] == preferences::login()) { - side_choice = &sd; - //side_num = nb_sides; - break; // found the preferred one - } - } - - if(sd["player_id"] == preferences::login()) { - //We already own a side in this game. - //generate_menu(); - return; - } - //++nb_sides; - } - - if(!side_choice) { - DBG_MP << "could not find a side, all " << get_scenario(level).child_count("side") << " sides were unsuitable\n"; - //set_result(QUIT); - return; - } - } - - mp::level_to_gamestate(level, state); - - campaign_info.reset(new mp_campaign_info(*wesnothd_connection)); - - //campaign_info->connected_players.insert(li.users().begin(), li.users().end()); - - ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, campaign_info.get())); - - connect_engine->receive_from_server(level); - - gui2::tmp_staging dlg(*connect_engine, li, wesnothd_connection); + gui2::tmp_join_game dlg(state, li, *wesnothd_connection, true, observe); dlg.show(video); if(dlg.get_retval() == gui2::twindow::OK) { @@ -568,9 +435,9 @@ static void enter_wait_mode(CVideo& video, const config& game_config, saved_game controller.play_game(); } - //if(wesnothd_connection) { - // wesnothd_connection->send_data(config("leave_game")); - //} + if(wesnothd_connection) { + wesnothd_connection->send_data(config("leave_game")); + } return; } diff --git a/src/gui/dialogs/multiplayer/mp_join_game.cpp b/src/gui/dialogs/multiplayer/mp_join_game.cpp new file mode 100644 index 00000000000..b8985768e53 --- /dev/null +++ b/src/gui/dialogs/multiplayer/mp_join_game.cpp @@ -0,0 +1,508 @@ +/* + Copyright (C) 2008 - 2016 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. +*/ + +#define GETTEXT_DOMAIN "wesnoth-lib" + +#include "gui/dialogs/multiplayer/mp_join_game.hpp" + +#include "config_assign.hpp" +#include "formula/string_utils.hpp" +#include "game_config_manager.hpp" +#include "game_initialization/mp_game_utils.hpp" +#include "game_preferences.hpp" +#include "gettext.hpp" +#include "gui/auxiliary/field.hpp" +#include "gui/dialogs/helper.hpp" +#include "gui/dialogs/message.hpp" +#include "gui/dialogs/multiplayer/faction_select.hpp" +#include "gui/dialogs/network_transmission.hpp" +#include "gui/dialogs/transient_message.hpp" +#include "gui/widgets/integer_selector.hpp" +#include "gui/widgets/button.hpp" +#include "gui/widgets/chatbox.hpp" +#include "gui/widgets/menu_button.hpp" +#include "gui/widgets/image.hpp" +#ifdef GUI2_EXPERIMENTAL_LISTBOX +#include "gui/widgets/list.hpp" +#else +#include "gui/widgets/listbox.hpp" +#endif +#include "gui/widgets/minimap.hpp" +#include "gui/widgets/settings.hpp" +#include "gui/widgets/label.hpp" +#include "gui/widgets/slider.hpp" +#include "gui/widgets/stacked_widget.hpp" +#include "gui/widgets/status_label_helper.hpp" +#include "gui/widgets/toggle_button.hpp" +#include "gui/widgets/toggle_panel.hpp" +#include "gui/widgets/text_box.hpp" +#include "game_config.hpp" +#include "mp_ui_alerts.hpp" +#include "settings.hpp" +#include "statistics.hpp" +#include "units/types.hpp" +#include "formatter.hpp" +#include "wesnothd_connection.hpp" + +#ifdef GUI2_EXPERIMENTAL_LISTBOX +#include "utils/functional.hpp" +#endif + +namespace gui2 +{ + +REGISTER_DIALOG(mp_join_game) + +tmp_join_game::tmp_join_game(saved_game& state, lobby_info& lobby_info, twesnothd_connection& wesnothd_connection, const bool first_scenario, const bool observe_game) + : level_() + , state_(state) + , lobby_info_(lobby_info) + , wesnothd_connection_(wesnothd_connection) + , update_timer_(0) + , first_scenario_(first_scenario) + , observe_game_(observe_game) + , stop_updates_(false) +{ + set_show_even_without_video(true); +} + +tmp_join_game::~tmp_join_game() +{ + if(update_timer_ != 0) { + remove_timer(update_timer_); + update_timer_ = 0; + } +} + +/* + * Fetch the selected game's config from the server and prompts an initial faction selection. + */ +void tmp_join_game::post_build(twindow& window) +{ + // Ask for the next scenario data, if applicable + if(!first_scenario_) { + wesnothd_connection_.send_data(config("load_next_scenario")); + } + + bool has_scenario_and_controllers = false; + while(!has_scenario_and_controllers) { + config revc; + const bool data_res = gui2::tnetwork_transmission::wesnothd_receive_dialog( + window.video(), "download level data", revc, wesnothd_connection_); + + if(!data_res) { + window.set_retval(twindow::CANCEL); + } + + mp::check_response(data_res, revc); + + if(revc.child("leave_game")) { + window.set_retval(twindow::CANCEL); + } else if(config& next_scenario = revc.child("next_scenario")) { + level_.swap(next_scenario); + } else if(revc.has_attribute("version")) { + level_.swap(revc); + + has_scenario_and_controllers = true; + } else if(config& controllers = revc.child("controllers")) { + int index = 0; + for(const config& controller : controllers.child_range("controller")) { + if(config& side = get_scenario().child("side", index)) { + side["is_local"] = controller["is_local"]; + } + ++index; + } + + has_scenario_and_controllers = true; + } + + } + + if(level_["started"].to_bool()) { + window.set_retval(twindow::OK); + } + + if(first_scenario_) { + state_ = saved_game(); + state_.classification() = game_classification(level_); + + // Make sure that we have the same config as host, if possible. + game_config_manager::get()->load_game_config_for_game(state_.classification()); + } + + game_config::add_color_info(get_scenario()); + + // If we're just an observer, we don't need to find an appropriate side and set faction selection + if(observe_game_) { + return; + } + + // Search for an appropriate vacant slot. If a description is set (i.e. we're loading from a saved game), + // then prefer to get the side with the same description as our login. Otherwise just choose the first + // available side. + const config* side_choice = nullptr; + + int side_num = 0, nb_sides = 0; + for(const config& side : get_scenario().child_range("side")) { + if(side["controller"] == "reserved" && side["current_player"] == preferences::login()) { + side_choice = &side; + side_num = nb_sides; + break; + } + + if(side["controller"] == "human" && side["player_id"].empty()) { + if(!side_choice) { // Found the first empty side + side_choice = &side; + side_num = nb_sides; + } + + if(side["current_player"] == preferences::login()) { + side_choice = &side; + side_num = nb_sides; + break; // Found the preferred one + } + } + + if(side["player_id"] == preferences::login()) { + // We already own a side in this game + return; + } + + ++nb_sides; + } + + if(!side_choice) { + window.set_retval(twindow::CANCEL); + } + + const bool allow_choice = (*side_choice)["allow_changes"].to_bool(true); + + // If the client is allowed to choose their team, do that here instead of having it set by the server + if(allow_choice) { + events::event_context context; + + const config& era = level_.child("era"); + // TODO: Check whether we have the era. If we don't, inform the player + if(!era) { + throw config::error(_("No era information found.")); + } + + config::const_child_itors possible_sides = era.child_range("multiplayer_side"); + if(possible_sides.empty()) { + // TODO: is this set_retval needed? + window.set_retval(twindow::CANCEL); + + throw config::error(_("No multiplayer sides found")); + } + + const std::string color = (*side_choice)["color"].str(); + + std::vector era_factions; + for(const config& side : possible_sides) { + era_factions.push_back(&side); + } + + const bool is_mp = state_.classification().is_normal_mp_game(); + const bool lock_settings = get_scenario()["force_lock_settings"].to_bool(!is_mp); + const bool use_map_settings = level_.child("multiplayer")["mp_use_map_settings"].to_bool(); + const bool saved_game = level_.child("multiplayer")["savegame"].to_bool(); + + ng::flg_manager flg(era_factions, *side_choice, lock_settings, use_map_settings, saved_game); + + // FIXME: this dialog doesn't show! + gui2::tfaction_select dlg(flg, color, side_num); + dlg.show(window.video()); + + if(dlg.get_retval() != gui2::twindow::OK) { + window.set_retval(twindow::CANCEL); + return; + } + + config faction; + config& change = faction.add_child("change_faction"); + change["change_faction"] = true; + change["name"] = preferences::login(); + change["faction"] = flg.current_faction()["id"]; + change["leader"] = flg.current_leader(); + change["gender"] = flg.current_gender(); + + wesnothd_connection_.send_data(faction); + } +} + +static std::string generate_user_description(const config& side) +{ + // Allow the host to override, since only the host knows the ai_algorithm. + if(const config::attribute_value* desc = side.get("user_description")) { + return desc->str(); + } + + const std::string controller_type = side["controller"].str(); + const std::string reservation = side["reserved_for"].str(); + const std::string owner = side["player_id"].str(); + + if(controller_type == "ai") { + return _("Computer Player"); + } else if(controller_type == "null") { + return _("Empty slot"); + } else if(controller_type == "reserved") { + return vgettext("Reserved for $playername", {{"playername", reservation}}); + } else if(owner.empty()) { + return _("Vacant slot"); + } else if(controller_type == "human" || controller_type == "network") { + return owner; + } else { + return _("empty"); + } +} + +void tmp_join_game::pre_show(twindow& window) +{ + window.set_enter_disabled(true); + + // + // Set title + // + tlabel& title = find_widget(&window, "title", false); + title.set_label((formatter() << title.label() << " " << utils::unicode_em_dash << " " << get_scenario()["name"].t_str()).str()); + + // + // Set up sides list + // + generate_side_list(window); + + // + // Initialize chatbox and game rooms + // + tchatbox& chat = find_widget(&window, "chat", false); + + chat.set_lobby_info(lobby_info_); + chat.set_wesnothd_connection(wesnothd_connection_); + + chat.room_window_open("this game", true); // TODO: better title? + chat.active_window_changed(); + + // + // Set up player list + // + update_player_list(window); + + // + // Set up the network handling + // + update_timer_ = add_timer(game_config::lobby_network_timer, std::bind(&tmp_join_game::network_handler, this, std::ref(window)), true); + + // + // Set up the Lua plugin context + // + plugins_context_.reset(new plugins_context("Multiplayer Join Game")); + + plugins_context_->set_callback("launch", [&window](const config&) { window.set_retval(twindow::OK); }, false); + plugins_context_->set_callback("quit", [&window](const config&) { window.set_retval(twindow::CANCEL); }, false); + plugins_context_->set_callback("chat", [&chat](const config& cfg) { chat.send_chat_message(cfg["message"], false); }, true); + + // TODO: the old mp wait dialog didn't have an OK button. Evaluate if we want to add one. Hiding it for now + find_widget(&window, "ok", false).set_visible(twidget::tvisible::hidden); +} + +/* + * We don't need the full widget setup as is done initially, just value setters. + */ +void tmp_join_game::generate_side_list(twindow& window) +{ + if(stop_updates_) { + return; + } + + tlistbox& list = find_widget(&window, "side_list", false); + + window.keyboard_capture(&list); + + list.clear(); + + for(const auto& side : get_scenario().child_range("side")) { + if(!side["allow_player"].to_bool(true)) { + continue; + } + + std::map data; + string_map item; + + item["label"] = side["side"]; + data.emplace("side_number", item); + + std::string leader_image = ng::random_enemy_picture; + std::string leader_type = side["type"]; + std::string leader_name; + + const std::string leader_gender = side["gender"]; + + // If there is a unit which can recruit, use it as a leader. + // Necessary to display leader information when loading saves. + for(const config& side_unit : side.child_range("unit")) { + if(side_unit["canrecruit"].to_bool()) { + leader_type = side_unit["type"].str(); + break; + } + } + + if(const unit_type* ut = unit_types.find(leader_type)) { + const unit_type& type = ut->get_gender_unit_type(leader_gender); + + const std::string color = !side["color"].empty() ? side["color"] : side["side"].str(); + + leader_image = formatter() << type.image() << "~RC(" << type.flag_rgb() << ">" << color << ")"; + leader_name = type.type_name(); + } + + item["label"] = leader_image; + data.emplace("leader_image", item); + + std::string description = generate_user_description(side); + if(!leader_name.empty()) { + description += formatter() << " (" << leader_name << ")"; + } + + item["label"] = description; + data.emplace("leader_type", item); + + item["label"] = (formatter() << "" << side["faction_name"] << "").str(); + data.emplace("leader_faction", item); + + std::string gender_icon = "icons/icon-random.png"; + if(side["gender"] != "null") { + gender_icon = formatter() << "icons/icon-" << leader_gender << ".png"; + } + + item["label"] = gender_icon; + item["tooltip"] = side["gender"]; + data.emplace("leader_gender", item); + + item.clear(); + + // TODO: why this tstring stuff? + item["label"] = t_string::from_serialized(side["user_team_name"].str()); + data.emplace("side_team", item); + + // Don't show gold for saved games + // TODO: gold icon + if(side["allow_changes"].to_bool()) { + item["label"] = side["gold"].str() + " " + "Gold"; + data.emplace("side_gold", item); + } + + const int income_amt = side["income"]; + if(income_amt != 0) { + const std::string income_string = formatter() << (income_amt > 0 ? "+" : "") << income_amt << "Income"; + + item["label"] = income_string; + data.emplace("side_income", item); + } + + // TODO: colorize + item["label"] = side["color"]; + data.emplace("side_color", item); + + list.add_row(data); + } +} + +void tmp_join_game::update_player_list(twindow& window) +{ + tlistbox& player_list = find_widget(&window, "player_list", false); + + player_list.clear(); + + for(const auto& side : get_scenario().child_range("side")) { + std::map data; + string_map item; + + item["label"] = side["player_id"]; + data.emplace("player_name", item); + + player_list.add_row(data); + } +} + +void tmp_join_game::network_handler(twindow& window) +{ + config data; + if(!wesnothd_connection_.receive_data(data)) { + return; + } + + // Update chat + find_widget(&window, "chat", false).process_network_data(data); + + if(!data["message"].empty()) { + gui2::show_transient_message(window.video(), _("Response") , data["message"]); + } + + if(data["failed"].to_bool()) { + window.set_retval(twindow::CANCEL); + } else if(data.child("start_game")) { + window.set_retval(twindow::OK); + } else if(data.child("leave_game")) { + window.set_retval(twindow::CANCEL); + } + + if(data.child("stop_updates")) { + stop_updates_ = true; + } else if(const config& c = data.child("scenario_diff")) { + // TODO: We should catch config::error and then leave the game. + level_.apply_diff(c); + + generate_side_list(window); + } else if(const config& change = data.child("change_controller")) { + if(config& side_to_change = get_scenario().find_child("side", "side", change["side"])) { + side_to_change.merge_with(change); + } + } else if(data.has_child("scenario") || data.has_child("snapshot") || data.child("next_scenario")) { + level_ = first_scenario_ ? data : data.child("next_scenario"); + + generate_side_list(window); + } + + // Update player list + // TODO: optimally, it wouldn't regenerate the entire list every single refresh cycle + update_player_list(window); +} + +config& tmp_join_game::get_scenario() +{ + if(config& scenario = level_.child("scenario")) { + return scenario; + } else if(config& snapshot = level_.child("snapshot")) { + return snapshot; + } + + return level_; +} + +void tmp_join_game::post_show(twindow& window) +{ + if(window.get_retval() == twindow::OK) { + if(const config& stats = level_.child("statistics")) { + statistics::fresh_stats(); + statistics::read_stats(stats); + } + + mp::level_to_gamestate(level_, state_); + + mp_ui_alerts::game_has_begun(); + } else { + wesnothd_connection_.send_data(config("leave_game")); + } +} + +} // namespace gui2 diff --git a/src/gui/dialogs/multiplayer/mp_join_game.hpp b/src/gui/dialogs/multiplayer/mp_join_game.hpp new file mode 100644 index 00000000000..b396bf4a406 --- /dev/null +++ b/src/gui/dialogs/multiplayer/mp_join_game.hpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2008 - 2016 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. +*/ + +#ifndef GUI_DIALOGS_MP_JOIN_GAME_HPP_INCLUDED +#define GUI_DIALOGS_MP_JOIN_GAME_HPP_INCLUDED + +#include "ai/configuration.hpp" +#include "gui/dialogs/dialog.hpp" +#include "gui/dialogs/lobby/info.hpp" +#include "gui/dialogs/multiplayer/plugin_executor.hpp" + +#include "game_initialization/connect_engine.hpp" +#include "game_initialization/multiplayer.hpp" +#include "mp_game_settings.hpp" + +class config; + +namespace gui2 +{ + +class ttoggle_button; +class ttoggle_panel; +class tslider; +class tlabel; +class tmenu_button; +class twidget; + +class tmp_join_game : public tdialog, private plugin_executor +{ +public: + tmp_join_game(saved_game& state, lobby_info& lobby_info, twesnothd_connection& wesnothd_connection, + const bool first_scenario = true, const bool observe_game = false); + + ~tmp_join_game(); + +private: + /** Inherited from tdialog, implemented by REGISTER_DIALOG. */ + virtual const std::string& window_id() const; + + /** Inherited from tdialog. */ + void post_build(twindow& window); + + /** Inherited from tdialog. */ + void pre_show(twindow& window); + + /** Inherited from tdialog. */ + void post_show(twindow& window); + + void generate_side_list(twindow& window); + + void update_player_list(twindow& window); + + void update_leader_display(ng::side_engine& side, tgrid& row_grid); + + void network_handler(twindow& window); + + config& get_scenario(); + + config level_; + + saved_game& state_; + + lobby_info& lobby_info_; + + twesnothd_connection& wesnothd_connection_; + + size_t update_timer_; + + const bool first_scenario_; + const bool observe_game_; + + bool stop_updates_; +}; + +} // namespace gui2 + +#endif diff --git a/src/gui/dialogs/multiplayer/mp_staging.cpp b/src/gui/dialogs/multiplayer/mp_staging.cpp index 55b284f3444..2a13b429716 100644 --- a/src/gui/dialogs/multiplayer/mp_staging.cpp +++ b/src/gui/dialogs/multiplayer/mp_staging.cpp @@ -468,8 +468,7 @@ void tmp_staging::post_show(twindow& window) { if(window.get_retval() == twindow::OK) { connect_engine_.start_game(); - } - else { + } else { connect_engine_.leave_game(); } } diff --git a/src/tests/gui/test_gui2.cpp b/src/tests/gui/test_gui2.cpp index ac82528711a..477c6918a81 100644 --- a/src/tests/gui/test_gui2.cpp +++ b/src/tests/gui/test_gui2.cpp @@ -75,6 +75,7 @@ #include "gui/dialogs/multiplayer/mp_connect.hpp" #include "gui/dialogs/multiplayer/mp_create_game.hpp" #include "gui/dialogs/multiplayer/mp_create_game_set_password.hpp" +#include "gui/dialogs/multiplayer/mp_join_game.hpp" #include "gui/dialogs/multiplayer/mp_join_game_password_prompt.hpp" #include "gui/dialogs/multiplayer/mp_staging.hpp" #include "gui/dialogs/depcheck_confirm_change.hpp" @@ -410,6 +411,7 @@ BOOST_AUTO_TEST_CASE(test_gui2) test(); //test(); test(); + test(); test(); test(); test(); @@ -486,6 +488,7 @@ BOOST_AUTO_TEST_CASE(test_gui2) "title_screen", "end_credits", "mp_staging", + "mp_join_game", }; std::sort(list.begin(), list.end()); std::sort(omitted.begin(), omitted.end());