mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-10 22:36:59 +00:00
990 lines
30 KiB
C++
990 lines
30 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2007 - 2009
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/** @file multiplayer_lobby.cpp
|
|
* A section on the server where players can chat, create and join games.
|
|
*/
|
|
|
|
#include "global.hpp"
|
|
|
|
#include "filesystem.hpp"
|
|
#include "foreach.hpp"
|
|
#include "game_preferences.hpp"
|
|
#include "lobby_preferences.hpp"
|
|
#include "map_exception.hpp"
|
|
#include "marked-up_text.hpp"
|
|
#include "minimap.hpp"
|
|
#include "multiplayer_lobby.hpp"
|
|
#include "gettext.hpp"
|
|
#include "log.hpp"
|
|
#include "playmp_controller.hpp"
|
|
#include "sound.hpp"
|
|
#include "wml_exception.hpp"
|
|
#include "formula_string_utils.hpp"
|
|
|
|
static lg::log_domain log_config("config");
|
|
#define ERR_CF LOG_STREAM(err, log_config)
|
|
|
|
namespace {
|
|
std::vector<std::string> empty_string_vector;
|
|
}
|
|
|
|
namespace mp {
|
|
gamebrowser::gamebrowser(CVideo& video, const config &map_hashes) :
|
|
menu(video, empty_string_vector, false, -1, -1, NULL, &menu::bluebg_style),
|
|
gold_icon_locator_("themes/gold.png"),
|
|
xp_icon_locator_("themes/units.png"),
|
|
vision_icon_locator_("misc/invisible.png"),
|
|
time_limit_icon_locator_("themes/sand-clock.png"),
|
|
observer_icon_locator_("misc/eye.png"),
|
|
no_observer_icon_locator_("misc/no_observer.png"),
|
|
shuffle_sides_icon_locator_("misc/shuffle-sides.png"),
|
|
map_hashes_(map_hashes),
|
|
item_height_(100),
|
|
margin_(5),
|
|
minimap_size_(item_height_ - 2*margin_),
|
|
h_padding_(5),
|
|
header_height_(20),
|
|
selected_(0),
|
|
visible_range_(std::pair<size_t,size_t>(0,0)),
|
|
games_(),
|
|
redraw_items_(),
|
|
widths_(),
|
|
double_clicked_(false),
|
|
ignore_next_doubleclick_(false),
|
|
last_was_doubleclick_(false)
|
|
{
|
|
set_numeric_keypress_selection(false);
|
|
}
|
|
|
|
void gamebrowser::set_inner_location(const SDL_Rect& rect)
|
|
{
|
|
set_full_size(games_.size());
|
|
set_shown_size(rect.h / row_height());
|
|
bg_register(rect);
|
|
scroll(get_position());
|
|
}
|
|
|
|
void gamebrowser::scroll(unsigned int pos)
|
|
{
|
|
if(pos < games_.size()) {
|
|
visible_range_.first = pos;
|
|
visible_range_.second = std::min<size_t>(pos + inner_location().h / row_height(), games_.size());
|
|
set_dirty();
|
|
}
|
|
}
|
|
|
|
SDL_Rect gamebrowser::get_item_rect(size_t index) const {
|
|
if(index < visible_range_.first || index > visible_range_.second) {
|
|
const SDL_Rect res = { 0, 0, 0, 0 };
|
|
return res;
|
|
}
|
|
const SDL_Rect& loc = inner_location();
|
|
return create_rect(
|
|
loc.x
|
|
, loc.y + (index - visible_range_.first) * row_height()
|
|
, loc.w
|
|
, row_height());
|
|
}
|
|
|
|
void gamebrowser::draw()
|
|
{
|
|
if(hidden())
|
|
return;
|
|
if(dirty()) {
|
|
bg_restore();
|
|
util::scoped_ptr<clip_rect_setter> clipper(NULL);
|
|
if(clip_rect())
|
|
clipper.assign(new clip_rect_setter(video().getSurface(), clip_rect()));
|
|
draw_contents();
|
|
update_rect(location());
|
|
set_dirty(false);
|
|
}
|
|
}
|
|
|
|
void gamebrowser::draw_contents()
|
|
{
|
|
if(!games_.empty()) {
|
|
for(size_t i = visible_range_.first; i != visible_range_.second; ++i) {
|
|
style_->draw_row(*this,i,get_item_rect(i),(i==selected_)? SELECTED_ROW : NORMAL_ROW);
|
|
}
|
|
} else {
|
|
const SDL_Rect rect = inner_location();
|
|
font::draw_text(&video(), rect, font::SIZE_NORMAL, font::NORMAL_COLOR, _("--no games open--"), rect.x + margin_, rect.y + margin_);
|
|
}
|
|
}
|
|
|
|
void gamebrowser::draw_row(const size_t index, const SDL_Rect& item_rect, ROW_TYPE /*type*/) {
|
|
const game_item& game = games_[index];
|
|
int xpos = item_rect.x + margin_;
|
|
int ypos = item_rect.y + margin_;
|
|
std::string no_era_string = "";
|
|
// Draw minimaps
|
|
if (game.mini_map != NULL) {
|
|
int minimap_x = xpos + (minimap_size_ - game.mini_map->w)/2;
|
|
int minimap_y = ypos + (minimap_size_ - game.mini_map->h)/2;
|
|
video().blit_surface(minimap_x, minimap_y, game.mini_map);
|
|
}
|
|
xpos += minimap_size_ + margin_;
|
|
|
|
// Set font color
|
|
SDL_Color font_color;
|
|
if (game.vacant_slots > 0) {
|
|
if (game.reloaded || game.started) {
|
|
font_color = font::YELLOW_COLOR;
|
|
} else {
|
|
font_color = font::GOOD_COLOR;
|
|
}
|
|
} else {
|
|
if (game.observers) {
|
|
font_color = font::NORMAL_COLOR;
|
|
} else {
|
|
font_color = font::BAD_COLOR;
|
|
}
|
|
}
|
|
if(!game.have_era && font_color != font::BAD_COLOR) {
|
|
font_color = font::DISABLED_COLOR;
|
|
no_era_string = _(" (Unknown Era)");
|
|
}
|
|
|
|
const surface status_text(font::get_rendered_text(game.status,
|
|
font::SIZE_NORMAL, font_color));
|
|
const int status_text_width = status_text ? status_text->w : 0;
|
|
|
|
// First line: draw game name
|
|
const surface name_surf(font::get_rendered_text(
|
|
font::make_text_ellipsis(game.name + no_era_string, font::SIZE_PLUS,
|
|
(item_rect.x + item_rect.w) - xpos - margin_ - status_text_width - h_padding_),
|
|
font::SIZE_PLUS, font_color));
|
|
video().blit_surface(xpos, ypos, name_surf);
|
|
|
|
// Draw status text
|
|
if(status_text) {
|
|
// Align the bottom of the text with the game name
|
|
video().blit_surface(item_rect.x + item_rect.w - margin_ - status_text_width,
|
|
ypos + name_surf->h - status_text->h, status_text);
|
|
}
|
|
|
|
// Second line
|
|
ypos = item_rect.y + item_rect.h/2;
|
|
|
|
// Draw map info
|
|
const surface map_info_surf(font::get_rendered_text(
|
|
font::make_text_ellipsis(game.map_info, font::SIZE_NORMAL,
|
|
(item_rect.x + item_rect.w) - xpos - margin_),
|
|
font::SIZE_NORMAL, font::NORMAL_COLOR));
|
|
if(map_info_surf) {
|
|
video().blit_surface(xpos, ypos - map_info_surf->h/2, map_info_surf);
|
|
}
|
|
|
|
// Third line
|
|
ypos = item_rect.y + item_rect.h - margin_;
|
|
|
|
// Draw observer icon
|
|
const surface observer_icon(image::get_image(game.observers
|
|
? observer_icon_locator_ : no_observer_icon_locator_));
|
|
if(observer_icon) {
|
|
video().blit_surface(xpos, ypos - observer_icon->h, observer_icon);
|
|
|
|
// Set ypos to the middle of the line, so that
|
|
// all text and icons can be aligned symmetrical to it
|
|
ypos -= observer_icon->h/2;
|
|
xpos += observer_icon->w + 2 * h_padding_;
|
|
}
|
|
|
|
// Draw shuffle icon
|
|
if (game.shuffle_sides)
|
|
{
|
|
const surface shuffle_icon(image::get_image(shuffle_sides_icon_locator_));
|
|
if(shuffle_icon) {
|
|
video().blit_surface(xpos, ypos - shuffle_icon->h/2, shuffle_icon);
|
|
|
|
xpos += shuffle_icon->w + 2 * h_padding_;
|
|
}
|
|
}
|
|
|
|
// Draw gold icon
|
|
const surface gold_icon(image::get_image(gold_icon_locator_));
|
|
if(gold_icon) {
|
|
video().blit_surface(xpos, ypos - gold_icon->h/2, gold_icon);
|
|
|
|
xpos += gold_icon->w + h_padding_;
|
|
}
|
|
|
|
// Draw gold text
|
|
const surface gold_text(font::get_rendered_text(game.gold, font::SIZE_NORMAL,
|
|
game.use_map_settings ? font::GRAY_COLOR : font::NORMAL_COLOR));
|
|
if(gold_text) {
|
|
video().blit_surface(xpos, ypos - gold_text->h/2, gold_text);
|
|
|
|
xpos += gold_text->w + 2 * h_padding_;
|
|
}
|
|
|
|
// Draw xp icon
|
|
const surface xp_icon(image::get_image(xp_icon_locator_));
|
|
if(xp_icon) {
|
|
video().blit_surface(xpos, ypos - xp_icon->h/2, xp_icon);
|
|
|
|
xpos += xp_icon->w + h_padding_;
|
|
}
|
|
|
|
// Draw xp text
|
|
const surface xp_text(font::get_rendered_text(game.xp, font::SIZE_NORMAL, font::NORMAL_COLOR));
|
|
if(xp_text) {
|
|
video().blit_surface(xpos, ypos - xp_text->h/2, xp_text);
|
|
|
|
xpos += xp_text->w + 2 * h_padding_;
|
|
}
|
|
|
|
if(!game.time_limit.empty()) {
|
|
// Draw time icon
|
|
const surface time_icon(image::get_image(time_limit_icon_locator_));
|
|
video().blit_surface(xpos, ypos - time_icon->h/2, time_icon);
|
|
|
|
xpos += time_icon->w + h_padding_;
|
|
|
|
// Draw time text
|
|
const surface time_text(font::get_rendered_text(game.time_limit,
|
|
font::SIZE_NORMAL, font::NORMAL_COLOR));
|
|
video().blit_surface(xpos, ypos - time_text->h/2, time_text);
|
|
|
|
xpos += time_text->w + 2 * h_padding_;
|
|
}
|
|
|
|
// Draw vision icon
|
|
const surface vision_icon(image::get_image(vision_icon_locator_));
|
|
if(vision_icon) {
|
|
video().blit_surface(xpos, ypos - vision_icon->h/2, vision_icon);
|
|
|
|
xpos += vision_icon->w + h_padding_;
|
|
}
|
|
|
|
// Draw vision text
|
|
const surface vision_text(font::get_rendered_text(
|
|
font::make_text_ellipsis(game.vision, font::SIZE_NORMAL,
|
|
(item_rect.x + item_rect.w) - xpos - margin_),
|
|
font::SIZE_NORMAL,
|
|
game.use_map_settings ? font::GRAY_COLOR : font::NORMAL_COLOR));
|
|
if(vision_text) {
|
|
video().blit_surface(xpos, ypos - vision_text->h/2, vision_text);
|
|
}
|
|
|
|
// Draw map settings text
|
|
if (game.use_map_settings) {
|
|
xpos += vision_text->w + 4 * h_padding_;
|
|
const surface map_settings_text(font::get_rendered_text(
|
|
font::make_text_ellipsis(_("Use map settings"), font::SIZE_NORMAL,
|
|
(item_rect.x + item_rect.w) - xpos - margin_),
|
|
font::SIZE_NORMAL,
|
|
(game.verified && game.vacant_slots > 0)
|
|
? font::GOOD_COLOR : font::NORMAL_COLOR));
|
|
video().blit_surface(xpos, ypos - map_settings_text->h/2, map_settings_text);
|
|
}
|
|
}
|
|
|
|
void gamebrowser::handle_event(const SDL_Event& event)
|
|
{
|
|
scrollarea::handle_event(event);
|
|
if(event.type == SDL_KEYDOWN) {
|
|
if(focus(&event) && !games_.empty()) {
|
|
switch(event.key.keysym.sym) {
|
|
case SDLK_UP:
|
|
if(selected_ > 0) {
|
|
--selected_;
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
}
|
|
break;
|
|
case SDLK_DOWN:
|
|
if(selected_ < games_.size() - 1) {
|
|
++selected_;
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
}
|
|
break;
|
|
case SDLK_PAGEUP:
|
|
{
|
|
const long items_on_screen = visible_range_.second - visible_range_.first;
|
|
selected_ = static_cast<size_t>(std::max<long>(static_cast<long>(selected_) - items_on_screen, 0));
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
}
|
|
break;
|
|
case SDLK_PAGEDOWN:
|
|
{
|
|
const size_t items_on_screen = visible_range_.second - visible_range_.first;
|
|
selected_ = std::min<size_t>(selected_ + items_on_screen, games_.size() - 1);
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
}
|
|
break;
|
|
case SDLK_HOME:
|
|
selected_ = 0;
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
break;
|
|
case SDLK_END:
|
|
selected_ = games_.size() - 1;
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else if((event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) || event.type == DOUBLE_CLICK_EVENT) {
|
|
int x = 0;
|
|
int y = 0;
|
|
if(event.type == SDL_MOUSEBUTTONDOWN) {
|
|
x = event.button.x;
|
|
y = event.button.y;
|
|
} else {
|
|
x = reinterpret_cast<long>(event.user.data1);
|
|
y = reinterpret_cast<long>(event.user.data2);
|
|
}
|
|
const SDL_Rect& loc = inner_location();
|
|
|
|
if(!games_.empty() && point_in_rect(x, y, loc)) {
|
|
for(size_t i = visible_range_.first; i != visible_range_.second; ++i) {
|
|
const SDL_Rect& item_rect = get_item_rect(i);
|
|
|
|
if(point_in_rect(x, y, item_rect)) {
|
|
set_focus(true);
|
|
selected_ = i;
|
|
break;
|
|
}
|
|
}
|
|
if(event.type == DOUBLE_CLICK_EVENT) {
|
|
if (ignore_next_doubleclick_) {
|
|
ignore_next_doubleclick_ = false;
|
|
} else if(selection_is_joinable() || selection_is_observable()) {
|
|
double_clicked_ = true;
|
|
last_was_doubleclick_ = true;
|
|
}
|
|
} else if (last_was_doubleclick_) {
|
|
// If we have a double click as the next event, it means
|
|
// this double click was generated from a click that
|
|
// already has helped in generating a double click.
|
|
// ??
|
|
SDL_Event ev;
|
|
SDL_PeepEvents(&ev, 1, SDL_PEEKEVENT,
|
|
SDL_EVENTMASK(DOUBLE_CLICK_EVENT));
|
|
if (ev.type == DOUBLE_CLICK_EVENT) {
|
|
ignore_next_doubleclick_ = true;
|
|
}
|
|
last_was_doubleclick_ = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct minimap_cache_item {
|
|
|
|
minimap_cache_item() :
|
|
map_data(),
|
|
mini_map(),
|
|
map_info_size()
|
|
{
|
|
}
|
|
|
|
std::string map_data;
|
|
surface mini_map;
|
|
std::string map_info_size;
|
|
};
|
|
|
|
void gamebrowser::set_game_items(const config& cfg, const config& game_config)
|
|
{
|
|
const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
|
|
const bool selection_visible = (selected_ >= visible_range_.first && selected_ <= visible_range_.second);
|
|
const std::string selected_game = (selected_ < games_.size()) ? games_[selected_].id : "";
|
|
|
|
item_height_ = 100;
|
|
|
|
// Don't throw the rendered minimaps away
|
|
std::vector<minimap_cache_item> minimap_cache;
|
|
for(std::vector<game_item>::iterator oldgame = games_.begin(); oldgame != games_.end(); ++oldgame) {
|
|
minimap_cache_item item;
|
|
item.map_data = oldgame->map_data;
|
|
item.mini_map = oldgame->mini_map;
|
|
item.map_info_size = oldgame->map_info_size;
|
|
minimap_cache.push_back(item);
|
|
}
|
|
|
|
games_.clear();
|
|
|
|
foreach (const config &game, cfg.child("gamelist").child_range("game"))
|
|
{
|
|
bool verified = true;
|
|
games_.push_back(game_item());
|
|
games_.back().password_required = game["password"].to_bool();
|
|
games_.back().reloaded = game["savegame"].to_bool();
|
|
games_.back().have_era = true;
|
|
if (!game["mp_era"].empty())
|
|
{
|
|
const config &era_cfg = game_config.find_child("era", "id", game["mp_era"]);
|
|
utils::string_map symbols;
|
|
symbols["era_id"] = game["mp_era"];
|
|
if (era_cfg) {
|
|
games_.back().map_info = era_cfg["name"].str();
|
|
} else {
|
|
if (!game["require_era"].to_bool(true)) {
|
|
games_.back().have_era = true;
|
|
} else {
|
|
games_.back().have_era = false;
|
|
}
|
|
games_.back().map_info = vgettext("Unknown era: $era_id", symbols);
|
|
verified = false;
|
|
}
|
|
} else {
|
|
games_.back().map_info = _("Unknown era");
|
|
verified = false;
|
|
}
|
|
games_.back().map_data = game["map_data"].str();
|
|
if(games_.back().map_data.empty()) {
|
|
games_.back().map_data = read_map(game["map"]);
|
|
}
|
|
|
|
if(! games_.back().map_data.empty()) {
|
|
try {
|
|
std::vector<minimap_cache_item>::iterator i;
|
|
bool found = false;
|
|
for(i = minimap_cache.begin(); i != minimap_cache.end() && !found; ++i) {
|
|
if (i->map_data == games_.back().map_data) {
|
|
found = true;
|
|
games_.back().map_info_size = i->map_info_size;
|
|
games_.back().mini_map = i->mini_map;
|
|
}
|
|
}
|
|
if (!found) {
|
|
// Parsing the map and generating the minimap are both cpu expensive
|
|
gamemap map(game_config, games_.back().map_data);
|
|
games_.back().mini_map = image::getMinimap(minimap_size_, minimap_size_, map, 0);
|
|
games_.back().map_info_size = str_cast(map.w()) + "x"
|
|
+ str_cast(map.h());
|
|
}
|
|
games_.back().map_info += " - " + games_.back().map_info_size;
|
|
} catch (incorrect_map_format_error &e) {
|
|
ERR_CF << "illegal map: " << e.message << '\n';
|
|
verified = false;
|
|
} catch(twml_exception& e) {
|
|
ERR_CF << "map could not be loaded: " << e.dev_message << '\n';
|
|
verified = false;
|
|
}
|
|
} else {
|
|
games_.back().map_info += " - ??x??";
|
|
}
|
|
games_.back().map_info += " ";
|
|
if (!game["mp_scenario"].empty())
|
|
{
|
|
// check if it's a multiplayer scenario
|
|
const config *level_cfg = &game_config.find_child("multiplayer", "id", game["mp_scenario"]);
|
|
if (!*level_cfg) {
|
|
// check if it's a user map
|
|
level_cfg = &game_config.find_child("generic_multiplayer", "id", game["mp_scenario"]);
|
|
}
|
|
if (*level_cfg) {
|
|
games_.back().map_info += (*level_cfg)["name"].str();
|
|
// reloaded games do not match the original scenario hash,
|
|
// so it makes no sense to test them, they always would appear
|
|
// as remote scenarios
|
|
if (map_hashes_ && !games_.back().reloaded) {
|
|
std::string hash = game["hash"];
|
|
bool hash_found = false;
|
|
foreach (const config::attribute &i, map_hashes_.attribute_range()) {
|
|
if (i.first == game["mp_scenario"] && i.second == hash) {
|
|
hash_found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!hash_found) {
|
|
games_.back().map_info += " - ";
|
|
games_.back().map_info += _("Remote scenario");
|
|
verified = false;
|
|
}
|
|
}
|
|
} else {
|
|
utils::string_map symbols;
|
|
symbols["scenario_id"] = game["mp_scenario"];
|
|
games_.back().map_info += vgettext("Unknown scenario: $scenario_id", symbols);
|
|
verified = false;
|
|
}
|
|
} else {
|
|
games_.back().map_info += _("Unknown scenario");
|
|
verified = false;
|
|
}
|
|
if (games_.back().reloaded) {
|
|
games_.back().map_info += " - ";
|
|
games_.back().map_info += _("Reloaded game");
|
|
verified = false;
|
|
}
|
|
games_.back().id = game["id"].str();
|
|
games_.back().name = game["name"].str();
|
|
std::string turn = game["turn"];
|
|
std::string slots = game["slots"];
|
|
games_.back().vacant_slots = lexical_cast_default<size_t>(slots, 0);
|
|
games_.back().current_turn = 0;
|
|
if (!turn.empty()) {
|
|
games_.back().started = true;
|
|
int index = turn.find_first_of('/');
|
|
if (index > -1){
|
|
const std::string current_turn = turn.substr(0, index);
|
|
games_.back().current_turn = lexical_cast<unsigned int>(current_turn);
|
|
}
|
|
games_.back().status = _("Turn ") + turn;
|
|
} else {
|
|
games_.back().started = false;
|
|
if (games_.back().vacant_slots > 0) {
|
|
games_.back().status = std::string(_n("Vacant Slot:", "Vacant Slots:",
|
|
games_.back().vacant_slots)) + " " + slots;
|
|
if (games_.back().password_required) {
|
|
games_.back().status += std::string(" (") + std::string(_("Password Required")) + ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
games_.back().use_map_settings = game["mp_use_map_settings"].to_bool();
|
|
games_.back().gold = game["mp_village_gold"].str();
|
|
if (game["mp_fog"].to_bool()) {
|
|
games_.back().vision = _("Fog");
|
|
games_.back().fog = true;
|
|
if (game["mp_shroud"].to_bool()) {
|
|
games_.back().vision += "/";
|
|
games_.back().vision += _("Shroud");
|
|
games_.back().shroud = true;
|
|
} else {
|
|
games_.back().shroud = false;
|
|
}
|
|
} else if (game["mp_shroud"].to_bool()) {
|
|
games_.back().vision = _("Shroud");
|
|
games_.back().fog = false;
|
|
games_.back().shroud = true;
|
|
} else {
|
|
games_.back().vision = _("none");
|
|
games_.back().fog = false;
|
|
games_.back().shroud = false;
|
|
}
|
|
if (game["mp_countdown"].to_bool()) {
|
|
games_.back().time_limit = game["mp_countdown_init_time"].str() + " / +"
|
|
+ game["mp_countdown_turn_bonus"].str() + " "
|
|
+ game["mp_countdown_action_bonus"].str();
|
|
} else {
|
|
games_.back().time_limit = "";
|
|
}
|
|
games_.back().xp = game["experience_modifier"].str() + "%";
|
|
games_.back().observers = game["observer"].to_bool(true);
|
|
games_.back().shuffle_sides = game["shuffle_sides"].to_bool(true);
|
|
games_.back().verified = verified;
|
|
|
|
// Hack...
|
|
if(preferences::fi_invert() ? game_matches_filter(games_.back(), cfg) : !game_matches_filter(games_.back(), cfg)) games_.pop_back();
|
|
}
|
|
set_full_size(games_.size());
|
|
set_shown_size(inner_location().h / row_height());
|
|
|
|
// Try to preserve the game selection
|
|
if (!selected_game.empty()) {
|
|
for (unsigned int i=0; i < games_.size(); i++) {
|
|
if (games_[i].id == selected_game) {
|
|
selected_ = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(selected_ >= games_.size())
|
|
selected_ = std::max<long>(static_cast<long>(games_.size()) - 1, 0);
|
|
|
|
if (scrolled_to_max) {
|
|
set_position(get_max_position());
|
|
} else {
|
|
// Keep the selected game visible if it was visible before
|
|
if (selection_visible && (visible_range_.first > selected_
|
|
|| visible_range_.second < selected_)) {
|
|
set_position(selected_);
|
|
}
|
|
}
|
|
scroll(get_position());
|
|
set_dirty();
|
|
}
|
|
|
|
void gamebrowser::select_game(const std::string& id) {
|
|
if (id.empty()) return;
|
|
|
|
for (unsigned int i=0; i < games_.size(); i++) {
|
|
if (games_[i].id == id) {
|
|
selected_ = i;
|
|
break;
|
|
}
|
|
}
|
|
adjust_position(selected_);
|
|
set_dirty();
|
|
}
|
|
|
|
bool gamebrowser::game_matches_filter(const game_item& i, const config& cfg) {
|
|
|
|
if(!preferences::filter_lobby()) return true;
|
|
|
|
if(preferences::fi_vacant_slots() && i.vacant_slots == 0) return false;
|
|
|
|
if(preferences::fi_friends_in_game()) {
|
|
bool found_friend = false;
|
|
foreach(const config &user, cfg.child_range("user")) {
|
|
if(preferences::is_friend(user["name"]) && user["game_id"] == i.id) {
|
|
found_friend = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found_friend) return false;
|
|
}
|
|
|
|
if(!preferences::fi_text().empty()) {
|
|
bool found_match = true;
|
|
foreach(const std::string& search_string, utils::split(preferences::fi_text(), ' ', utils::STRIP_SPACES)) {
|
|
if(std::search(i.map_info.begin(), i.map_info.end(), search_string.begin(), search_string.end(), chars_equal_insensitive) == i.map_info.end() &&
|
|
std::search(i.name.begin(), i.name.end(), search_string.begin(), search_string.end(), chars_equal_insensitive) == i.name.end()) {
|
|
found_match = false;
|
|
break;
|
|
}
|
|
}
|
|
if(!found_match) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
lobby::lobby_sorter::lobby_sorter(const config& cfg) : cfg_(cfg)
|
|
{
|
|
set_alpha_sort(1);
|
|
}
|
|
|
|
bool lobby::lobby_sorter::column_sortable(int column) const
|
|
{
|
|
switch(column)
|
|
{
|
|
case MAP_COLUMN:
|
|
case STATUS_COLUMN:
|
|
return true;
|
|
default:
|
|
return basic_sorter::column_sortable(column);
|
|
}
|
|
}
|
|
|
|
bool lobby::lobby_sorter::less(int column, const gui::menu::item& row1, const gui::menu::item& row2) const
|
|
{
|
|
const config &list = cfg_.child("gamelist");
|
|
if (!list) {
|
|
return false;
|
|
}
|
|
|
|
size_t nb = list.child_count("game");
|
|
if(row1.id >= nb || row2.id >= nb) {
|
|
return false;
|
|
}
|
|
|
|
config::const_child_iterator gi = list.child_range("game").first, gs = gi;
|
|
std::advance(gi, row1.id);
|
|
const config &game1 = *gi;
|
|
gi = gs;
|
|
std::advance(gi, row2.id);
|
|
const config &game2 = *gi;
|
|
|
|
if(column == MAP_COLUMN) {
|
|
size_t mapsize1 = game1["map_data"].str().size();
|
|
if(mapsize1 == 0) {
|
|
mapsize1 = game1["map"].str().size();
|
|
}
|
|
|
|
size_t mapsize2 = game2["map_data"].str().size();
|
|
if(mapsize2 == 0) {
|
|
mapsize2 = game2["map"].str().size();
|
|
}
|
|
|
|
return mapsize1 < mapsize2;
|
|
|
|
} else if(column == STATUS_COLUMN) {
|
|
int nslots1 = game1["slots"].to_int();
|
|
int nslots2 = game2["slots"].to_int();
|
|
|
|
int turn1 = game1["turn"].to_int();
|
|
int turn2 = game2["turn"].to_int();
|
|
|
|
if(nslots1 > nslots2) {
|
|
return true;
|
|
} else if(nslots1 < nslots2) {
|
|
return false;
|
|
} else {
|
|
return turn1 < turn2;
|
|
}
|
|
}
|
|
|
|
return basic_sorter::less(column,row1,row2);
|
|
}
|
|
|
|
lobby::lobby(game_display& disp, const config& cfg, chat& c, config& gamelist) :
|
|
mp::ui(disp, _("Game Lobby"), cfg, c, gamelist),
|
|
|
|
game_vacant_slots_(),
|
|
game_observers_(),
|
|
|
|
observe_game_(disp.video(), _("Observe Game")),
|
|
join_game_(disp.video(), _("Join Game")),
|
|
create_game_(disp.video(), _("Create Game")),
|
|
skip_replay_(disp.video(), _("Quick Replays"), gui::button::TYPE_CHECK),
|
|
game_preferences_(disp.video(), _("Preferences")),
|
|
quit_game_(disp.video(), _("Quit")),
|
|
apply_filter_(disp.video(), _("Apply Filter"), gui::button::TYPE_CHECK),
|
|
invert_filter_(disp.video(), _("Invert"), gui::button::TYPE_CHECK),
|
|
vacant_slots_(disp.video(), _("Vacant Slots"), gui::button::TYPE_CHECK),
|
|
friends_in_game_(disp.video(), _("Friends in Game"), gui::button::TYPE_CHECK),
|
|
filter_label_(disp.video(), _("Search:")),
|
|
filter_text_(disp.video(), 150),
|
|
last_selected_game_(-1), sorter_(gamelist),
|
|
games_menu_(disp.video(),cfg.child("multiplayer_hashes")),
|
|
minimaps_(),
|
|
search_string_(preferences::fi_text())
|
|
{
|
|
skip_replay_.set_check(preferences::skip_mp_replay());
|
|
skip_replay_.set_help_string(_("Skip quickly to the active turn when observing"));
|
|
|
|
apply_filter_.set_check(preferences::filter_lobby());
|
|
apply_filter_.set_help_string(_("Enable the games filter. If unchecked all games are shown, regardless of any filter."));
|
|
|
|
invert_filter_.set_check(preferences::fi_invert());
|
|
invert_filter_.set_help_string(_("Show all games that do *not* match your filter. Useful for hiding games you are not interested in."));
|
|
invert_filter_.enable(apply_filter_.checked());
|
|
|
|
vacant_slots_.set_check(preferences::fi_vacant_slots());
|
|
vacant_slots_.set_help_string(_("Only show games that have at least one vacant slot"));
|
|
vacant_slots_.enable(apply_filter_.checked());
|
|
|
|
friends_in_game_.set_check(preferences::fi_friends_in_game());
|
|
friends_in_game_.set_help_string(_("Only show games that are played or observed by at least one of your friends"));
|
|
friends_in_game_.enable(apply_filter_.checked());
|
|
|
|
filter_label_.enable(apply_filter_.checked());
|
|
|
|
filter_text_.set_text(search_string_);
|
|
filter_text_.set_help_string(_("Only show games whose title or description contain the entered text"));
|
|
filter_text_.set_editable(apply_filter_.checked());
|
|
|
|
gamelist_updated();
|
|
sound::play_music_repeatedly(game_config::lobby_music);
|
|
}
|
|
|
|
void lobby::hide_children(bool hide)
|
|
{
|
|
ui::hide_children(hide);
|
|
|
|
games_menu_.hide(hide);
|
|
observe_game_.hide(hide);
|
|
join_game_.hide(hide);
|
|
create_game_.hide(hide);
|
|
skip_replay_.hide(hide);
|
|
game_preferences_.hide(hide);
|
|
quit_game_.hide(hide);
|
|
apply_filter_.hide(hide);
|
|
invert_filter_.hide(hide);
|
|
vacant_slots_.hide(hide);
|
|
friends_in_game_.hide(hide);
|
|
filter_label_.hide(hide);
|
|
filter_text_.hide(hide);
|
|
}
|
|
|
|
void lobby::layout_children(const SDL_Rect& rect)
|
|
{
|
|
ui::layout_children(rect);
|
|
|
|
int btn_space = 5;
|
|
int xborder = 10;
|
|
int yborder = 7;
|
|
|
|
// Align to the left border
|
|
join_game_.set_location(xscale(xborder), yscale(yborder));
|
|
observe_game_.set_location(join_game_.location().x + join_game_.location().w + btn_space, yscale(yborder));
|
|
create_game_.set_location(observe_game_.location().x + observe_game_.location().w + btn_space, yscale(yborder));
|
|
|
|
// Align 'Quit' to the right border
|
|
quit_game_.set_location(xscale(xscale_base - xborder) - quit_game_.location().w, yscale(yborder));
|
|
|
|
// Align in the middle between the right and left buttons
|
|
int space = (quit_game_.location().x - create_game_.location().x - create_game_.location().w
|
|
- skip_replay_.location().w - game_preferences_.location().w - btn_space) / 2;
|
|
if (space < btn_space) space = btn_space;
|
|
skip_replay_.set_location(create_game_.location().x + create_game_.location().w + space, yscale(yborder));
|
|
game_preferences_.set_location(quit_game_.location().x - game_preferences_.location().w - space, yscale(yborder));
|
|
|
|
games_menu_.set_location(client_area().x, client_area().y + title().height());
|
|
games_menu_.set_measurements(client_area().w, client_area().h
|
|
- title().height() - gui::ButtonVPadding
|
|
- apply_filter_.location().h
|
|
);
|
|
|
|
apply_filter_.set_location(client_area().x, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
|
|
invert_filter_.set_location(client_area().x + apply_filter_.location().w + btn_space, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
|
|
vacant_slots_.set_location(client_area().w - apply_filter_.location().w, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding);
|
|
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);
|
|
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);
|
|
filter_label_.set_location(filter_text_.location().x - filter_label_.location().w - btn_space, games_menu_.location().y + games_menu_.location().h + gui::ButtonVPadding
|
|
+ (apply_filter_.location().h - filter_label_.location().h) / 2);
|
|
|
|
}
|
|
|
|
void lobby::gamelist_updated(bool silent)
|
|
{
|
|
ui::gamelist_updated(silent);
|
|
const config &list = gamelist().child("gamelist");
|
|
if (!list) {
|
|
// No gamelist yet. Do not update anything.
|
|
return;
|
|
}
|
|
games_menu_.set_game_items(gamelist(), game_config());
|
|
join_game_.enable(games_menu_.selection_is_joinable());
|
|
observe_game_.enable(games_menu_.selection_is_observable());
|
|
}
|
|
|
|
void lobby::process_event()
|
|
{
|
|
join_game_.enable(games_menu_.selection_is_joinable());
|
|
observe_game_.enable(games_menu_.selection_is_observable());
|
|
|
|
const bool observe = (observe_game_.pressed() || (games_menu_.selected() && !games_menu_.selection_is_joinable())) && games_menu_.selection_is_observable();
|
|
const bool join = (join_game_.pressed() || games_menu_.selected()) && games_menu_.selection_is_joinable();
|
|
games_menu_.reset_selection();
|
|
preferences::set_skip_mp_replay(skip_replay_.checked());
|
|
playmp_controller::set_replay_last_turn(0);
|
|
preferences::set_message_private(false);
|
|
|
|
int selected_game = games_menu_.selection();
|
|
if (selected_game != last_selected_game_) {
|
|
if (games_menu_.empty()) {
|
|
set_selected_game("");
|
|
} else {
|
|
set_selected_game(games_menu_.selected_game().id);
|
|
}
|
|
ui::gamelist_updated();
|
|
last_selected_game_ = selected_game;
|
|
}
|
|
|
|
if(selected_user_changed()) {
|
|
set_selected_user_changed(false);
|
|
games_menu_.select_game(get_selected_user_game());
|
|
}
|
|
|
|
if(join || observe) {
|
|
const int selected = games_menu_.selection();
|
|
if(!games_menu_.empty() && selected >= 0) {
|
|
gamebrowser::game_item game = games_menu_.selected_game();
|
|
|
|
std::string password;
|
|
if(join && game.password_required) {
|
|
const int res = gui::show_dialog(disp_, NULL, _("Password Required"),
|
|
_("Joining this game requires a password."),
|
|
gui::OK_CANCEL, NULL, NULL, _("Password: "), &password);
|
|
if(res != 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
config response;
|
|
config& join = response.add_child("join");
|
|
join["id"] = game.id;
|
|
join["observe"] = observe;
|
|
|
|
if(!password.empty()) {
|
|
join["password"] = password;
|
|
}
|
|
network::send_data(response, 0);
|
|
|
|
if(observe) {
|
|
if (game.started){
|
|
playmp_controller::set_replay_last_turn(game.current_turn);
|
|
}
|
|
set_result(OBSERVE);
|
|
} else {
|
|
set_result(JOIN);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(create_game_.pressed()) {
|
|
set_result(CREATE);
|
|
return;
|
|
}
|
|
|
|
if(game_preferences_.pressed()) {
|
|
set_result(PREFERENCES);
|
|
return;
|
|
}
|
|
|
|
if(quit_game_.pressed()) {
|
|
recorder.set_skip(false);
|
|
set_result(QUIT);
|
|
return;
|
|
}
|
|
|
|
if(apply_filter_.pressed()) {
|
|
preferences::set_filter_lobby(apply_filter_.checked());
|
|
invert_filter_.enable(apply_filter_.checked());
|
|
vacant_slots_.enable(apply_filter_.checked());
|
|
friends_in_game_.enable(apply_filter_.checked());
|
|
filter_label_.enable(apply_filter_.checked());
|
|
|
|
/** @todo I am aware that the box is not grayed out even though
|
|
it definitely should be. This is because the textbox
|
|
class currently does not really have an easy way to do
|
|
that. I'll have to look into this.
|
|
*/
|
|
filter_text_.set_editable(apply_filter_.checked());
|
|
gamelist_updated();
|
|
return;
|
|
}
|
|
|
|
if(invert_filter_.pressed()) {
|
|
preferences::set_fi_invert(invert_filter_.checked());
|
|
gamelist_updated();
|
|
return;
|
|
}
|
|
|
|
if(vacant_slots_.pressed()) {
|
|
preferences::set_fi_vacant_slots(vacant_slots_.checked());
|
|
gamelist_updated();
|
|
return;
|
|
}
|
|
|
|
if(friends_in_game_.pressed()) {
|
|
preferences::set_fi_friends_in_game(friends_in_game_.checked());
|
|
gamelist_updated();
|
|
return;
|
|
}
|
|
|
|
if(search_string_ != filter_text_.text()) {
|
|
search_string_ = filter_text_.text();
|
|
preferences::set_fi_text(search_string_);
|
|
gamelist_updated();
|
|
}
|
|
|
|
}
|
|
|
|
void lobby::process_network_data(const config& data, const network::connection sock)
|
|
{
|
|
ui::process_network_data(data, sock);
|
|
|
|
// Invalidate game selection for the player list
|
|
last_selected_game_ = -1;
|
|
}
|
|
|
|
} // end namespace mp
|
|
|