multiplayer_wait.cpp

Go to the documentation of this file.
00001 /* $Id: multiplayer_wait.cpp 25703 2008-04-09 14:46:08Z esr $ */
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_display.hpp"
00020 #include "game_events.hpp"
00021 #include "game_preferences.hpp"
00022 #include "gettext.hpp"
00023 #include "leader_list.hpp"
00024 #include "log.hpp"
00025 #include "marked-up_text.hpp"
00026 #include "multiplayer.hpp"
00027 #include "multiplayer_wait.hpp"
00028 #include "replay.hpp"
00029 #include "statistics.hpp"
00030 #include "util.hpp"
00031 #include "video.hpp"
00032 #include "wml_separators.hpp"
00033 #include "serialization/string_utils.hpp"
00034 
00035 #include <cassert>
00036 
00037 #define DBG_NW LOG_STREAM(debug, network)
00038 #define LOG_NW LOG_STREAM(info, network)
00039 #define ERR_NW LOG_STREAM(err, network)
00040 
00041 namespace {
00042 const SDL_Rect leader_pane_position = {-260,-370,260,370};
00043 const int leader_pane_border = 10;
00044 }
00045 
00046 namespace mp {
00047 
00048 wait::leader_preview_pane::leader_preview_pane(game_display& disp,
00049         const config::child_list& side_list) :
00050     gui::preview_pane(disp.video()),
00051     side_list_(side_list),
00052     leader_combo_(disp, std::vector<std::string>()),
00053     gender_combo_(disp, std::vector<std::string>()),
00054     leaders_(side_list, &leader_combo_, &gender_combo_),
00055     selection_(0)
00056 {
00057     set_location(leader_pane_position);
00058 }
00059 
00060 void wait::leader_preview_pane::process_event()
00061 {
00062 
00063     if (leader_combo_.changed() || gender_combo_.changed()) {
00064         leaders_.set_leader_combo(&leader_combo_);
00065         leaders_.update_gender_list(leaders_.get_leader());
00066         set_dirty();
00067     }
00068 }
00069 
00070 void wait::leader_preview_pane::draw_contents()
00071 {
00072     bg_restore();
00073 
00074     surface const screen = video().getSurface();
00075 
00076     SDL_Rect const &loc = location();
00077     const SDL_Rect area = { loc.x + leader_pane_border, loc.y + leader_pane_border,
00078                             loc.w - leader_pane_border * 2, loc.h - leader_pane_border * 2 };
00079     SDL_Rect clip_area = area;
00080     const clip_rect_setter clipper(screen,clip_area);
00081 
00082     if(selection_ < side_list_.size()) {
00083         const config& side = *side_list_[selection_];
00084         std::string faction = side["faction"];
00085 
00086         const std::string recruits = side["recruit"];
00087         const std::vector<std::string> recruit_list = utils::split(recruits);
00088         std::ostringstream recruit_string;
00089 
00090         if(faction[0] == font::IMAGE) {
00091             std::string::size_type p = faction.find_first_of(COLUMN_SEPARATOR);
00092             if(p != std::string::npos && p < faction.size())
00093                 faction = faction.substr(p+1);
00094         }
00095         std::string leader = leaders_.get_leader();
00096         std::string gender = leaders_.get_gender();
00097 
00098         unit_type_data::unit_type_map_wrapper& utypes = unit_type_data::types();
00099         std::string leader_name;
00100         std::string image;
00101 
00102         const unit_type* ut;
00103         const unit_type* utg;
00104 
00105         if (utypes.find(leader) != utypes.end() && leader != "random") {
00106             ut = &(utypes.find(leader)->second);
00107             if (!gender.empty()) {
00108                 if (gender == "female")
00109                     utg = &(ut->get_gender_unit_type(unit_race::FEMALE));
00110                 else
00111                     utg = &(ut->get_gender_unit_type(unit_race::MALE));
00112             } else
00113                 utg = ut;
00114 
00115             leader_name = utg->type_name();
00116 #ifdef LOW_MEM
00117             image = utg->image();
00118 #else
00119             image = utg->image() + std::string("~RC(") + std::string(utg->flag_rgb() + ">1)");
00120 #endif
00121         }
00122 
00123         for(std::vector<std::string>::const_iterator itor = recruit_list.begin();
00124                 itor != recruit_list.end(); ++itor) {
00125 
00126             if (utypes.find(*itor) != utypes.end()) {
00127                 if(itor != recruit_list.begin())
00128                     recruit_string << ", ";
00129                 recruit_string << utypes.find(*itor)->second.type_name();
00130             }
00131         }
00132 
00133         SDL_Rect image_rect = {area.x,area.y,0,0};
00134 
00135         surface unit_image(image::get_image(image));
00136 
00137         if(!unit_image.null()) {
00138             image_rect.w = unit_image->w;
00139             image_rect.h = unit_image->h;
00140             SDL_BlitSurface(unit_image,NULL,screen,&image_rect);
00141         }
00142 
00143         font::draw_text(&video(),area,font::SIZE_PLUS,font::NORMAL_COLOUR,faction,area.x + 110, area.y + 60);
00144         const SDL_Rect leader_rect = font::draw_text(&video(),area,font::SIZE_SMALL,font::NORMAL_COLOUR,
00145                 _("Leader: "),area.x, area.y + 110);
00146         const SDL_Rect gender_rect = font::draw_text(&video(),area,font::SIZE_SMALL,font::NORMAL_COLOUR,
00147                 _("Gender: "),area.x, leader_rect.y + 30 + (leader_rect.h - leader_combo_.height()) / 2);
00148         font::draw_wrapped_text(&video(),area,font::SIZE_SMALL,font::NORMAL_COLOUR,
00149                 _("Recruits: ") + recruit_string.str(),area.x, area.y + 132 + 30 + (leader_rect.h - leader_combo_.height()) / 2,
00150                 area.w);
00151         leader_combo_.set_location(leader_rect.x + leader_rect.w + 16, leader_rect.y + (leader_rect.h - leader_combo_.height()) / 2);
00152         gender_combo_.set_location(leader_rect.x + leader_rect.w + 16, gender_rect.y + (gender_rect.h - gender_combo_.height()) / 2);
00153     }
00154 }
00155 
00156 bool wait::leader_preview_pane::show_above() const
00157 {
00158     return false;
00159 }
00160 
00161 bool wait::leader_preview_pane::left_side() const
00162 {
00163     return false;
00164 }
00165 
00166 void wait::leader_preview_pane::set_selection(int selection)
00167 {
00168     selection_ = selection;
00169     leaders_.update_leader_list(selection_);
00170     leaders_.update_gender_list(leaders_.get_leader());
00171     set_dirty();
00172 }
00173 
00174 std::string wait::leader_preview_pane::get_selected_leader()
00175 {
00176     return leaders_.get_leader();
00177 }
00178 
00179 std::string wait::leader_preview_pane::get_selected_gender()
00180 {
00181     return leaders_.get_gender();
00182 }
00183 
00184 handler_vector wait::leader_preview_pane::handler_members() {
00185     handler_vector h;
00186     h.push_back(&leader_combo_);
00187     h.push_back(&gender_combo_);
00188     return h;
00189 }
00190 
00191 
00192 wait::wait(game_display& disp, const config& cfg,
00193         mp::chat& c, config& gamelist) :
00194     ui(disp, _("Game Lobby"), cfg, c, gamelist),
00195     cancel_button_(disp.video(), _("Cancel")),
00196     start_label_(disp.video(), _("Waiting for game to start..."), font::SIZE_SMALL, font::LOBBY_COLOUR),
00197     game_menu_(disp.video(), std::vector<std::string>(), false, -1, -1, NULL, &gui::menu::bluebg_style),
00198     stop_updates_(false)
00199 {
00200     game_menu_.set_numeric_keypress_selection(false);
00201     gamelist_updated();
00202 }
00203 
00204 void wait::process_event()
00205 {
00206     if (cancel_button_.pressed())
00207         set_result(QUIT);
00208 }
00209 
00210 void wait::join_game(bool observe)
00211 {
00212     for(;;) {
00213         network::connection data_res = dialogs::network_receive_dialog(disp(),
00214                 _("Getting game data..."), level_);
00215         if (!data_res) {
00216             set_result(QUIT);
00217             return;
00218         }
00219         check_response(data_res, level_);
00220         if(level_.child("leave_game")) {
00221             set_result(QUIT);
00222             return;
00223         }
00224         //if we have got valid side data
00225         //the first condition is to make sure that we don't have another
00226         //WML message with a side-tag in it
00227         if( (level_.values.find("version") != level_.values.end()) && (level_.child("side") != NULL) )
00228             break;
00229     }
00230 
00231     // Add the map name to the title.
00232     append_to_title(": " + level_["name"]);
00233 
00234     if (!observe) {
00235         const config::child_list& sides_list = level_.get_children("side");
00236 
00237         if(sides_list.empty()) {
00238             set_result(QUIT);
00239             throw config::error(_("No multiplayer sides available in this game"));
00240             return;
00241         }
00242 
00243         //search for an appropriate vacant slot. If a description is set
00244         //(i.e. we're loading from a saved game), then prefer to get the side
00245         //with the same description as our login. Otherwise just choose the first
00246         //available side.
00247         int side_choice = 0;
00248         for(config::child_list::const_iterator s = sides_list.begin(); s != sides_list.end(); ++s) {
00249             if((**s)["controller"] == "network" && (**s)["id"].empty()) {
00250                 if((**s)["save_id"] == preferences::login() || (**s)["current_player"] == preferences::login()) {
00251                     side_choice = s - sides_list.begin();
00252                 }
00253             }
00254         }
00255         const bool allow_changes = (*sides_list[side_choice])["allow_changes"] != "no";
00256 
00257 
00258         //if the client is allowed to choose their team, instead of having
00259         //it set by the server, do that here.
00260         std::string leader_choice, gender_choice;
00261         size_t faction_choice = 0;
00262 
00263         if(allow_changes) {
00264             events::event_context context;
00265 
00266             const config* era = level_.child("era");
00267             //! @todo Check whether we have the era. If we don't inform the user.
00268             if(era == NULL)
00269                 throw config::error(_("No era information found."));
00270             const config::child_list& possible_sides =
00271                 era->get_children("multiplayer_side");
00272             if(possible_sides.empty()) {
00273                 set_result(QUIT);
00274                 throw config::error(_("No multiplayer sides found"));
00275                 return;
00276             }
00277 
00278             std::vector<std::string> choices;
00279             for(config::child_list::const_iterator side =
00280                     possible_sides.begin(); side !=
00281                     possible_sides.end(); ++side) {
00282                 choices.push_back((**side)["name"]);
00283             }
00284 
00285             std::vector<gui::preview_pane* > preview_panes;
00286             leader_preview_pane leader_selector(disp(),
00287                     possible_sides);
00288             preview_panes.push_back(&leader_selector);
00289 
00290             const int res = gui::show_dialog(disp(), NULL, "", _("Choose your side:"),
00291                         gui::OK_CANCEL, &choices, &preview_panes);
00292             if(res < 0) {
00293                 set_result(QUIT);
00294                 return;
00295             }
00296             faction_choice = res;
00297             leader_choice = leader_selector.get_selected_leader();
00298             gender_choice = leader_selector.get_selected_gender();
00299 
00300             assert(faction_choice < possible_sides.size());
00301 
00302             config faction;
00303             config& change = faction.add_child("change_faction");
00304             change["name"] = preferences::login();
00305             change["faction"] = lexical_cast<std::string>(faction_choice);
00306             change["leader"] = leader_choice;
00307             change["gender"] = gender_choice;
00308             network::send_data(faction, 0, true);
00309         }
00310 
00311     }
00312 
00313     generate_menu();
00314 }
00315 
00316 const game_state& wait::get_state()
00317 {
00318     return state_;
00319 }
00320 
00321 void wait::start_game()
00322 {
00323     const config* stats = level_.child("statistics");
00324     if(stats != NULL) {
00325         statistics::fresh_stats();
00326         statistics::read_stats(*stats);
00327     }
00328 
00329 
00330 
00331     //! @todo Instead of using level_to_gamestate reinit the state_,
00332     //! this needs more testing -- Mordante
00333     //! It seems level_to_gamestate is needed for the start of game
00334     //! download, but downloads of later scenarios miss certain info
00335     //! and add a players section. Use players to decide between old
00336     //! and new way. (Of course it would be nice to unify the data
00337     //! stored.)
00338     if(level_.child("player") == 0) {
00339         level_to_gamestate(level_, state_, level_["savegame"] == "yes");
00340     } else {
00341 
00342         state_ = game_state(level_);
00343 
00344         // When we observe and don't have the addon installed we still need
00345         // the old way, no clue why however. Code is a copy paste of
00346         // playcampaign.cpp:576 which shows an 'Unknown scenario: '$scenario|'
00347         // error. This seems to work and have no side effects....
00348         if(!state_.scenario.empty() && state_.scenario != "null") {
00349             DBG_NW << "Falling back to loading the old way.\n";
00350             level_to_gamestate(level_, state_, level_["savegame"] == "yes");
00351         }
00352     }
00353     // add era events after loaded
00354     const config* const era_cfg = level_.child("era");
00355     if (era_cfg != NULL && level_["savegame"] != "yes") {
00356         game_events::add_events(era_cfg->get_children("event"),"era_events");
00357     }
00358 
00359     LOG_NW << "starting game\n";
00360 }
00361 
00362 game_state& wait::request_snapshot(){
00363     config cfg;
00364 
00365     cfg.add_child("snapshot_request");
00366 
00367     return state_;
00368 }
00369 
00370 void wait::layout_children(const SDL_Rect& rect)
00371 {
00372     ui::layout_children(rect);
00373 
00374     const SDL_Rect ca = client_area();
00375     int y = ca.y + ca.h - cancel_button_.height();
00376 
00377     game_menu_.set_location(ca.x, ca.y + title().height());
00378     game_menu_.set_measurements(ca.w, y - ca.y - title().height()
00379             - gui::ButtonVPadding);
00380     game_menu_.set_max_width(ca.w);
00381     game_menu_.set_max_height(y - ca.y - title().height() - gui::ButtonVPadding);
00382     cancel_button_.set_location(ca.x + ca.w - cancel_button_.width(), y);
00383     start_label_.set_location(ca.x, y + 4);
00384 }
00385 
00386 void wait::hide_children(bool hide)
00387 {
00388     ui::hide_children(hide);
00389 
00390     cancel_button_.hide(hide);
00391     game_menu_.hide(hide);
00392 }
00393 
00394 void wait::process_network_data(const config& data, const network::connection sock)
00395 {
00396     ui::process_network_data(data, sock);
00397 
00398     if(data["message"] != "") {
00399         /* GCC-3.3 needs a temp var otherwise compilation fails */
00400         gui::dialog dlg(disp(),_("Response"),data["message"],gui::OK_ONLY);
00401         dlg.show();
00402     }
00403     if(data["failed"] == "yes") {
00404         set_result(QUIT);
00405         return;
00406     } else if(data.child("stop_updates")) {
00407         stop_updates_ = true;
00408     } else if(data.child("start_game")) {
00409         LOG_NW << "received start_game message\n";
00410         set_result(PLAY);
00411         return;
00412     } else if(data.child("leave_game")) {
00413         set_result(QUIT);
00414         return;
00415     } else if(data.child("scenario_diff")) {
00416         LOG_NW << "received diff for scenario... applying...\n";
00417         //! @todo We should catch config::error and then leave the game.
00418         level_.apply_diff(*data.child("scenario_diff"));
00419         generate_menu();
00420     } else if(data.child("side")) {
00421         level_ = data;
00422         LOG_NW << "got some sides. Current number of sides = "
00423             << level_.get_children("side").size() << ","
00424             << data.get_children("side").size() << "\n";
00425         generate_menu();
00426     }
00427 }
00428 
00429 void wait::generate_menu()
00430 {
00431     if (stop_updates_)
00432         return;
00433 
00434     std::vector<std::string> details;
00435     std::vector<std::string> playerlist;
00436 
00437     const config::child_list& sides = level_.get_children("side");
00438     for(config::child_list::const_iterator s = sides.begin(); s != sides.end(); ++s) {
00439         const config& sd = **s;
00440 
00441         if(sd["allow_player"] == "no") {
00442             continue;
00443         }
00444 
00445         std::string description = sd["user_description"];
00446         const std::string faction_id = sd["id"];
00447 
00448         t_string side_name = sd["name"];
00449         std::string leader_type = sd["type"];
00450         std::string gender_id = sd["gender"];
00451 
00452         // Hack: if there is a unit which can recruit, use it as a
00453         // leader. Necessary to display leader information when loading
00454         // saves.
00455         config::const_child_itors side_units = sd.child_range("unit");
00456         for(;side_units.first != side_units.second; ++side_units.first) {
00457             if(utils::string_bool((**side_units.first)["canrecruit"], false)) {
00458                 leader_type = (**side_units.first)["type"];
00459                 break;
00460             }
00461         }
00462 
00463         if(!sd["id"].empty())
00464             playerlist.push_back(sd["id"]);
00465 
00466         std::string leader_name;
00467         std::string leader_image;
00468         unit_type_data::unit_type_map_wrapper& utypes = unit_type_data::types();
00469         const unit_type* ut;
00470         const unit_type* utg;
00471 
00472         if (utypes.find(leader_type) != utypes.end() && leader_type != "random") {
00473             ut = &(utypes.find(leader_type)->second);
00474             if (!gender_id.empty()) {
00475                 if (gender_id == "female")
00476                     utg = &(ut->get_gender_unit_type(unit_race::FEMALE));
00477                 else
00478                     // FIXME: this will make it look male, even if it's random. But all this
00479                     // code will be wiped out when the MP UI gets unified, anyway.
00480                     utg = &(ut->get_gender_unit_type(unit_race::MALE));
00481             } else
00482                 utg = ut;
00483 
00484             leader_name = utg->type_name();
00485 #ifdef LOW_MEM
00486             leader_image = utg->image();
00487 #else
00488             leader_image = utg->image() + std::string("~RC(") + std::string(utg->flag_rgb() + ">" + sd["side"] + ")");
00489 #endif
00490         } else {
00491             leader_image = leader_list_manager::random_enemy_picture;
00492         }
00493         if (!leader_image.empty()) {
00494             // Dumps the "image" part of the faction name, if any,
00495             // to replace it by a picture of the actual leader
00496             if(side_name.str()[0] == font::IMAGE) {
00497                 std::string::size_type p =
00498                     side_name.str().find_first_of(COLUMN_SEPARATOR);
00499                 if(p != std::string::npos && p < side_name.size()) {
00500                     side_name = IMAGE_PREFIX + leader_image + COLUMN_SEPARATOR + side_name.str().substr(p+1);
00501                 }
00502             }
00503         }
00504 
00505         std::stringstream str;
00506         str << sd["side"] << ". " << COLUMN_SEPARATOR;
00507         str << description << COLUMN_SEPARATOR << side_name << COLUMN_SEPARATOR;
00508         // Mark parentheses translatable for languages like Japanese
00509         if(!leader_name.empty())
00510             str << _("(") << leader_name << _(")");
00511         str << COLUMN_SEPARATOR;
00512         // Don't show gold for saved games
00513         if(sd["allow_changes"] == "yes")
00514             str << sd["gold"] << ' ' << _("Gold") << COLUMN_SEPARATOR;
00515 
00516         int income_amt = lexical_cast_default<int>(sd["income"], 0);
00517         if(income_amt != 0){
00518             str << _("(") << _("Income") << ' ';
00519             if(income_amt > 0)
00520                 str << _("+");
00521             str << sd["income"] << _(")");
00522         }
00523 
00524         str << COLUMN_SEPARATOR << sd["user_team_name"];
00525         int disp_color = lexical_cast_default<int>(sd["colour"], 0) - 1;
00526         if(!sd["colour"].empty()) {
00527             try {
00528                 disp_color = game_config::color_info(sd["colour"]).index() - 1;
00529             } catch(config::error&) {
00530                 //ignore
00531             }
00532         } else {
00533             //! @todo we fall back to the side colour, but that's ugly rather
00534             // make the colour mandatory in 1.5.
00535             disp_color = lexical_cast_default<int>(sd["side"], 0) - 1;
00536         }
00537         str << COLUMN_SEPARATOR << get_colour_string(disp_color);
00538         details.push_back(str.str());
00539     }
00540 
00541     game_menu_.set_items(details);
00542 
00543     // Uses the actual connected player list if we do not have any
00544     // "gamelist" user data
00545     if (gamelist().child("user") == NULL) {
00546         set_user_list(playerlist, true);
00547     }
00548 }
00549 
00550 } // namespace mp
00551 

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