MP Staging: some progress with getting the network processing working, as well as using the dialog for non-hosts

This commit is contained in:
Charles Dang 2016-09-29 13:37:22 +11:00
parent 4488e35706
commit 75636ee17c
4 changed files with 331 additions and 45 deletions

View file

@ -430,7 +430,6 @@
id = "player_list"
definition = "default"
vertical_scrollbar_mode = "always"
horizontal_scrollbar_mode = "never"
[list_definition]
@ -493,7 +492,7 @@
[button]
id = "ok"
definition = "default"
definition = "large"
label = _ "Im Ready"
[/button]
[/column]
@ -504,7 +503,7 @@
[button]
id = "cancel"
definition = "default"
definition = "large"
label = _ "Cancel"
[/button]
[/column]
@ -616,7 +615,23 @@
[/row]
{GUI_HORIZONTAL_SPACER_LINE}
[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

View file

@ -406,8 +406,18 @@ static std::unique_ptr<twesnothd_connection> open_connection(CVideo& video, cons
// 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,
bool observe, int current_turn = 0)
lobby_info& li, bool observe, int current_turn = 0)
{
DBG_MP << "entering wait mode" << std::endl;
@ -415,21 +425,163 @@ static void enter_wait_mode(CVideo& video, const config& game_config, saved_game
gamelist.clear();
statistics::fresh_stats();
mp_campaign_info campaign_info(*wesnothd_connection);
campaign_info.is_host = false;
std::unique_ptr<mp_campaign_info> campaign_info;
campaign_info.reset(new mp_campaign_info(*wesnothd_connection));
campaign_info->is_host = false;
if(preferences::skip_mp_replay() || preferences::blindfold_replay()) {
campaign_info.skip_replay_until_turn = current_turn;
campaign_info.skip_replay_blindfolded = preferences::blindfold_replay();
campaign_info->skip_replay_until_turn = current_turn;
campaign_info->skip_replay_blindfolded = preferences::blindfold_replay();
}
{
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);
dlg.show(video);
if(dlg.get_retval() == gui2::twindow::OK) {
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
controller.set_mp_info(campaign_info.get());
controller.play_game();
}
//if(wesnothd_connection) {
// wesnothd_connection->send_data(config("leave_game"));
//}
return;
}
mp::wait ui(video, wesnothd_connection, game_config, state, gamechat, gamelist);
ui.join_game(observe);
run_lobby_loop(video, ui);
res = ui.get_result();
campaign_info.connected_players.insert(ui.user_list().begin(), ui.user_list().end());
campaign_info->connected_players.insert(ui.user_list().begin(), ui.user_list().end());
if (res == mp::ui::PLAY) {
ui.start_game();
@ -441,7 +593,7 @@ static void enter_wait_mode(CVideo& video, const config& game_config, saved_game
switch (res) {
case mp::ui::PLAY: {
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
controller.set_mp_info(&campaign_info);
controller.set_mp_info(campaign_info.get());
controller.play_game();
break;
}
@ -476,21 +628,22 @@ static bool enter_connect_mode(CVideo& video, const config& game_config,
{
ng::connect_engine_ptr connect_engine(new ng::connect_engine(state, true, campaign_info.get()));
if(preferences::new_lobby()) {
gui2::tmp_staging dlg(game_config, *connect_engine, li);
dlg.show(video);
if(preferences::new_lobby()) {
gui2::tmp_staging dlg(*connect_engine, li, wesnothd_connection);
dlg.show(video);
if(dlg.get_retval() == gui2::twindow::OK) {
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
controller.set_mp_info(campaign_info.get());
controller.play_game();
if(wesnothd_connection) {
wesnothd_connection->send_data(config("leave_game"));
}
}
if(dlg.get_retval() == gui2::twindow::OK) {
campaign_controller controller(video, state, game_config, game_config_manager::get()->terrain_types());
controller.set_mp_info(campaign_info.get());
controller.play_game();
}
return true;
}
if(wesnothd_connection) {
wesnothd_connection->send_data(config("leave_game"));
}
return true;
}
mp::connect ui(video, wesnothd_connection, state.mp_settings().name, game_config, gamechat, gamelist,
*connect_engine);
@ -714,7 +867,7 @@ static void enter_lobby_mode(CVideo& video, const config& game_config,
case mp::ui::JOIN:
case mp::ui::OBSERVE:
try {
enter_wait_mode(video, game_config, state, wesnothd_connection, res == mp::ui::OBSERVE, current_turn);
enter_wait_mode(video, game_config, state, wesnothd_connection, li, res == mp::ui::OBSERVE, current_turn);
} catch(config::error& error) {
if(!error.message.empty()) {
gui2::show_error_message(video, error.message);

View file

@ -16,7 +16,6 @@
#include "gui/dialogs/multiplayer/mp_staging.hpp"
#include "config_assign.hpp"
#include "game_preferences.hpp"
#include "gettext.hpp"
#include "gui/auxiliary/field.hpp"
#include "gui/dialogs/helper.hpp"
@ -43,28 +42,39 @@
#include "gui/widgets/toggle_panel.hpp"
#include "gui/widgets/text_box.hpp"
#include "game_config.hpp"
#include "savegame.hpp"
#include "mp_ui_alerts.hpp"
#include "settings.hpp"
#include "units/types.hpp"
#include "formatter.hpp"
#include "wesnothd_connection.hpp"
#ifdef GUI2_EXPERIMENTAL_LISTBOX
#include "utils/functional.hpp"
#endif
#include <boost/algorithm/string.hpp>
namespace gui2
{
REGISTER_DIALOG(mp_staging)
tmp_staging::tmp_staging(const config& /*cfg*/, ng::connect_engine& connect_engine, lobby_info& lobby_info)
tmp_staging::tmp_staging(ng::connect_engine& connect_engine, lobby_info& lobby_info, twesnothd_connection* wesnothd_connection)
: connect_engine_(connect_engine)
, ai_algorithms_(ai::configuration::get_available_ais())
, lobby_info_(lobby_info)
, wesnothd_connection_(wesnothd_connection)
, update_timer_(0)
{
set_show_even_without_video(true);
assert(!ai_algorithms_.empty());
}
tmp_staging::~tmp_staging()
{
if(update_timer_ != 0) {
remove_timer(update_timer_);
update_timer_ = 0;
}
}
void tmp_staging::pre_show(twindow& window)
@ -123,8 +133,6 @@ void tmp_staging::pre_show(twindow& window)
//
// AI Algorithm
//
assert(!ai_algorithms_.empty());
int selection = 0;
// We use an index-based loop in order to get the index of the selected option
@ -155,7 +163,7 @@ void tmp_staging::pre_show(twindow& window)
tmenu_button& controller_selection = find_widget<tmenu_button>(&row_grid, "controller", false);
controller_selection.set_values(controller_names, side.current_controller_index());
controller_selection.set_active(side.controller_options().size() > 1);
controller_selection.set_active(controller_names.size() > 1);
controller_selection.connect_click_handler(std::bind(&tmp_staging::on_controller_select, this, std::ref(side), std::ref(row_grid)));
on_controller_select(side, row_grid);
@ -181,7 +189,7 @@ void tmp_staging::pre_show(twindow& window)
// As such, the index is off if there is only 1 playable team. This is a hack to make sure the menu_button
// widget doesn't assert with the invalid initial selection. The connect_engine should be fixed once the GUI1
// dialog is dropped
team_selection.set_values(team_names, std::min(static_cast<int>(team_names.size() - 1), side.team()));
team_selection.set_values(team_names, std::min<int>(team_names.size() - 1, side.team()));
team_selection.set_active(!saved_game);
team_selection.connect_click_handler(std::bind(&tmp_staging::on_team_select, this, std::ref(side), std::ref(team_selection)));
@ -244,14 +252,73 @@ void tmp_staging::pre_show(twindow& window)
tchatbox& chat = find_widget<tchatbox>(&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_staging::network_handler, this, std::ref(window)), true);
network_handler(window);
//
// Set up the Lua plugin context
//
plugins_context_.reset(new plugins_context("Multiplayer Staging"));
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);
}
/*
* We don't need the full widget setup as is done initially, just value setters.
*/
void tmp_staging::update_side_ui(twindow& window, const int i)
{
tgrid& row_grid = *find_widget<tlistbox>(&window, "side_list", false).get_row_grid(i);
ng::side_engine& side = *connect_engine_.side_engines()[i].get();
const int ai_i = std::find_if(ai_algorithms_.begin(), ai_algorithms_.end(), [&side](ai::description* a) {
return a->id == side.ai_algorithm();
}) - ai_algorithms_.begin();
find_widget<tmenu_button>(&row_grid, "ai_controller", false).set_selected(ai_i);
std::vector<config> controller_names;
for(const auto& controller : side.controller_options()) {
controller_names.push_back(config_of("label", controller.second));
}
tmenu_button& controller_selection = find_widget<tmenu_button>(&row_grid, "controller", false);
controller_selection.set_values(controller_names, side.current_controller_index());
controller_selection.set_active(controller_names.size() > 1);
update_leader_display(side, row_grid);
find_widget<tmenu_button>(&row_grid, "side_team", false).set_selected(side.team());
find_widget<tmenu_button>(&row_grid, "side_color", false).set_selected(side.color());
find_widget<tslider>(&row_grid, "side_gold_slider", false).set_value(side.cfg()["gold"].to_int(100));
find_widget<tslider>(&row_grid, "side_income_slider", false).set_value(side.cfg()["income"]);
}
void tmp_staging::update_player_list(twindow& window)
{
tlistbox& player_list = find_widget<tlistbox>(&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;
@ -261,15 +328,6 @@ void tmp_staging::pre_show(twindow& window)
player_list.add_row(data);
}
//
// Set up the Lua plugin context
//
plugins_context_.reset(new plugins_context("Multiplayer Staging"));
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);
}
void tmp_staging::sync_changes()
@ -354,7 +412,55 @@ void tmp_staging::update_leader_display(ng::side_engine& side, tgrid& row_grid)
// Gender
if(current_gender != utils::unicode_em_dash) {
const std::string gender_icon = formatter() << "icons/icon-" << current_gender << ".png";
find_widget<timage>(&row_grid, "leader_gender", false).set_label(gender_icon);
timage& icon = find_widget<timage>(&row_grid, "leader_gender", false);
icon.set_label(gender_icon);
icon.set_tooltip(current_gender);
}
}
void tmp_staging::network_handler(twindow& window)
{
config data;
if(!wesnothd_connection_ || !wesnothd_connection_->receive_data(data)) {
return;
}
// Update chat
find_widget<tchatbox>(&window, "chat", false).process_network_data(data);
// TODO: why is this needed...
const bool was_able_to_start = connect_engine_.can_start_game();
bool quit_signal_recieved;
std::tie(quit_signal_recieved, std::ignore) = connect_engine_.process_network_data(data);
if(quit_signal_recieved) {
window.set_retval(twindow::CANCEL);
}
// Update sides
for(unsigned i = 0; i < connect_engine_.side_engines().size(); i++) {
update_side_ui(window, i);
}
// Update player list
// TODO: optimally, it wouldn't regenerate the entire list every single refresh cycle
update_player_list(window);
// Update status label and buttons
// T O D O F I X T H I S S H I T
find_widget<tlabel>(&window, "status_label", false).set_label(
connect_engine_.can_start_game() ? "" : connect_engine_.sides_available()
? _("Waiting for players to join...")
: _("Waiting for players to choose factions...")
);
find_widget<tbutton>(&window, "ok", false).set_active(connect_engine_.can_start_game());
if(!was_able_to_start && connect_engine_.can_start_game()) {
mp_ui_alerts::ready_for_start();
}
}

View file

@ -38,7 +38,9 @@ class twidget;
class tmp_staging : public tdialog, private plugin_executor
{
public:
tmp_staging(const config& cfg, ng::connect_engine& connect_engine, lobby_info& lobby_info);
tmp_staging(ng::connect_engine& connect_engine, lobby_info& lobby_info, twesnothd_connection* wesnothd_connection = nullptr);
~tmp_staging();
private:
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
@ -50,6 +52,10 @@ private:
/** Inherited from tdialog. */
void post_show(twindow& window);
void update_side_ui(twindow& window, const int i);
void update_player_list(twindow& window);
void sync_changes();
void on_controller_select(ng::side_engine& side, tgrid& row_grid);
@ -61,11 +67,17 @@ private:
void update_leader_display(ng::side_engine& side, tgrid& row_grid);
void network_handler(twindow& window);
ng::connect_engine& connect_engine_;
std::vector<ai::description*> ai_algorithms_;
lobby_info& lobby_info_;
twesnothd_connection* wesnothd_connection_;
size_t update_timer_;
};
} // namespace gui2