wesnoth/src/multiplayer_lobby.cpp
uso 3b5c2d8a45 let the wesnoth server send the game id and not only the game name
if a player joins a game to avoid ambiguity
2007-08-28 16:18:19 +00:00

716 lines
22 KiB
C++

/* $Id$ */
/*
Copyright (C) 2007
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 version 2
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.
*/
#include "global.hpp"
#include "filesystem.hpp"
#include "marked-up_text.hpp"
#include "minimap.hpp"
#include "multiplayer_lobby.hpp"
#include "replay.hpp"
#include "wassert.hpp"
#include "wml_separators.hpp"
#include "game_config.hpp"
#include "gettext.hpp"
#include "playmp_controller.hpp"
#include "show_dialog.hpp"
#include "sound.hpp"
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_("misc/gold.png"),
xp_icon_locator_("misc/units.png"),
vision_icon_locator_("misc/invisible.png"),
time_limit_icon_locator_("misc/sand-clock.png"),
observer_icon_locator_("misc/eye.png"),
no_observer_icon_locator_("misc/no_observer.png"), map_hashes_(map_hashes),
item_height_(100), margin_(5), h_padding_(5),
header_height_(20), selected_(0), visible_range_(std::pair<size_t,size_t>(0,0)),
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 = minimum<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();
const SDL_Rect res = { loc.x, loc.y + (index - visible_range_.first) * row_height(), loc.w, row_height()};
return res;
}
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_COLOUR, _("--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_;
//draw minimaps
video().blit_surface(xpos, ypos, game.mini_map);
xpos += item_height_ + margin_;
//set font color
SDL_Color font_color;
if (game.vacant_slots > 0) {
if (!game.started) {
font_color = font::GOOD_COLOUR;
} else {
font_color = font::YELLOW_COLOUR;
}
} else {
if (game.observers) {
font_color = font::NORMAL_COLOUR;
} else {
font_color = font::BAD_COLOUR;
}
}
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, 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_COLOUR));
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_));
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 gold icon
const surface gold_icon(image::get_image(gold_icon_locator_));
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_COLOUR : font::NORMAL_COLOUR));
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_));
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_COLOUR));
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_COLOUR));
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_));
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_COLOUR : font::NORMAL_COLOUR));
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, font::NORMAL_COLOUR));
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>(maximum<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_ = minimum<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 = (long)event.user.data1;
y = (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 {
std::string map_id;
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());
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_id = oldgame->id;
item.mini_map = oldgame->mini_map;
item.map_info_size = oldgame->map_info_size;
minimap_cache.push_back(item);
}
games_.clear();
config::child_list games = cfg.get_children("game");
config::child_iterator game;
for(game = games.begin(); game != games.end(); ++game) {
games_.push_back(game_item());
if((**game)["mp_era"] != "") {
const config* const era_cfg = game_config.find_child("era", "id", (**game)["mp_era"]);
games_.back().map_info = era_cfg != NULL ? era_cfg->get_attribute("name") : _("Unknown era");
} else {
games_.back().map_info = _("Unknown era");
}
games_.back().map_data = (**game)["map_data"];
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_id == games_.back().id) {
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(item_height_ - margin_, item_height_ - 2 * margin_, map, 0);
games_.back().map_info_size = lexical_cast_default<std::string, int>(map.w(), "??") + std::string("x") + lexical_cast_default<std::string, int>(map.h(), "??");
}
games_.back().map_info += " - " + games_.back().map_info_size;
} catch(gamemap::incorrect_format_exception &e) {
std::cerr << "illegal map: " << e.msg_ << "\n";
}
} else {
games_.back().map_info += " - ??x??";
}
games_.back().map_info += " ";
if((**game)["mp_scenario"] != "" && map_hashes_) {
const config* level_cfg = game_config.find_child("generic_multiplayer", "id", (**game)["mp_scenario"]);
if(level_cfg == NULL)
level_cfg = game_config.find_child("multiplayer", "id", (**game)["mp_scenario"]);
games_.back().map_info += level_cfg != NULL ? level_cfg->get_attribute("name") : _("Unknown scenario");
if(level_cfg) {
const std::string& hash = (**game)["hash"];
bool hash_found = false;
for(string_map::const_iterator i = map_hashes_->values.begin(); i != map_hashes_->values.end(); ++i) {
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");
}
}
} else {
games_.back().map_info += _("Unknown scenario");
}
games_.back().id = (**game)["id"];
games_.back().name = (**game)["name"];
const std::string& turn = (**game)["turn"];
const std::string& slots = (**game)["slots"];
games_.back().vacant_slots = lexical_cast_default<size_t>(slots, 0);
games_.back().current_turn = 0;
if(turn != "") {
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(slots != "")
games_.back().status = std::string(ngettext(_("Vacant Slot:"), _("Vacant Slots:"), games_.back().vacant_slots)) + " " + slots;
}
if((**game)["mp_use_map_settings"] == "yes") {
games_.back().use_map_settings = true;
} else {
games_.back().use_map_settings = false;
}
games_.back().gold = (**game)["mp_village_gold"];
if((**game)["mp_fog"] == "yes") {
games_.back().vision = _("Fog");
games_.back().fog = true;
if((**game)["mp_shroud"] == "yes") {
games_.back().vision += "/";
games_.back().vision += _("Shroud");
games_.back().shroud = true;
} else {
games_.back().shroud = false;
}
} else if((**game)["mp_shroud"] == "yes") {
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"] == "yes" ) {
games_.back().time_limit = (**game)["mp_countdown_init_time"] + " / +" + (**game)["mp_countdown_turn_bonus"] + " " + (**game)["mp_countdown_action_bonus"];
} else {
games_.back().time_limit = "";
}
games_.back().xp = (**game)["experience_modifier"] + "%";
games_.back().observers = (**game)["observer"] != "no" ? true : false;
}
set_full_size(games_.size());
set_shown_size(inner_location().h / row_height());
if(scrolled_to_max) {
set_position(get_max_position());
}
scroll(get_position());
if(selected_ >= games_.size())
selected_ = maximum<long>(static_cast<long>(games_.size()) - 1, 0);
set_dirty();
}
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* const list = cfg_.child("gamelist");
if(list == NULL) {
return false;
}
const config::child_list& games = list->get_children("game");
if(row1.id >= games.size() || row2.id >= games.size()) {
return false;
}
const config& game1 = *games[row1.id];
const config& game2 = *games[row2.id];
if(column == MAP_COLUMN) {
size_t mapsize1 = game1["map_data"].size();
if(mapsize1 == 0) {
mapsize1 = game1["map"].size();
}
size_t mapsize2 = game2["map_data"].size();
if(mapsize2 == 0) {
mapsize2 = game2["map"].size();
}
return mapsize1 < mapsize2;
} else if(column == STATUS_COLUMN) {
const int nslots1 = atoi(game1["slots"].c_str());
const int nslots2 = atoi(game2["slots"].c_str());
const int turn1 = atoi(game1["turn"].c_str());
const int turn2 = atoi(game2["turn"].c_str());
if(nslots1 > nslots2) {
return true;
} else if(nslots1 < nslots2) {
return false;
} else {
return turn1 < turn2;
}
} else {
return basic_sorter::less(column,row1,row2);
}
return false;
}
lobby::lobby(game_display& disp, const config& cfg, chat& c, config& gamelist) :
mp::ui(disp, _("Game Lobby"), cfg, c, gamelist),
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),
#ifndef USE_TINY_GUI
game_preferences_(disp.video(), _("Preferences")),
#endif
quit_game_(disp.video(), _("Quit")),
last_selected_game_(-1), sorter_(gamelist),
games_menu_(disp.video(),cfg.child("multiplayer_hashes"))
{
skip_replay_.set_check(preferences::skip_mp_replay());
skip_replay_.set_help_string(_("Skip quickly to the active turn when observing"));
game_config::debug = false;
gamelist_updated();
sound::play_music_repeatedly(game_config::title_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);
#ifndef USE_TINY_GUI
game_preferences_.hide(hide);
#endif
quit_game_.hide(hide);
}
void lobby::layout_children(const SDL_Rect& rect)
{
ui::layout_children(rect);
#ifdef USE_TINY_GUI
int btn_space = 3;
int xborder = 0;
int yborder = 0;
#else
int btn_space = 5;
int xborder = 10;
int yborder = 7;
#endif
// 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));
#ifndef USE_TINY_GUI
// 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));
#else
skip_replay_.set_location(create_game_.location().x + create_game_.location().w, yscale(yborder));
quit_game_.set_location(skip_replay_.location().x + skip_replay_.location().w + btn_space, yscale(yborder));
#endif
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);
}
void lobby::gamelist_updated(bool silent)
{
ui::gamelist_updated(silent);
const config* list = gamelist().child("gamelist");
if(list == NULL) {
// No gamelist yet. Do not update anything.
return;
}
games_menu_.set_game_items(*list, 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();
preferences::set_skip_mp_replay(skip_replay_.checked());
playmp_controller::set_replay_last_turn(0);
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(join || observe) {
const int selected = games_menu_.selection();
if(!games_menu_.empty() && selected >= 0) {
gamebrowser::game_item game = games_menu_.selected_game();
config response;
config& join = response.add_child("join");
join["id"] = game.id;
if (observe){
join["observe"] = "yes";
}
else{
join["observe"] = "no";
}
network::send_data(response);
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;
}
#ifndef USE_TINY_GUI
if(game_preferences_.pressed()) {
set_result(PREFERENCES);
return;
}
#endif
if(quit_game_.pressed()) {
recorder.set_skip(false);
set_result(QUIT);
return;
}
}
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;
}
}