multiplayer_lobby.cpp

Go to the documentation of this file.
00001 /* $Id: multiplayer_lobby.cpp 51992 2011-11-16 03:42:09Z espreon $ */
00002 /*
00003    Copyright (C) 2007 - 2009
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 as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 /** @file multiplayer_lobby.cpp
00017  * A section on the server where players can chat, create and join games.
00018  */
00019 
00020 #include "global.hpp"
00021 
00022 #include "filesystem.hpp"
00023 #include "foreach.hpp"
00024 #include "game_preferences.hpp"
00025 #include "lobby_preferences.hpp"
00026 #include "map_exception.hpp"
00027 #include "marked-up_text.hpp"
00028 #include "minimap.hpp"
00029 #include "multiplayer_lobby.hpp"
00030 #include "gettext.hpp"
00031 #include "log.hpp"
00032 #include "playmp_controller.hpp"
00033 #include "sound.hpp"
00034 #include "wml_exception.hpp"
00035 #include "formula_string_utils.hpp"
00036 
00037 static lg::log_domain log_config("config");
00038 #define ERR_CF LOG_STREAM(err, log_config)
00039 
00040 namespace {
00041 std::vector<std::string> empty_string_vector;
00042 }
00043 
00044 namespace mp {
00045 gamebrowser::gamebrowser(CVideo& video, const config &map_hashes) :
00046     menu(video, empty_string_vector, false, -1, -1, NULL, &menu::bluebg_style),
00047     gold_icon_locator_("themes/gold.png"),
00048     xp_icon_locator_("themes/units.png"),
00049     vision_icon_locator_("misc/invisible.png"),
00050     time_limit_icon_locator_("themes/sand-clock.png"),
00051     observer_icon_locator_("misc/eye.png"),
00052     no_observer_icon_locator_("misc/no_observer.png"),
00053     shuffle_sides_icon_locator_("misc/shuffle-sides.png"),
00054     map_hashes_(map_hashes),
00055     item_height_(100),
00056     margin_(5),
00057     minimap_size_(item_height_ - 2*margin_),
00058     h_padding_(5),
00059     header_height_(20),
00060     selected_(0),
00061     visible_range_(std::pair<size_t,size_t>(0,0)),
00062     games_(),
00063     redraw_items_(),
00064     widths_(),
00065     double_clicked_(false),
00066     ignore_next_doubleclick_(false),
00067     last_was_doubleclick_(false)
00068 {
00069     set_numeric_keypress_selection(false);
00070 }
00071 
00072 void gamebrowser::set_inner_location(const SDL_Rect& rect)
00073 {
00074     set_full_size(games_.size());
00075     set_shown_size(rect.h / row_height());
00076     bg_register(rect);
00077     scroll(get_position());
00078 }
00079 
00080 void gamebrowser::scroll(unsigned int pos)
00081 {
00082     if(pos < games_.size()) {
00083         visible_range_.first = pos;
00084         visible_range_.second = std::min<size_t>(pos + inner_location().h / row_height(), games_.size());
00085         set_dirty();
00086     }
00087 }
00088 
00089 SDL_Rect gamebrowser::get_item_rect(size_t index) const {
00090     if(index < visible_range_.first || index > visible_range_.second) {
00091         const SDL_Rect res = { 0, 0, 0, 0 };
00092         return res;
00093     }
00094     const SDL_Rect& loc = inner_location();
00095     return create_rect(
00096               loc.x
00097             , loc.y + (index - visible_range_.first) * row_height()
00098             , loc.w
00099             , row_height());
00100 }
00101 
00102 void gamebrowser::draw()
00103 {
00104     if(hidden())
00105         return;
00106     if(dirty()) {
00107         bg_restore();
00108         util::scoped_ptr<clip_rect_setter> clipper(NULL);
00109         if(clip_rect())
00110             clipper.assign(new clip_rect_setter(video().getSurface(), clip_rect()));
00111         draw_contents();
00112         update_rect(location());
00113         set_dirty(false);
00114     }
00115 }
00116 
00117 void gamebrowser::draw_contents()
00118 {
00119     if(!games_.empty()) {
00120         for(size_t i = visible_range_.first; i != visible_range_.second; ++i) {
00121             style_->draw_row(*this,i,get_item_rect(i),(i==selected_)? SELECTED_ROW : NORMAL_ROW);
00122         }
00123     } else {
00124         const SDL_Rect rect = inner_location();
00125         font::draw_text(&video(), rect, font::SIZE_NORMAL, font::NORMAL_COLOR, _("--no games open--"), rect.x + margin_, rect.y + margin_);
00126     }
00127 }
00128 
00129 void gamebrowser::draw_row(const size_t index, const SDL_Rect& item_rect, ROW_TYPE /*type*/) {
00130     const game_item& game = games_[index];
00131     int xpos = item_rect.x + margin_;
00132     int ypos = item_rect.y + margin_;
00133     std::string no_era_string = "";
00134     // Draw minimaps
00135     if (game.mini_map != NULL) {
00136         int minimap_x = xpos + (minimap_size_ - game.mini_map->w)/2;
00137         int minimap_y = ypos + (minimap_size_ - game.mini_map->h)/2;
00138         video().blit_surface(minimap_x, minimap_y, game.mini_map);
00139     }
00140     xpos += minimap_size_ + margin_;
00141 
00142     // Set font color
00143     SDL_Color font_color;
00144     if (game.vacant_slots > 0) {
00145         if (game.reloaded || game.started) {
00146             font_color = font::YELLOW_COLOR;
00147         } else {
00148             font_color = font::GOOD_COLOR;
00149         }
00150     } else {
00151         if (game.observers) {
00152             font_color = font::NORMAL_COLOR;
00153         } else {
00154             font_color = font::BAD_COLOR;
00155         }
00156     }
00157     if(!game.have_era && font_color != font::BAD_COLOR) {
00158         font_color = font::DISABLED_COLOR;
00159         no_era_string = _(" (Unknown Era)");
00160     }
00161 
00162     const surface status_text(font::get_rendered_text(game.status,
00163         font::SIZE_NORMAL, font_color));
00164     const int status_text_width = status_text ? status_text->w : 0;
00165 
00166     // First line: draw game name
00167     const surface name_surf(font::get_rendered_text(
00168         font::make_text_ellipsis(game.name + no_era_string, font::SIZE_PLUS,
00169             (item_rect.x + item_rect.w) - xpos - margin_ - status_text_width - h_padding_),
00170         font::SIZE_PLUS, font_color));
00171     video().blit_surface(xpos, ypos, name_surf);
00172 
00173     // Draw status text
00174     if(status_text) {
00175         // Align the bottom of the text with the game name
00176         video().blit_surface(item_rect.x + item_rect.w - margin_ - status_text_width,
00177             ypos + name_surf->h - status_text->h, status_text);
00178     }
00179 
00180     // Second line
00181     ypos = item_rect.y + item_rect.h/2;
00182 
00183     // Draw map info
00184     const surface map_info_surf(font::get_rendered_text(
00185         font::make_text_ellipsis(game.map_info, font::SIZE_NORMAL,
00186             (item_rect.x + item_rect.w) - xpos - margin_),
00187         font::SIZE_NORMAL, font::NORMAL_COLOR));
00188     if(map_info_surf) {
00189         video().blit_surface(xpos, ypos - map_info_surf->h/2, map_info_surf);
00190     }
00191 
00192     // Third line
00193     ypos = item_rect.y + item_rect.h  - margin_;
00194 
00195     // Draw observer icon
00196     const surface observer_icon(image::get_image(game.observers
00197         ? observer_icon_locator_ : no_observer_icon_locator_));
00198     if(observer_icon) {
00199         video().blit_surface(xpos, ypos - observer_icon->h, observer_icon);
00200 
00201         // Set ypos to the middle of the line, so that
00202         // all text and icons can be aligned symmetrical to it
00203         ypos -= observer_icon->h/2;
00204         xpos += observer_icon->w + 2 * h_padding_;
00205     }
00206 
00207     // Draw shuffle icon
00208     if (game.shuffle_sides)
00209     {
00210         const surface shuffle_icon(image::get_image(shuffle_sides_icon_locator_));
00211         if(shuffle_icon) {
00212             video().blit_surface(xpos, ypos - shuffle_icon->h/2, shuffle_icon);
00213 
00214             xpos += shuffle_icon->w + 2 * h_padding_;
00215         }
00216     }
00217 
00218     // Draw gold icon
00219     const surface gold_icon(image::get_image(gold_icon_locator_));
00220     if(gold_icon) {
00221         video().blit_surface(xpos, ypos - gold_icon->h/2, gold_icon);
00222 
00223         xpos += gold_icon->w + h_padding_;
00224     }
00225 
00226     // Draw gold text
00227     const surface gold_text(font::get_rendered_text(game.gold, font::SIZE_NORMAL,
00228         game.use_map_settings ? font::GRAY_COLOR : font::NORMAL_COLOR));
00229     if(gold_text) {
00230         video().blit_surface(xpos, ypos - gold_text->h/2, gold_text);
00231 
00232         xpos += gold_text->w + 2 * h_padding_;
00233     }
00234 
00235     // Draw xp icon
00236     const surface xp_icon(image::get_image(xp_icon_locator_));
00237     if(xp_icon) {
00238         video().blit_surface(xpos, ypos - xp_icon->h/2, xp_icon);
00239 
00240         xpos += xp_icon->w + h_padding_;
00241     }
00242 
00243     // Draw xp text
00244     const surface xp_text(font::get_rendered_text(game.xp, font::SIZE_NORMAL, font::NORMAL_COLOR));
00245     if(xp_text) {
00246         video().blit_surface(xpos, ypos - xp_text->h/2, xp_text);
00247 
00248         xpos += xp_text->w + 2 * h_padding_;
00249     }
00250 
00251     if(!game.time_limit.empty()) {
00252         // Draw time icon
00253         const surface time_icon(image::get_image(time_limit_icon_locator_));
00254         video().blit_surface(xpos, ypos - time_icon->h/2, time_icon);
00255 
00256         xpos += time_icon->w + h_padding_;
00257 
00258         // Draw time text
00259         const surface time_text(font::get_rendered_text(game.time_limit,
00260             font::SIZE_NORMAL, font::NORMAL_COLOR));
00261         video().blit_surface(xpos, ypos - time_text->h/2, time_text);
00262 
00263         xpos += time_text->w + 2 * h_padding_;
00264     }
00265 
00266     // Draw vision icon
00267     const surface vision_icon(image::get_image(vision_icon_locator_));
00268     if(vision_icon) {
00269         video().blit_surface(xpos, ypos - vision_icon->h/2, vision_icon);
00270 
00271         xpos += vision_icon->w + h_padding_;
00272     }
00273 
00274     // Draw vision text
00275     const surface vision_text(font::get_rendered_text(
00276         font::make_text_ellipsis(game.vision, font::SIZE_NORMAL,
00277             (item_rect.x + item_rect.w) - xpos - margin_),
00278         font::SIZE_NORMAL,
00279         game.use_map_settings ? font::GRAY_COLOR : font::NORMAL_COLOR));
00280     if(vision_text) {
00281         video().blit_surface(xpos, ypos - vision_text->h/2, vision_text);
00282     }
00283 
00284     // Draw map settings text
00285     if (game.use_map_settings) {
00286         xpos += vision_text->w + 4 * h_padding_;
00287         const surface map_settings_text(font::get_rendered_text(
00288             font::make_text_ellipsis(_("Use map settings"), font::SIZE_NORMAL,
00289                 (item_rect.x + item_rect.w) - xpos - margin_),
00290             font::SIZE_NORMAL,
00291             (game.verified && game.vacant_slots > 0)
00292                 ? font::GOOD_COLOR : font::NORMAL_COLOR));
00293         video().blit_surface(xpos, ypos - map_settings_text->h/2, map_settings_text);
00294     }
00295 }
00296 
00297 void gamebrowser::handle_event(const SDL_Event& event)
00298 {
00299     scrollarea::handle_event(event);
00300     if(event.type == SDL_KEYDOWN) {
00301         if(focus(&event) && !games_.empty()) {
00302             switch(event.key.keysym.sym) {
00303                 case SDLK_UP:
00304                     if(selected_ > 0) {
00305                         --selected_;
00306                         adjust_position(selected_);
00307                         set_dirty();
00308                     }
00309                     break;
00310                 case SDLK_DOWN:
00311                     if(selected_ < games_.size() - 1) {
00312                         ++selected_;
00313                         adjust_position(selected_);
00314                         set_dirty();
00315                     }
00316                     break;
00317                 case SDLK_PAGEUP:
00318                 {
00319                     const long items_on_screen = visible_range_.second - visible_range_.first;
00320                     selected_ = static_cast<size_t>(std::max<long>(static_cast<long>(selected_) - items_on_screen, 0));
00321                     adjust_position(selected_);
00322                     set_dirty();
00323                 }
00324                     break;
00325                 case SDLK_PAGEDOWN:
00326                 {
00327                     const size_t items_on_screen = visible_range_.second - visible_range_.first;
00328                     selected_ = std::min<size_t>(selected_ + items_on_screen, games_.size() - 1);
00329                     adjust_position(selected_);
00330                     set_dirty();
00331                 }
00332                     break;
00333                 case SDLK_HOME:
00334                     selected_ = 0;
00335                     adjust_position(selected_);
00336                     set_dirty();
00337                     break;
00338                 case SDLK_END:
00339                     selected_ = games_.size() - 1;
00340                     adjust_position(selected_);
00341                     set_dirty();
00342                     break;
00343                 default:
00344                     break;
00345             }
00346         }
00347     } else if((event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) || event.type == DOUBLE_CLICK_EVENT) {
00348         int x = 0;
00349         int y = 0;
00350         if(event.type == SDL_MOUSEBUTTONDOWN) {
00351             x = event.button.x;
00352             y = event.button.y;
00353         } else {
00354             x = reinterpret_cast<long>(event.user.data1);
00355             y = reinterpret_cast<long>(event.user.data2);
00356         }
00357         const SDL_Rect& loc = inner_location();
00358 
00359         if(!games_.empty() && point_in_rect(x, y, loc)) {
00360             for(size_t i = visible_range_.first; i != visible_range_.second; ++i) {
00361                 const SDL_Rect& item_rect = get_item_rect(i);
00362 
00363                 if(point_in_rect(x, y, item_rect)) {
00364                     set_focus(true);
00365                     selected_ = i;
00366                     break;
00367                 }
00368             }
00369             if(event.type == DOUBLE_CLICK_EVENT) {
00370                 if (ignore_next_doubleclick_) {
00371                     ignore_next_doubleclick_ = false;
00372                 } else if(selection_is_joinable() || selection_is_observable()) {
00373                     double_clicked_ = true;
00374                     last_was_doubleclick_ = true;
00375                 }
00376             } else if (last_was_doubleclick_) {
00377                 // If we have a double click as the next event, it means
00378                 // this double click was generated from a click that
00379                 // already has helped in generating a double click.
00380                 // ??
00381                 SDL_Event ev;
00382                 SDL_PeepEvents(&ev, 1, SDL_PEEKEVENT,
00383                                SDL_EVENTMASK(DOUBLE_CLICK_EVENT));
00384                 if (ev.type == DOUBLE_CLICK_EVENT) {
00385                     ignore_next_doubleclick_ = true;
00386                 }
00387                 last_was_doubleclick_ = false;
00388             }
00389         }
00390     }
00391 }
00392 
00393 struct minimap_cache_item {
00394 
00395     minimap_cache_item() :
00396         map_data(),
00397         mini_map(),
00398         map_info_size()
00399     {
00400     }
00401 
00402     std::string map_data;
00403     surface mini_map;
00404     std::string map_info_size;
00405 };
00406 
00407 void gamebrowser::set_game_items(const config& cfg, const config& game_config)
00408 {
00409     const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
00410     const bool selection_visible = (selected_ >= visible_range_.first && selected_ <= visible_range_.second);
00411     const std::string selected_game = (selected_ < games_.size()) ? games_[selected_].id : "";
00412 
00413     item_height_ = 100;
00414 
00415     // Don't throw the rendered minimaps away
00416     std::vector<minimap_cache_item> minimap_cache;
00417     for(std::vector<game_item>::iterator oldgame = games_.begin(); oldgame != games_.end(); ++oldgame) {
00418         minimap_cache_item item;
00419         item.map_data = oldgame->map_data;
00420         item.mini_map = oldgame->mini_map;
00421         item.map_info_size = oldgame->map_info_size;
00422         minimap_cache.push_back(item);
00423     }
00424 
00425     games_.clear();
00426 
00427     foreach (const config &game, cfg.child("gamelist").child_range("game"))
00428     {
00429         bool verified = true;
00430         games_.push_back(game_item());
00431         games_.back().password_required = game["password"].to_bool();
00432         games_.back().reloaded = game["savegame"].to_bool();
00433         games_.back().have_era = true;
00434         if (!game["mp_era"].empty())
00435         {
00436             const config &era_cfg = game_config.find_child("era", "id", game["mp_era"]);
00437             utils::string_map symbols;
00438             symbols["era_id"] = game["mp_era"];
00439             if (era_cfg) {
00440                 games_.back().map_info = era_cfg["name"].str();
00441             } else {
00442                 if (!game["require_era"].to_bool(true)) {
00443                     games_.back().have_era = true;
00444                 } else {
00445                     games_.back().have_era = false;
00446                 }
00447                 games_.back().map_info = vgettext("Unknown era: $era_id", symbols);
00448                 verified = false;
00449             }
00450         } else {
00451             games_.back().map_info = _("Unknown era");
00452             verified = false;
00453         }
00454         games_.back().map_data = game["map_data"].str();
00455         if(games_.back().map_data.empty()) {
00456             games_.back().map_data = read_map(game["map"]);
00457         }
00458 
00459         if(! games_.back().map_data.empty()) {
00460             try {
00461                 std::vector<minimap_cache_item>::iterator i;
00462                 bool found = false;
00463                 for(i = minimap_cache.begin(); i != minimap_cache.end() && !found; ++i) {
00464                     if (i->map_data == games_.back().map_data) {
00465                         found = true;
00466                         games_.back().map_info_size = i->map_info_size;
00467                         games_.back().mini_map = i->mini_map;
00468                     }
00469                 }
00470                 if (!found) {
00471                     // Parsing the map and generating the minimap are both cpu expensive
00472                     gamemap map(game_config, games_.back().map_data);
00473                     games_.back().mini_map = image::getMinimap(minimap_size_, minimap_size_, map, 0);
00474                     games_.back().map_info_size = str_cast(map.w()) + utils::unicode_multiplication_sign
00475                         + str_cast(map.h());
00476                 }
00477                 games_.back().map_info += " — " + games_.back().map_info_size;
00478             } catch (incorrect_map_format_error &e) {
00479                 ERR_CF << "illegal map: " << e.message << '\n';
00480                 verified = false;
00481             } catch(twml_exception& e) {
00482                 ERR_CF <<  "map could not be loaded: " << e.dev_message << '\n';
00483                 verified = false;
00484             }
00485         } else {
00486             games_.back().map_info += " — ??×??";
00487         }
00488         games_.back().map_info += " ";
00489         if (!game["mp_scenario"].empty())
00490         {
00491             // check if it's a multiplayer scenario
00492             const config *level_cfg = &game_config.find_child("multiplayer", "id", game["mp_scenario"]);
00493             if (!*level_cfg) {
00494                 // check if it's a user map
00495                 level_cfg = &game_config.find_child("generic_multiplayer", "id", game["mp_scenario"]);
00496             }
00497             if (*level_cfg) {
00498                 games_.back().map_info += (*level_cfg)["name"].str();
00499                 // reloaded games do not match the original scenario hash,
00500                 // so it makes no sense to test them, they always would appear
00501                 // as remote scenarios
00502                 if (map_hashes_ && !games_.back().reloaded) {
00503                     std::string hash = game["hash"];
00504                     bool hash_found = false;
00505                     foreach (const config::attribute &i, map_hashes_.attribute_range()) {
00506                         if (i.first == game["mp_scenario"] && i.second == hash) {
00507                             hash_found = true;
00508                             break;
00509                         }
00510                     }
00511                     if(!hash_found) {
00512                         games_.back().map_info += " — ";
00513                         games_.back().map_info += _("Remote scenario");
00514                         verified = false;
00515                     }
00516                 }
00517             } else {
00518                 utils::string_map symbols;
00519                 symbols["scenario_id"] = game["mp_scenario"];
00520                 games_.back().map_info += vgettext("Unknown scenario: $scenario_id", symbols);
00521                 verified = false;
00522             }
00523         } else {
00524             games_.back().map_info += _("Unknown scenario");
00525             verified = false;
00526         }
00527         if (games_.back().reloaded) {
00528             games_.back().map_info += " — ";
00529             games_.back().map_info += _("Reloaded game");
00530             verified = false;
00531         }
00532         games_.back().id = game["id"].str();
00533         games_.back().name = game["name"].str();
00534         std::string turn = game["turn"];
00535         std::string slots = game["slots"];
00536         games_.back().vacant_slots = lexical_cast_default<size_t>(slots, 0);
00537         games_.back().current_turn = 0;
00538         if (!turn.empty()) {
00539             games_.back().started = true;
00540             int index = turn.find_first_of('/');
00541             if (index > -1){
00542                 const std::string current_turn = turn.substr(0, index);
00543                 games_.back().current_turn = lexical_cast<unsigned int>(current_turn);
00544             }
00545             games_.back().status = _("Turn ") + turn;
00546         } else {
00547             games_.back().started = false;
00548             if (games_.back().vacant_slots > 0) {
00549                 games_.back().status = std::string(_n("Vacant Slot:", "Vacant Slots:",
00550                         games_.back().vacant_slots)) + " " + slots;
00551                 if (games_.back().password_required) {
00552                     games_.back().status += std::string(" (") + std::string(_("Password Required")) + ")";
00553                 }
00554             }
00555         }
00556 
00557         games_.back().use_map_settings = game["mp_use_map_settings"].to_bool();
00558         games_.back().gold = game["mp_village_gold"].str();
00559         if (game["mp_fog"].to_bool()) {
00560             games_.back().vision = _("Fog");
00561             games_.back().fog = true;
00562             if (game["mp_shroud"].to_bool()) {
00563                 games_.back().vision += "/";
00564                 games_.back().vision += _("Shroud");
00565                 games_.back().shroud = true;
00566             } else {
00567                 games_.back().shroud = false;
00568             }
00569         } else if (game["mp_shroud"].to_bool()) {
00570             games_.back().vision = _("Shroud");
00571             games_.back().fog = false;
00572             games_.back().shroud = true;
00573         } else {
00574             games_.back().vision = _("none");
00575             games_.back().fog = false;
00576             games_.back().shroud = false;
00577         }
00578         if (game["mp_countdown"].to_bool()) {
00579             games_.back().time_limit = game["mp_countdown_init_time"].str() + " / +"
00580                 + game["mp_countdown_turn_bonus"].str() + " "
00581                 + game["mp_countdown_action_bonus"].str();
00582         } else {
00583             games_.back().time_limit = "";
00584         }
00585         games_.back().xp = game["experience_modifier"].str() + "%";
00586         games_.back().observers = game["observer"].to_bool(true);
00587         games_.back().shuffle_sides = game["shuffle_sides"].to_bool(true);
00588         games_.back().verified = verified;
00589 
00590         // Hack...
00591         if(preferences::fi_invert() ? game_matches_filter(games_.back(), cfg) : !game_matches_filter(games_.back(), cfg)) games_.pop_back();
00592     }
00593     set_full_size(games_.size());
00594     set_shown_size(inner_location().h / row_height());
00595 
00596     // Try to preserve the game selection
00597     if (!selected_game.empty()) {
00598         for (unsigned int i=0; i < games_.size(); i++) {
00599             if (games_[i].id == selected_game) {
00600                 selected_ = i;
00601                 break;
00602             }
00603         }
00604     }
00605     if(selected_ >= games_.size())
00606         selected_ = std::max<long>(static_cast<long>(games_.size()) - 1, 0);
00607 
00608     if (scrolled_to_max) {
00609         set_position(get_max_position());
00610     } else {
00611         // Keep the selected game visible if it was visible before
00612         if (selection_visible && (visible_range_.first > selected_
00613                                   || visible_range_.second < selected_)) {
00614             set_position(selected_);
00615         }
00616     }
00617     scroll(get_position());
00618     set_dirty();
00619 }
00620 
00621 void gamebrowser::select_game(const std::string& id) {
00622     if (id.empty()) return;
00623 
00624     for (unsigned int i=0; i < games_.size(); i++) {
00625         if (games_[i].id == id) {
00626             selected_ = i;
00627             break;
00628         }
00629     }
00630     adjust_position(selected_);
00631     set_dirty();
00632 }
00633 
00634 bool gamebrowser::game_matches_filter(const game_item& i, const config& cfg) {
00635 
00636     if(!preferences::filter_lobby()) return true;
00637 
00638     if(preferences::fi_vacant_slots() && i.vacant_slots == 0) return false;
00639 
00640     if(preferences::fi_friends_in_game()) {
00641         bool found_friend = false;
00642         foreach(const config &user, cfg.child_range("user")) {
00643             if(preferences::is_friend(user["name"]) && user["game_id"] == i.id) {
00644                 found_friend = true;
00645                 break;
00646             }
00647         }
00648         if(!found_friend) return false;
00649     }
00650 
00651     if(!preferences::fi_text().empty()) {
00652         bool found_match = true;
00653         foreach(const std::string& search_string, utils::split(preferences::fi_text(), ' ', utils::STRIP_SPACES)) {
00654             if(std::search(i.map_info.begin(), i.map_info.end(), search_string.begin(), search_string.end(), chars_equal_insensitive) == i.map_info.end() &&
00655                     std::search(i.name.begin(), i.name.end(), search_string.begin(), search_string.end(), chars_equal_insensitive) == i.name.end()) {
00656                 found_match = false;
00657                 break;
00658             }
00659         }
00660         if(!found_match) return false;
00661     }
00662 
00663     return true;
00664 }
00665 
00666 lobby::lobby_sorter::lobby_sorter(const config& cfg) : cfg_(cfg)
00667 {
00668     set_alpha_sort(1);
00669 }
00670 
00671 bool lobby::lobby_sorter::column_sortable(int column) const
00672 {
00673     switch(column)
00674     {
00675     case MAP_COLUMN:
00676     case STATUS_COLUMN:
00677         return true;
00678     default:
00679         return basic_sorter::column_sortable(column);
00680     }
00681 }
00682 
00683 bool lobby::lobby_sorter::less(int column, const gui::menu::item& row1, const gui::menu::item& row2) const
00684 {
00685     const config &list = cfg_.child("gamelist");
00686     if (!list) {
00687         return false;
00688     }
00689 
00690     size_t nb = list.child_count("game");
00691     if(row1.id >= nb || row2.id >= nb) {
00692         return false;
00693     }
00694 
00695     config::const_child_iterator gi = list.child_range("game").first, gs = gi;
00696     std::advance(gi, row1.id);
00697     const config &game1 = *gi;
00698     gi = gs;
00699     std::advance(gi, row2.id);
00700     const config &game2 = *gi;
00701 
00702     if(column == MAP_COLUMN) {
00703         size_t mapsize1 = game1["map_data"].str().size();
00704         if(mapsize1 == 0) {
00705             mapsize1 = game1["map"].str().size();
00706         }
00707 
00708         size_t mapsize2 = game2["map_data"].str().size();
00709         if(mapsize2 == 0) {
00710             mapsize2 = game2["map"].str().size();
00711         }
00712 
00713         return mapsize1 < mapsize2;
00714 
00715     } else if(column == STATUS_COLUMN) {
00716         int nslots1 = game1["slots"].to_int();
00717         int nslots2 = game2["slots"].to_int();
00718 
00719         int turn1 = game1["turn"].to_int();
00720         int turn2 = game2["turn"].to_int();
00721 
00722         if(nslots1 > nslots2) {
00723             return true;
00724         } else if(nslots1 < nslots2) {
00725             return false;
00726         } else {
00727             return turn1 < turn2;
00728         }
00729     }
00730 
00731     return basic_sorter::less(column,row1,row2);
00732 }
00733 
00734 lobby::lobby(game_display& disp, const config& cfg, chat& c, config& gamelist) :
00735     mp::ui(disp, _("Game Lobby"), cfg, c, gamelist),
00736 
00737     game_vacant_slots_(),
00738     game_observers_(),
00739 
00740     observe_game_(disp.video(), _("Observe Game")),
00741     join_game_(disp.video(), _("Join Game")),
00742     create_game_(disp.video(), _("Create Game")),
00743     skip_replay_(disp.video(), _("Quick replays"), gui::button::TYPE_CHECK),
00744     game_preferences_(disp.video(), _("Preferences")),
00745     quit_game_(disp.video(), _("Quit")),
00746     apply_filter_(disp.video(), _("Apply filter"), gui::button::TYPE_CHECK),
00747     invert_filter_(disp.video(), _("Invert"), gui::button::TYPE_CHECK),
00748     vacant_slots_(disp.video(), _("Vacant slots"), gui::button::TYPE_CHECK),
00749     friends_in_game_(disp.video(), _("Friends in game"), gui::button::TYPE_CHECK),
00750     filter_label_(disp.video(), _("Search:")),
00751     filter_text_(disp.video(), 150),
00752     last_selected_game_(-1), sorter_(gamelist),
00753     games_menu_(disp.video(),cfg.child("multiplayer_hashes")),
00754     minimaps_(),
00755     search_string_(preferences::fi_text())
00756 {
00757     skip_replay_.set_check(preferences::skip_mp_replay());
00758     skip_replay_.set_help_string(_("Skip quickly to the active turn when observing"));
00759 
00760     apply_filter_.set_check(preferences::filter_lobby());
00761     apply_filter_.set_help_string(_("Enable the games filter. If unchecked all games are shown, regardless of any filter."));
00762 
00763     invert_filter_.set_check(preferences::fi_invert());
00764     invert_filter_.set_help_string(_("Show all games that do *not* match your filter. Useful for hiding games you are not interested in."));
00765     invert_filter_.enable(apply_filter_.checked());
00766 
00767     vacant_slots_.set_check(preferences::fi_vacant_slots());
00768     vacant_slots_.set_help_string(_("Only show games that have at least one vacant slot"));
00769     vacant_slots_.enable(apply_filter_.checked());
00770 
00771     friends_in_game_.set_check(preferences::fi_friends_in_game());
00772     friends_in_game_.set_help_string(_("Only show games that are played or observed by at least one of your friends"));
00773     friends_in_game_.enable(apply_filter_.checked());
00774 
00775     filter_label_.enable(apply_filter_.checked());
00776 
00777     filter_text_.set_text(search_string_);
00778     filter_text_.set_help_string(_("Only show games whose title or description contain the entered text"));
00779     filter_text_.set_editable(apply_filter_.checked());
00780 
00781     gamelist_updated();
00782     sound::play_music_repeatedly(game_config::lobby_music);
00783 }
00784 
00785 void lobby::hide_children(bool hide)
00786 {
00787     ui::hide_children(hide);
00788 
00789     games_menu_.hide(hide);
00790     observe_game_.hide(hide);
00791     join_game_.hide(hide);
00792     create_game_.hide(hide);
00793     skip_replay_.hide(hide);
00794     game_preferences_.hide(hide);
00795     quit_game_.hide(hide);
00796     apply_filter_.hide(hide);
00797     invert_filter_.hide(hide);
00798     vacant_slots_.hide(hide);
00799     friends_in_game_.hide(hide);
00800     filter_label_.hide(hide);
00801     filter_text_.hide(hide);
00802 }
00803 
00804 void lobby::layout_children(const SDL_Rect& rect)
00805 {
00806     ui::layout_children(rect);
00807 
00808     int btn_space = 5;
00809     int xborder   = 10;
00810     int yborder   = 7;
00811 
00812     // Align to the left border
00813     join_game_.set_location(xscale(xborder), yscale(yborder));
00814     observe_game_.set_location(join_game_.location().x + join_game_.location().w + btn_space, yscale(yborder));
00815     create_game_.set_location(observe_game_.location().x + observe_game_.location().w + btn_space, yscale(yborder));
00816 
00817     // Align 'Quit' to the right border
00818     quit_game_.set_location(xscale(xscale_base - xborder) - quit_game_.location().w, yscale(yborder));
00819 
00820     // Align in the middle between the right and left buttons
00821     int space = (quit_game_.location().x - create_game_.location().x - create_game_.location().w
00822                  - skip_replay_.location().w - game_preferences_.location().w - btn_space) / 2;
00823     if (space < btn_space) space = btn_space;
00824     skip_replay_.set_location(create_game_.location().x + create_game_.location().w + space, yscale(yborder));
00825     game_preferences_.set_location(quit_game_.location().x - game_preferences_.location().w - space, yscale(yborder));
00826 
00827     games_menu_.set_location(client_area().x, client_area().y + title().height());
00828     games_menu_.set_measurements(client_area().w, client_area().h
00829             - title().height() - gui::ButtonVPadding
00830             - apply_filter_.location().h
00831             );
00832 
00833     apply_filter_.set_location(client_area().x, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
00834     invert_filter_.set_location(client_area().x + apply_filter_.location().w + btn_space, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
00835     vacant_slots_.set_location(client_area().w - apply_filter_.location().w, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
00836     friends_in_game_.set_location(vacant_slots_.location().x - friends_in_game_.location().w - btn_space, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
00837     filter_text_.set_location(friends_in_game_.location().x - filter_text_.location().w - btn_space * 4, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
00838     filter_label_.set_location(filter_text_.location().x - filter_label_.location().w - btn_space, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding
00839             + (apply_filter_.location().h - filter_label_.location().h) / 2);
00840 
00841 }
00842 
00843 void lobby::gamelist_updated(bool silent)
00844 {
00845     ui::gamelist_updated(silent);
00846     const config &list = gamelist().child("gamelist");
00847     if (!list) {
00848         // No gamelist yet. Do not update anything.
00849         return;
00850     }
00851     games_menu_.set_game_items(gamelist(), game_config());
00852     join_game_.enable(games_menu_.selection_is_joinable());
00853     observe_game_.enable(games_menu_.selection_is_observable());
00854 }
00855 
00856 void lobby::process_event()
00857 {
00858     join_game_.enable(games_menu_.selection_is_joinable());
00859     observe_game_.enable(games_menu_.selection_is_observable());
00860 
00861     const bool observe = (observe_game_.pressed() || (games_menu_.selected() && !games_menu_.selection_is_joinable())) && games_menu_.selection_is_observable();
00862     const bool join = (join_game_.pressed() || games_menu_.selected()) && games_menu_.selection_is_joinable();
00863     games_menu_.reset_selection();
00864     preferences::set_skip_mp_replay(skip_replay_.checked());
00865     playmp_controller::set_replay_last_turn(0);
00866     preferences::set_message_private(false);
00867 
00868     int selected_game = games_menu_.selection();
00869     if (selected_game != last_selected_game_) {
00870         if (games_menu_.empty()) {
00871             set_selected_game("");
00872         } else {
00873             set_selected_game(games_menu_.selected_game().id);
00874         }
00875         ui::gamelist_updated();
00876         last_selected_game_ = selected_game;
00877     }
00878 
00879     if(selected_user_changed()) {
00880         set_selected_user_changed(false);
00881         games_menu_.select_game(get_selected_user_game());
00882     }
00883 
00884     if(join || observe) {
00885         const int selected = games_menu_.selection();
00886         if(!games_menu_.empty() && selected >= 0) {
00887             gamebrowser::game_item game = games_menu_.selected_game();
00888 
00889             std::string password;
00890             if(join && game.password_required) {
00891                 const int res = gui::show_dialog(disp_, NULL, _("Password Required"),
00892                           _("Joining this game requires a password."),
00893                           gui::OK_CANCEL, NULL, NULL, _("Password: "), &password);
00894                 if(res != 0) {
00895                     return;
00896                 }
00897             }
00898 
00899             config response;
00900             config& join = response.add_child("join");
00901             join["id"] = game.id;
00902             join["observe"] = observe;
00903 
00904             if(!password.empty()) {
00905                 join["password"] = password;
00906             }
00907             network::send_data(response, 0);
00908 
00909             if(observe) {
00910                 if (game.started){
00911                     playmp_controller::set_replay_last_turn(game.current_turn);
00912                 }
00913                 set_result(OBSERVE);
00914             } else {
00915                 set_result(JOIN);
00916             }
00917         }
00918         return;
00919     }
00920 
00921     if(create_game_.pressed()) {
00922         set_result(CREATE);
00923         return;
00924     }
00925 
00926     if(game_preferences_.pressed()) {
00927         set_result(PREFERENCES);
00928         return;
00929     }
00930 
00931     if(quit_game_.pressed()) {
00932         recorder.set_skip(false);
00933         set_result(QUIT);
00934         return;
00935     }
00936 
00937     if(apply_filter_.pressed()) {
00938         preferences::set_filter_lobby(apply_filter_.checked());
00939         invert_filter_.enable(apply_filter_.checked());
00940         vacant_slots_.enable(apply_filter_.checked());
00941         friends_in_game_.enable(apply_filter_.checked());
00942         filter_label_.enable(apply_filter_.checked());
00943 
00944         /** @todo I am aware that the box is not grayed out even though
00945                   it definitely should be. This is because the textbox
00946                   class currently does not really have an easy way to do
00947                   that. I'll have to look into this.
00948         */
00949         filter_text_.set_editable(apply_filter_.checked());
00950         gamelist_updated();
00951         return;
00952     }
00953 
00954     if(invert_filter_.pressed()) {
00955         preferences::set_fi_invert(invert_filter_.checked());
00956         gamelist_updated();
00957         return;
00958     }
00959 
00960     if(vacant_slots_.pressed()) {
00961         preferences::set_fi_vacant_slots(vacant_slots_.checked());
00962         gamelist_updated();
00963         return;
00964     }
00965 
00966     if(friends_in_game_.pressed()) {
00967         preferences::set_fi_friends_in_game(friends_in_game_.checked());
00968         gamelist_updated();
00969         return;
00970     }
00971 
00972     if(search_string_ != filter_text_.text()) {
00973         search_string_ = filter_text_.text();
00974         preferences::set_fi_text(search_string_);
00975         gamelist_updated();
00976     }
00977 
00978 }
00979 
00980 void lobby::process_network_data(const config& data, const network::connection sock)
00981 {
00982     ui::process_network_data(data, sock);
00983 
00984     // Invalidate game selection for the player list
00985     last_selected_game_ = -1;
00986 }
00987 
00988 } // end namespace mp
00989 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:06 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs