multiplayer.cpp

Go to the documentation of this file.
00001 /* $Id: multiplayer.cpp 25333 2008-03-30 13:49:03Z jhinrichs $ */
00002 /*
00003    Copyright (C) 2007 - 2008
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 #include "global.hpp"
00016 
00017 #include "construct_dialog.hpp"
00018 #include "dialogs.hpp"
00019 #include "game_config.hpp"
00020 #include "gettext.hpp"
00021 #include "log.hpp"
00022 #include "multiplayer.hpp"
00023 #include "multiplayer_ui.hpp"
00024 #include "multiplayer_connect.hpp"
00025 #include "multiplayer_wait.hpp"
00026 #include "multiplayer_lobby.hpp"
00027 #include "multiplayer_create.hpp"
00028 #include "playmp_controller.hpp"
00029 #include "network.hpp"
00030 #include "playcampaign.hpp"
00031 #include "game_preferences.hpp"
00032 #include "preferences_display.hpp"
00033 #include "random.hpp"
00034 #include "replay.hpp"
00035 #include "video.hpp"
00036 #include "statistics.hpp"
00037 #include "serialization/string_utils.hpp"
00038 #include "upload_log.hpp"
00039 #include "wml_separators.hpp"
00040 
00041 #define LOG_NW LOG_STREAM(info, network)
00042 
00043 namespace {
00044 
00045 class network_game_manager
00046 {
00047 public:
00048     // Add a constructor to avoid stupid warnings with some versions of GCC
00049     network_game_manager() {};
00050 
00051     ~network_game_manager()
00052     {
00053         if(network::nconnections() > 0) {
00054             LOG_NW << "sending leave_game\n";
00055             config cfg;
00056             cfg.add_child("leave_game");
00057             network::send_data(cfg, 0, true);
00058             LOG_NW << "sent leave_game\n";
00059         }
00060     };
00061 };
00062 
00063 }
00064 
00065 static void run_lobby_loop(display& disp, mp::ui& ui)
00066 {
00067     disp.video().modeChanged();
00068     bool first = true;
00069     font::cache_mode(font::CACHE_LOBBY);
00070     while (ui.get_result() == mp::ui::CONTINUE) {
00071         if (disp.video().modeChanged() || first) {
00072             SDL_Rect lobby_pos = { 0, 0, disp.video().getx(), disp.video().gety() };
00073             ui.set_location(lobby_pos);
00074             first = false;
00075         }
00076 
00077         events::raise_process_event();
00078         events::raise_draw_event();
00079         events::pump();
00080 
00081         ui.process_network();
00082 
00083         disp.flip();
00084         disp.delay(20);
00085     }
00086     font::cache_mode(font::CACHE_GAME);
00087 }
00088 
00089 namespace {
00090 
00091 enum server_type {
00092     ABORT_SERVER,
00093     WESNOTHD_SERVER,
00094     SIMPLE_SERVER
00095 };
00096 
00097 class server_button : public gui::dialog_button
00098 {
00099 public:
00100     server_button(CVideo &vid): dialog_button(vid, _("View List"))
00101     {}
00102 protected:
00103     int action(gui::dialog_process_info &dp_info)
00104     {
00105         //display a dialog with a list of known servers
00106         gui::dialog server_dialog(dialog()->get_display(), _("List of Servers"),
00107             _("Choose a known server from the list"), gui::OK_CANCEL);
00108         std::vector<std::string> servers;
00109         std::ostringstream menu_heading;
00110         menu_heading << HEADING_PREFIX << _("Name") << COLUMN_SEPARATOR << _("Address");
00111         servers.push_back(menu_heading.str());
00112         const std::vector<game_config::server_info>& pref_servers = preferences::server_list();
00113         std::vector<game_config::server_info>::const_iterator server;
00114         for(server = pref_servers.begin(); server != pref_servers.end(); ++server) {
00115             servers.push_back(server->name + COLUMN_SEPARATOR + server->address);
00116         }
00117         server_dialog.set_menu(servers);
00118         gui::menu::basic_sorter server_sorter;
00119         server_sorter.set_alpha_sort(0).set_id_sort(1);
00120         server_dialog.get_menu().set_sorter(&server_sorter);
00121         if(server_dialog.show() >= 0) {
00122             //now save the result back to the parent dialog
00123             dialog()->get_textbox().set_text(preferences::server_list()[server_dialog.result()].address);
00124         }
00125         //the button state should be cleared after popping up an intermediate dialog
00126         dp_info.clear_buttons();
00127         return gui::CONTINUE_DIALOG;
00128     }
00129 };
00130 }
00131 
00132 static server_type open_connection(game_display& disp, const std::string& original_host)
00133 {
00134     std::string h = original_host;
00135 
00136     if(h.empty()) {
00137         gui::dialog d(disp, _("Connect to Host"), "", gui::OK_CANCEL);
00138         d.set_textbox(_("Choose host to connect to: "), preferences::network_host());
00139         d.add_button( new server_button(disp.video()), gui::dialog::BUTTON_EXTRA);
00140         if(d.show() || d.textbox_text().empty()) {
00141             return ABORT_SERVER;
00142         }
00143         h = d.textbox_text();
00144     }
00145 
00146     network::connection sock;
00147 
00148     const int pos = h.find_first_of(":");
00149     std::string host;
00150     unsigned int port;
00151 
00152     if(pos == -1) {
00153         host = h;
00154         port = 15000;
00155     } else {
00156         host = h.substr(0, pos);
00157         port = lexical_cast_default<unsigned int>(h.substr(pos + 1), 15000);
00158     }
00159 
00160     // shown_hosts is used to prevent the client being locked in a redirect
00161     // loop.
00162     typedef std::pair<std::string, int> hostpair;
00163     std::set<hostpair> shown_hosts;
00164     shown_hosts.insert(hostpair(host, port));
00165 
00166     config::child_list redirects;
00167     config data;
00168     sock = dialogs::network_connect_dialog(disp,_("Connecting to Server..."),host,port);
00169 
00170     do {
00171 
00172         if (!sock) {
00173             return ABORT_SERVER;
00174         }
00175 
00176         data.clear();
00177         network::connection data_res = dialogs::network_receive_dialog(
00178                 disp,_("Reading from Server..."),data);
00179         if (!data_res) return ABORT_SERVER;
00180         mp::check_response(data_res, data);
00181 
00182         // Backwards-compatibility "version" attribute
00183         const std::string& version = data["version"];
00184         if(version.empty() == false && version != game_config::version) {
00185             utils::string_map i18n_symbols;
00186             i18n_symbols["version1"] = version;
00187             i18n_symbols["version2"] = game_config::version;
00188             const std::string errorstring = vgettext("The server requires version '$version1' while you are using version '$version2'", i18n_symbols);
00189             throw network::error(errorstring);
00190         }
00191 
00192         // Check for "redirect" messages
00193         if(data.child("redirect")) {
00194             config* redirect = data.child("redirect");
00195 
00196             host = (*redirect)["host"];
00197             port = lexical_cast_default<unsigned int>((*redirect)["port"], 15000);
00198 
00199             if(shown_hosts.find(hostpair(host,port)) != shown_hosts.end()) {
00200                 throw network::error(_("Server-side redirect loop"));
00201             }
00202             shown_hosts.insert(hostpair(host, port));
00203 
00204             if(network::nconnections() > 0)
00205                 network::disconnect();
00206             sock = dialogs::network_connect_dialog(disp,_("Connecting to Server..."),host,port);
00207             continue;
00208         }
00209 
00210         if(data.child("version")) {
00211             config cfg;
00212             config res;
00213             cfg["version"] = game_config::version;
00214             res.add_child("version", cfg);
00215             network::send_data(res, 0, true);
00216         }
00217 
00218         //if we got a direction to login
00219         if(data.child("mustlogin")) {
00220             bool first_time = true;
00221             config* error = NULL;
00222 
00223             do {
00224                 if(error != NULL) {
00225                     gui::dialog(disp,"",(*error)["message"],gui::OK_ONLY).show();
00226                 }
00227 
00228                 std::string login = preferences::login();
00229 
00230                 if(!first_time) {
00231                     const int res = gui::show_dialog(disp, NULL, "",
00232                             _("You must log in to this server"), gui::OK_CANCEL,
00233                             NULL, NULL, _("Login: "), &login, mp::max_login_size);
00234                     if(res != 0 || login.empty()) {
00235                         return ABORT_SERVER;
00236                     }
00237                     preferences::set_login(login);
00238                 }
00239 
00240                 first_time = false;
00241 
00242                 config response;
00243                 response.add_child("login")["username"] = login;
00244                 network::send_data(response, 0, true);
00245 
00246                 network::connection data_res = network::receive_data(data, 0, 3000);
00247                 if(!data_res) {
00248                     throw network::error(_("Connection timed out"));
00249                 }
00250 
00251                 error = data.child("error");
00252             } while(error != NULL);
00253         }
00254     } while(!(data.child("join_lobby") || data.child("join_game")));
00255 
00256     if (h != preferences::server_list().front().address)
00257         preferences::set_network_host(h);
00258 
00259     if (data.child("join_lobby")) {
00260         return WESNOTHD_SERVER;
00261     } else {
00262         return SIMPLE_SERVER;
00263     }
00264 
00265 }
00266 
00267 
00268 // The multiplayer logic consists in 4 screens:
00269 //
00270 // lobby <-> create <-> connect <-> (game)
00271 //       <------------> wait    <-> (game)
00272 //
00273 // To each of this screen corresponds a dialog, each dialog being defined in
00274 // the multiplayer_(screen) file.
00275 //
00276 // The functions enter_(screen)_mode are simple functions that take care of
00277 // creating the dialogs, then, according to the dialog result, of calling other
00278 // of those screen functions.
00279 
00280 static void enter_wait_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist, bool observe)
00281 {
00282     mp::ui::result res;
00283     game_state state;
00284     network_game_manager m;
00285     upload_log nolog(false);
00286 
00287     gamelist.clear();
00288     statistics::fresh_stats();
00289 
00290     {
00291         mp::wait ui(disp, game_config, chat, gamelist);
00292 
00293         ui.join_game(observe);
00294 
00295         run_lobby_loop(disp, ui);
00296         res = ui.get_result();
00297 
00298         if (res == mp::ui::PLAY) {
00299             ui.start_game();
00300             // FIXME commented a pointeless if since the else does exactly the same thing
00301             //if (preferences::skip_mp_replay()){
00302                 //FIXME implement true skip replay
00303                 //state = ui.request_snapshot();
00304                 //state = ui.get_state();
00305             //}
00306             //else{
00307                 state = ui.get_state();
00308             //}
00309         }
00310     }
00311 
00312     switch (res) {
00313     case mp::ui::PLAY:
00314         play_game(disp, state, game_config, nolog, IO_CLIENT,
00315             preferences::skip_mp_replay() && observe);
00316         recorder.clear();
00317 
00318         break;
00319     case mp::ui::QUIT:
00320     default:
00321         break;
00322     }
00323 }
00324 
00325 static void enter_create_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist, mp::controller default_controller, bool is_server);
00326 
00327 static void enter_connect_mode(game_display& disp, const config& game_config,
00328         mp::chat& chat, config& gamelist, const mp::create::parameters& params,
00329         mp::controller default_controller, bool is_server)
00330 {
00331     mp::ui::result res;
00332     game_state state;
00333     const network::manager net_manager(1,1);
00334     const network::server_manager serv_manager(15000, is_server ?
00335             network::server_manager::TRY_CREATE_SERVER :
00336             network::server_manager::NO_SERVER);
00337     network_game_manager m;
00338     upload_log nolog(false);
00339 
00340     gamelist.clear();
00341     statistics::fresh_stats();
00342 
00343     {
00344         mp::connect ui(disp, game_config, chat, gamelist, params, default_controller);
00345         run_lobby_loop(disp, ui);
00346 
00347         res = ui.get_result();
00348 
00349         // start_game() updates the parameters to reflect game start,
00350         // so it must be called before get_level()
00351         if (res == mp::ui::PLAY) {
00352             ui.start_game();
00353             state = ui.get_state();
00354         }
00355     }
00356 
00357     switch (res) {
00358     case mp::ui::PLAY:
00359         play_game(disp, state, game_config, nolog, IO_SERVER);
00360         recorder.clear();
00361 
00362         break;
00363     case mp::ui::CREATE:
00364         enter_create_mode(disp, game_config, chat, gamelist, default_controller, is_server);
00365         break;
00366     case mp::ui::QUIT:
00367     default:
00368         network::send_data(config("refresh_lobby"), 0, true);
00369         break;
00370     }
00371 }
00372 
00373 static void enter_create_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist, mp::controller default_controller, bool is_server)
00374 {
00375     mp::ui::result res;
00376     mp::create::parameters params;
00377 
00378     {
00379         mp::create ui(disp, game_config, chat, gamelist);
00380         run_lobby_loop(disp, ui);
00381         res = ui.get_result();
00382         params = ui.get_parameters();
00383     }
00384 
00385     switch (res) {
00386     case mp::ui::CREATE:
00387         enter_connect_mode(disp, game_config, chat, gamelist, params, default_controller, is_server);
00388         break;
00389     case mp::ui::QUIT:
00390     default:
00391         //update lobby content
00392         network::send_data(config("refresh_lobby"), 0, true);
00393         break;
00394     }
00395 }
00396 
00397 static void enter_lobby_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist)
00398 {
00399     mp::ui::result res;
00400 
00401     while (true) {
00402         {
00403             mp::lobby ui(disp, game_config, chat, gamelist);
00404             run_lobby_loop(disp, ui);
00405             res = ui.get_result();
00406         }
00407 
00408         switch (res) {
00409         case mp::ui::JOIN:
00410             try {
00411                 enter_wait_mode(disp, game_config, chat, gamelist, false);
00412             } catch(config::error& error) {
00413                 if(!error.message.empty()) {
00414                     gui::show_error_message(disp, error.message);
00415                 }
00416                 //update lobby content
00417                 network::send_data(config("refresh_lobby"), 0, true);
00418             }
00419             break;
00420         case mp::ui::OBSERVE:
00421             try {
00422                 enter_wait_mode(disp, game_config, chat, gamelist, true);
00423             } catch(config::error& error) {
00424                 if(!error.message.empty()) {
00425                     gui::show_error_message(disp, error.message);
00426                 }
00427             }
00428             //update lobby content
00429             network::send_data(config("refresh_lobby"), 0, true);
00430             break;
00431         case mp::ui::CREATE:
00432             try {
00433                 enter_create_mode(disp, game_config, chat, gamelist, mp::CNTR_NETWORK, false);
00434             } catch(config::error& error) {
00435                 if (!error.message.empty())
00436                     gui::show_error_message(disp, error.message);
00437                 //update lobby content
00438                 network::send_data(config("refresh_lobby"), 0, true);
00439             }
00440             break;
00441         case mp::ui::QUIT:
00442             return;
00443         case mp::ui::PREFERENCES:
00444             {
00445                 const preferences::display_manager disp_manager(&disp);
00446                 preferences::show_preferences_dialog(disp,game_config);
00447                 //update lobby content
00448                 network::send_data(config("refresh_lobby"), 0, true);
00449             }
00450             break;
00451         default:
00452             return;
00453         }
00454     }
00455 }
00456 
00457 namespace mp {
00458 
00459 void start_server(game_display& disp, const config& game_config,
00460         mp::controller default_controller, bool is_server)
00461 {
00462     const set_random_generator generator_setter(&recorder);
00463     mp::chat chat;
00464     config gamelist;
00465     playmp_controller::set_replay_last_turn(0);
00466     preferences::set_message_private(false);
00467     enter_create_mode(disp, game_config, chat, gamelist, default_controller, is_server);
00468 }
00469 
00470 void start_client(game_display& disp, const config& game_config,
00471         const std::string host)
00472 {
00473     const set_random_generator generator_setter(&recorder);
00474     const network::manager net_manager(1,1);
00475 
00476     mp::chat chat;
00477     config gamelist;
00478     server_type type = open_connection(disp, host);
00479 
00480     switch(type) {
00481     case WESNOTHD_SERVER:
00482         enter_lobby_mode(disp, game_config, chat, gamelist);
00483         break;
00484     case SIMPLE_SERVER:
00485         playmp_controller::set_replay_last_turn(0);
00486         preferences::set_message_private(false);
00487         enter_wait_mode(disp, game_config, chat, gamelist, false);
00488         break;
00489     case ABORT_SERVER:
00490         break;
00491     }
00492 }
00493 
00494 }
00495 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs