00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016 #include "global.hpp"
00017
00018 #include "dialogs.hpp"
00019 #include "gettext.hpp"
00020 #include "game_preferences.hpp"
00021 #include "log.hpp"
00022 #include "gui/dialogs/lobby_main.hpp"
00023 #include "gui/dialogs/message.hpp"
00024 #include "gui/dialogs/mp_connect.hpp"
00025 #include "gui/dialogs/mp_create_game.hpp"
00026 #include "gui/dialogs/mp_login.hpp"
00027 #include "gui/widgets/settings.hpp"
00028 #include "gui/widgets/window.hpp"
00029 #include "hash.hpp"
00030 #include "multiplayer.hpp"
00031 #include "multiplayer_connect.hpp"
00032 #include "multiplayer_create.hpp"
00033 #include "multiplayer_error_codes.hpp"
00034 #include "multiplayer_wait.hpp"
00035 #include "multiplayer_lobby.hpp"
00036 #include "playmp_controller.hpp"
00037 #include "playcampaign.hpp"
00038 #include "formula_string_utils.hpp"
00039 #include "sound.hpp"
00040 #include "unit_id.hpp"
00041
00042 #include <boost/bind.hpp>
00043
00044 static lg::log_domain log_network("network");
00045 #define LOG_NW LOG_STREAM(info, log_network)
00046
00047 static lg::log_domain log_mp("mp/main");
00048 #define DBG_MP LOG_STREAM(debug, log_mp)
00049
00050 namespace {
00051
00052 class network_game_manager
00053 {
00054 public:
00055
00056 network_game_manager() {};
00057
00058 ~network_game_manager()
00059 {
00060 if(network::nconnections() > 0) {
00061 LOG_NW << "sending leave_game\n";
00062 config cfg;
00063 cfg.add_child("leave_game");
00064 network::send_data(cfg, 0);
00065 LOG_NW << "sent leave_game\n";
00066 }
00067 };
00068 };
00069
00070 }
00071
00072 static void run_lobby_loop(display& disp, mp::ui& ui)
00073 {
00074 DBG_MP << "running lobby loop" << std::endl;
00075 disp.video().modeChanged();
00076 bool first = true;
00077 font::cache_mode(font::CACHE_LOBBY);
00078 while (ui.get_result() == mp::ui::CONTINUE) {
00079 if (disp.video().modeChanged() || first) {
00080 SDL_Rect lobby_pos = create_rect(0
00081 , 0
00082 , disp.video().getx()
00083 , disp.video().gety());
00084 ui.set_location(lobby_pos);
00085 first = false;
00086 }
00087
00088
00089
00090 ui.process_network();
00091
00092 events::pump();
00093 events::raise_process_event();
00094 events::raise_draw_event();
00095
00096 disp.flip();
00097 disp.delay(20);
00098 }
00099 font::cache_mode(font::CACHE_GAME);
00100 }
00101
00102 namespace {
00103
00104 enum server_type {
00105 ABORT_SERVER,
00106 WESNOTHD_SERVER,
00107 SIMPLE_SERVER
00108 };
00109
00110 }
00111
00112 static server_type open_connection(game_display& disp, const std::string& original_host)
00113 {
00114 DBG_MP << "opening connection" << std::endl;
00115 std::string h = original_host;
00116
00117 if(h.empty()) {
00118 gui2::tmp_connect dlg;
00119
00120 dlg.show(disp.video());
00121 if(dlg.get_retval() == gui2::twindow::OK) {
00122 h = preferences::network_host();
00123 } else {
00124 return ABORT_SERVER;
00125 }
00126 }
00127
00128 network::connection sock;
00129
00130 const int pos = h.find_first_of(":");
00131 std::string host;
00132 unsigned int port;
00133
00134 if(pos == -1) {
00135 host = h;
00136 port = 15000;
00137 } else {
00138 host = h.substr(0, pos);
00139 port = lexical_cast_default<unsigned int>(h.substr(pos + 1), 15000);
00140 }
00141
00142
00143
00144 typedef std::pair<std::string, int> hostpair;
00145 std::set<hostpair> shown_hosts;
00146 shown_hosts.insert(hostpair(host, port));
00147
00148 config data;
00149 sock = dialogs::network_connect_dialog(disp,_("Connecting to Server..."),host,port);
00150
00151 do {
00152
00153 if (!sock) {
00154 return ABORT_SERVER;
00155 }
00156
00157 data.clear();
00158 network::connection data_res = dialogs::network_receive_dialog(
00159 disp,_("Reading from Server..."),data);
00160 if (!data_res) return ABORT_SERVER;
00161 mp::check_response(data_res, data);
00162
00163
00164 const std::string& version = data["version"];
00165 if(version.empty() == false && version != game_config::version) {
00166 utils::string_map i18n_symbols;
00167 i18n_symbols["version1"] = version;
00168 i18n_symbols["version2"] = game_config::version;
00169 const std::string errorstring = vgettext("The server requires version '$version1' while you are using version '$version2'", i18n_symbols);
00170 throw network::error(errorstring);
00171 }
00172
00173
00174 if (const config &redirect = data.child("redirect"))
00175 {
00176 host = redirect["host"].str();
00177 port =redirect["port"].to_int(15000);
00178
00179 if(shown_hosts.find(hostpair(host,port)) != shown_hosts.end()) {
00180 throw network::error(_("Server-side redirect loop"));
00181 }
00182 shown_hosts.insert(hostpair(host, port));
00183
00184 if(network::nconnections() > 0)
00185 network::disconnect();
00186 sock = dialogs::network_connect_dialog(disp,_("Connecting to Server..."),host,port);
00187 continue;
00188 }
00189
00190 if(data.child("version")) {
00191 config cfg;
00192 config res;
00193 cfg["version"] = game_config::version;
00194 res.add_child("version", cfg);
00195 network::send_data(res, 0);
00196 }
00197
00198
00199 if(data.child("mustlogin")) {
00200
00201 for(;;) {
00202 std::string password_reminder = "";
00203
00204 std::string login = preferences::login();
00205
00206 config response ;
00207 config &sp = response.add_child("login") ;
00208 sp["username"] = login ;
00209
00210
00211
00212
00213
00214 if (preferences::get_ping_timeout()) {
00215
00216 sp["selective_ping"] = false;
00217 } else {
00218
00219
00220 sp["selective_ping"] = true;
00221 }
00222 network::send_data(response, 0);
00223
00224
00225 network::connection data_res = network::receive_data(data, 0, 3000);
00226 if(!data_res) {
00227 throw network::error(_("Connection timed out"));
00228 }
00229
00230 config *warning = &data.child("warning");
00231
00232 if(*warning) {
00233 std::string warning_msg;
00234
00235 utils::string_map i18n_symbols;
00236 i18n_symbols["nick"] = login;
00237
00238 if((*warning)["warning_code"] == MP_NAME_INACTIVE_WARNING) {
00239 warning_msg = vgettext("The nickname ‘$nick’ is inactive. "
00240 "You cannot claim ownership of this nickname until you "
00241 "activate your account via email or ask an "
00242 "administrator to do it for you.", i18n_symbols);
00243 } else {
00244 warning_msg = (*warning)["message"].str();
00245 }
00246
00247 warning_msg += "\n\n";
00248 warning_msg += _("Do you want to continue?");
00249
00250 if(gui2::show_message(disp.video(), _("Warning"), warning_msg, gui2::tmessage::yes_no_buttons) != gui2::twindow::OK) {
00251 return ABORT_SERVER;
00252 }
00253 }
00254
00255 config *error = &data.child("error");
00256
00257
00258 if (!*error) break;
00259
00260 do {
00261 std::string password = preferences::password();
00262
00263 bool fall_through = (*error)["force_confirmation"].to_bool() ?
00264 (gui2::show_message(disp.video(), _("Confirm"), (*error)["message"], gui2::tmessage::ok_cancel_buttons) == gui2::twindow::CANCEL) :
00265 false;
00266
00267 const bool is_pw_request = !((*error)["password_request"].empty()) && !(password.empty());
00268
00269
00270
00271
00272
00273 if((is_pw_request || !password_reminder.empty()) && !fall_through) {
00274 if(is_pw_request) {
00275 if ((*error)["phpbb_encryption"].to_bool())
00276 {
00277
00278
00279
00280
00281
00282 for(std::string::size_type pos = 0; (pos = password.find('&', pos)) != std::string::npos; ++pos )
00283 password.replace(pos, 1, "&");
00284 for(std::string::size_type pos = 0; (pos = password.find('\"', pos)) != std::string::npos; ++pos )
00285 password.replace(pos, 1, """);
00286 for(std::string::size_type pos = 0; (pos = password.find('<', pos)) != std::string::npos; ++pos )
00287 password.replace(pos, 1, "<");
00288 for(std::string::size_type pos = 0; (pos = password.find('>', pos)) != std::string::npos; ++pos )
00289 password.replace(pos, 1, ">");
00290
00291 const std::string salt = (*error)["salt"];
00292
00293 if (salt.length() < 12) {
00294 throw network::error(_("Bad data received from server"));
00295 }
00296
00297 sp["password"] = util::create_hash(util::create_hash(password, util::get_salt(salt),
00298 util::get_iteration_count(salt)), salt.substr(12, 8));
00299
00300 } else {
00301 sp["password"] = password;
00302 }
00303 }
00304
00305 sp["password_reminder"] = password_reminder;
00306
00307
00308 network::send_data(response, 0);
00309
00310 network::connection data_res = network::receive_data(data, 0, 3000);
00311 if(!data_res) {
00312 throw network::error(_("Connection timed out"));
00313 }
00314
00315 error = &data.child("error");
00316
00317
00318 if (!*error) break;
00319
00320
00321 }
00322
00323 password_reminder = "";
00324
00325
00326
00327
00328
00329
00330 std::string error_message;
00331 utils::string_map i18n_symbols;
00332 i18n_symbols["nick"] = login;
00333
00334 if((*error)["error_code"] == MP_MUST_LOGIN) {
00335 error_message = _("You must login first.");
00336 } else if((*error)["error_code"] == MP_NAME_TAKEN_ERROR) {
00337 error_message = vgettext("The nickname ‘$nick’ is already taken.", i18n_symbols);
00338 } else if((*error)["error_code"] == MP_INVALID_CHARS_IN_NAME_ERROR) {
00339 error_message = vgettext("The nickname ‘$nick’ contains invalid "
00340 "characters. Only alpha-numeric characters, underscores and "
00341 "hyphens are allowed.", i18n_symbols);
00342 } else if((*error)["error_code"] == MP_NAME_TOO_LONG_ERROR) {
00343 error_message = vgettext("The nickname ‘$nick’ is too long. Nicks must "
00344 "be 20 characters or less.", i18n_symbols);
00345 } else if((*error)["error_code"] == MP_NAME_RESERVED_ERROR) {
00346 error_message = vgettext("The nickname ‘$nick’ is reserved and cannot be used by players.", i18n_symbols);
00347 } else if((*error)["error_code"] == MP_NAME_UNREGISTERED_ERROR) {
00348 error_message = vgettext("The nickname ‘$nick’ is not registered on this server.", i18n_symbols)
00349 + _(" This server disallows unregistered nicknames.");
00350 } else if((*error)["error_code"] == MP_PASSWORD_REQUEST) {
00351 error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols);
00352 } else if((*error)["error_code"] == MP_PASSWORD_REQUEST_FOR_LOGGED_IN_NAME) {
00353 error_message = vgettext("The nickname ‘$nick’ is registered on this server.", i18n_symbols)
00354 + "\n\n" + _("WARNING: There is already a client using this nickname, "
00355 "logging in will cause that client to be kicked!");
00356 } else if((*error)["error_code"] == MP_NO_SEED_ERROR) {
00357 error_message = _("Error in the login procedure (the server had no "
00358 "seed for your connection).");
00359 } else if((*error)["error_code"] == MP_INCORRECT_PASSWORD_ERROR) {
00360 error_message = _("The password you provided was incorrect.");
00361 } else if((*error)["error_code"] == MP_TOO_MANY_ATTEMPTS_ERROR) {
00362 error_message = _("You have made too many login attempts.");
00363 } else {
00364 error_message = (*error)["message"].str();
00365 }
00366
00367 gui2::tmp_login dlg(error_message, !((*error)["password_request"].empty()));
00368 dlg.show(disp.video());
00369
00370 switch(dlg.get_retval()) {
00371
00372 case gui2::twindow::OK:
00373 break;
00374
00375 case 1:
00376 password_reminder = "yes";
00377 break;
00378
00379 default: return ABORT_SERVER;
00380 }
00381
00382
00383 } while(login == preferences::login());
00384
00385
00386
00387
00388 if(!*error) break;
00389 }
00390 }
00391 } while(!(data.child("join_lobby") || data.child("join_game")));
00392
00393 if (h != preferences::server_list().front().address)
00394 preferences::set_network_host(h);
00395
00396 if (data.child("join_lobby")) {
00397 return WESNOTHD_SERVER;
00398 } else {
00399 return SIMPLE_SERVER;
00400 }
00401
00402 }
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417 static void enter_wait_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist, bool observe)
00418 {
00419 DBG_MP << "entering wait mode" << std::endl;
00420
00421 mp::ui::result res;
00422 game_state state;
00423 network_game_manager m;
00424
00425 gamelist.clear();
00426 statistics::fresh_stats();
00427 n_unit::id_manager::instance().clear();
00428
00429 {
00430 mp::wait ui(disp, game_config, chat, gamelist);
00431
00432 ui.join_game(observe);
00433
00434 run_lobby_loop(disp, ui);
00435 res = ui.get_result();
00436
00437 if (res == mp::ui::PLAY) {
00438 ui.start_game();
00439
00440
00441
00442
00443
00444
00445
00446 state = ui.get_state();
00447
00448 }
00449 }
00450
00451 switch (res) {
00452 case mp::ui::PLAY:
00453 play_game(disp, state, game_config, IO_CLIENT,
00454 preferences::skip_mp_replay() && observe);
00455 recorder.clear();
00456
00457 break;
00458 case mp::ui::QUIT:
00459 default:
00460 break;
00461 }
00462 }
00463
00464 static void enter_create_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist, mp::controller default_controller, bool local_players_only = false);
00465
00466 static bool enter_connect_mode(game_display& disp, const config& game_config,
00467 mp::chat& chat, config& gamelist, const mp_game_settings& params,
00468 const int num_turns, mp::controller default_controller, bool local_players_only = false)
00469 {
00470 DBG_MP << "entering connect mode" << std::endl;
00471
00472 mp::ui::result res;
00473 game_state state;
00474 const network::manager net_manager(1,1);
00475 network_game_manager m;
00476
00477 gamelist.clear();
00478 statistics::fresh_stats();
00479
00480 {
00481 mp::connect ui(disp, game_config, chat, gamelist, params, num_turns, default_controller, local_players_only);
00482 run_lobby_loop(disp, ui);
00483
00484 res = ui.get_result();
00485
00486
00487
00488 if (res == mp::ui::PLAY) {
00489 ui.start_game();
00490 state = ui.get_state();
00491 }
00492 }
00493
00494 switch (res) {
00495 case mp::ui::PLAY:
00496 play_game(disp, state, game_config, IO_SERVER);
00497 recorder.clear();
00498
00499 break;
00500 case mp::ui::CREATE:
00501 enter_create_mode(disp, game_config, chat, gamelist, default_controller, local_players_only);
00502 break;
00503 case mp::ui::QUIT:
00504 default:
00505 network::send_data(config("refresh_lobby"), 0);
00506 return false;
00507 }
00508
00509 return true;
00510 }
00511
00512 static void enter_create_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist, mp::controller default_controller, bool local_players_only)
00513 {
00514 DBG_MP << "entering create mode" << std::endl;
00515
00516 bool connect_canceled;
00517
00518 do {
00519 connect_canceled = false;
00520
00521 if (gui2::new_widgets) {
00522
00523 gui2::tmp_create_game dlg(game_config);
00524
00525 dlg.show(disp.video());
00526
00527 network::send_data(config("refresh_lobby"), 0);
00528 } else {
00529
00530 mp::ui::result res;
00531 mp_game_settings params;
00532 int num_turns;
00533
00534 {
00535 mp::create ui(disp, game_config, chat, gamelist, local_players_only);
00536 run_lobby_loop(disp, ui);
00537 res = ui.get_result();
00538 params = ui.get_parameters();
00539 num_turns = ui.num_turns();
00540 }
00541
00542 switch (res) {
00543 case mp::ui::CREATE:
00544 connect_canceled = !enter_connect_mode(disp, game_config, chat, gamelist, params, num_turns, default_controller, local_players_only);
00545 break;
00546 case mp::ui::QUIT:
00547 default:
00548
00549 network::send_data(config("refresh_lobby"), 0);
00550 break;
00551 }
00552 }
00553 } while(connect_canceled);
00554 }
00555
00556 static void do_preferences_dialog(game_display& disp, const config& game_config)
00557 {
00558 DBG_MP << "displaying preferences dialog" << std::endl;
00559 const preferences::display_manager disp_manager(&disp);
00560 preferences::show_preferences_dialog(disp,game_config);
00561
00562
00563
00564
00565
00566
00567 const SDL_Rect rect = screen_area();
00568 preferences::set_resolution(disp.video(), rect.w, rect.h);
00569
00570 gui2::settings::gamemap_width += rect.w - gui2::settings::screen_width ;
00571 gui2::settings::gamemap_height += rect.h - gui2::settings::screen_height ;
00572 gui2::settings::screen_width = rect.w;
00573 gui2::settings::screen_height = rect.h;
00574 }
00575
00576 static void enter_lobby_mode(game_display& disp, const config& game_config, mp::chat& chat, config& gamelist)
00577 {
00578 DBG_MP << "entering lobby mode" << std::endl;
00579
00580 mp::ui::result res;
00581
00582 while (true) {
00583 const config &cfg = game_config.child("lobby_music");
00584 if (cfg) {
00585 foreach (const config &i, cfg.child_range("music")) {
00586 sound::play_music_config(i);
00587 }
00588 sound::commit_music_changes();
00589 } else {
00590 sound::empty_playlist();
00591 sound::stop_music();
00592 }
00593 lobby_info li(game_config);
00594
00595
00596 const Uint32 color = SDL_MapRGBA(disp.video().getSurface()->format
00597 , 0
00598 , 0
00599 , 0
00600 , 255);
00601
00602 sdl_fill_rect(disp.video().getSurface(), NULL, color);
00603
00604 if(preferences::new_lobby()) {
00605 gui2::tlobby_main dlg(game_config, li, disp);
00606 dlg.set_preferences_callback(
00607 boost::bind(do_preferences_dialog,
00608 boost::ref(disp), boost::ref(game_config)));
00609 dlg.show(disp.video());
00610
00611 switch (dlg.get_legacy_result()) {
00612 case gui2::tlobby_main::CREATE:
00613 res = mp::ui::CREATE;
00614 break;
00615 case gui2::tlobby_main::JOIN:
00616 res = mp::ui::JOIN;
00617 break;
00618 case gui2::tlobby_main::OBSERVE:
00619 res = mp::ui::OBSERVE;
00620 break;
00621 default:
00622 res = mp::ui::QUIT;
00623 }
00624 } else {
00625 mp::lobby ui(disp, game_config, chat, gamelist);
00626 run_lobby_loop(disp, ui);
00627 res = ui.get_result();
00628 }
00629
00630 switch (res) {
00631 case mp::ui::JOIN:
00632 try {
00633 enter_wait_mode(disp, game_config, chat, gamelist, false);
00634 } catch(config::error& error) {
00635 if(!error.message.empty()) {
00636 gui2::show_error_message(disp.video(), error.message);
00637 }
00638
00639 network::send_data(config("refresh_lobby"), 0);
00640 }
00641 break;
00642 case mp::ui::OBSERVE:
00643 try {
00644 enter_wait_mode(disp, game_config, chat, gamelist, true);
00645 } catch(config::error& error) {
00646 if(!error.message.empty()) {
00647 gui2::show_error_message(disp.video(), error.message);
00648 }
00649 }
00650
00651
00652 network::send_data(config("refresh_lobby"), 0);
00653 break;
00654 case mp::ui::CREATE:
00655 try {
00656 enter_create_mode(disp, game_config, chat, gamelist, mp::CNTR_NETWORK);
00657 } catch(config::error& error) {
00658 if (!error.message.empty())
00659 gui2::show_error_message(disp.video(), error.message);
00660
00661 network::send_data(config("refresh_lobby"), 0);
00662 }
00663 break;
00664 case mp::ui::QUIT:
00665 return;
00666 case mp::ui::PREFERENCES:
00667 {
00668 do_preferences_dialog(disp, game_config);
00669
00670 network::send_data(config("refresh_lobby"), 0);
00671 }
00672 break;
00673 default:
00674 return;
00675 }
00676 }
00677 }
00678
00679 namespace mp {
00680
00681 void start_local_game(game_display& disp, const config& game_config,
00682 mp::controller default_controller)
00683 {
00684 DBG_MP << "starting local game" << std::endl;
00685 const rand_rng::set_random_generator generator_setter(&recorder);
00686 mp::chat chat;
00687 config gamelist;
00688 playmp_controller::set_replay_last_turn(0);
00689 preferences::set_message_private(false);
00690 enter_create_mode(disp, game_config, chat, gamelist, default_controller, true);
00691 }
00692
00693 void start_client(game_display& disp, const config& game_config,
00694 const std::string& host)
00695 {
00696 DBG_MP << "starting client" << std::endl;
00697 const rand_rng::set_random_generator generator_setter(&recorder);
00698 const network::manager net_manager(1,1);
00699
00700 mp::chat chat;
00701 config gamelist;
00702 server_type type = open_connection(disp, host);
00703
00704 switch(type) {
00705 case WESNOTHD_SERVER:
00706 enter_lobby_mode(disp, game_config, chat, gamelist);
00707 break;
00708 case SIMPLE_SERVER:
00709 playmp_controller::set_replay_last_turn(0);
00710 preferences::set_message_private(false);
00711 enter_wait_mode(disp, game_config, chat, gamelist, false);
00712 break;
00713 case ABORT_SERVER:
00714 break;
00715 }
00716 }
00717
00718 }
00719