00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #include "global.hpp"
00016
00017 #include "config.hpp"
00018 #include "construct_dialog.hpp"
00019 #include "game_display.hpp"
00020 #include "font.hpp"
00021 #include "marked-up_text.hpp"
00022 #include "gettext.hpp"
00023 #include "game_config.hpp"
00024 #include "image.hpp"
00025 #include "log.hpp"
00026 #include "multiplayer_ui.hpp"
00027 #include "network.hpp"
00028 #include "sound.hpp"
00029 #include "video.hpp"
00030 #include "replay.hpp"
00031 #include "wml_separators.hpp"
00032
00033 #define LOG_NG LOG_STREAM(info, engine)
00034 #define ERR_NG LOG_STREAM(err, engine)
00035 #define ERR_CF LOG_STREAM(err, config)
00036 #define DBG_NW LOG_STREAM(debug, network)
00037 #define LOG_NW LOG_STREAM(info, network)
00038 #define ERR_NW LOG_STREAM(err, network)
00039
00040 namespace {
00041
00042 class user_menu_style : public gui::menu::imgsel_style {
00043 public:
00044 user_menu_style() : gui::menu::imgsel_style("misc/selection", false,
00045 0x000000, 0x4a4440, 0x999999,
00046 0.0, 0.2, 0.2),
00047 item_size_(empty_rect)
00048 {}
00049 virtual void init();
00050 virtual SDL_Rect item_size(const std::string& ) const { return item_size_; }
00051 void set_width(const int width) { item_size_.w = width; }
00052 private:
00053 SDL_Rect item_size_;
00054 };
00055
00056 void user_menu_style::init()
00057 {
00058 imgsel_style::init();
00059 item_size_.h = font::get_max_height(font_size_);
00060 scale_images(-1, item_size_.h);
00061 item_size_.h += 2 * thickness_;
00062 }
00063
00064 user_menu_style umenu_style;
00065
00066 }
00067
00068 namespace mp {
00069
00070 void check_response(network::connection res, const config& data)
00071 {
00072 if(!res) {
00073 throw network::error(_("Connection timed out"));
00074 }
00075
00076 const config* err = data.child("error");
00077 if(err != NULL) {
00078 throw network::error((*err)["message"]);
00079 }
00080 }
00081
00082 void level_to_gamestate(config& level, game_state& state, bool saved_game)
00083 {
00084
00085
00086 config * const replay_data = level.child("replay");
00087 config replay_data_store;
00088 if(replay_data != NULL) {
00089 replay_data_store = *replay_data;
00090 LOG_NW << "setting replay\n";
00091 state.replay_data = *replay_data;
00092 recorder = replay(replay_data_store);
00093 if(!recorder.empty()) {
00094 recorder.set_skip(false);
00095 recorder.set_to_end();
00096 }
00097 }
00098
00099
00100 const std::string seed = level["random_seed"];
00101 if(! seed.empty()) {
00102 const unsigned calls = lexical_cast_default<unsigned>(level["random_calls"]);
00103 state.rng().seed_random(lexical_cast<int>(seed), calls);
00104 } else {
00105 ERR_NG << "No random seed found, random "
00106 "events will probably be out of sync.\n";
00107 }
00108
00109
00110 if(level.child("replay_start") == NULL){
00111 level.add_child("replay_start", level);
00112 }
00113
00114
00115
00116 state.starting_pos = *(level.child("replay_start"));
00117
00118 level["campaign_type"] = "multiplayer";
00119 state.campaign_type = "multiplayer";
00120 state.version = level["version"];
00121
00122 const config* const vars = level.child("variables");
00123 if(vars != NULL) {
00124 state.set_variables(*vars);
00125 }
00126 state.set_menu_items(level.get_children("menu_item"));
00127
00128
00129
00130 if (saved_game){
00131 state.snapshot = *(level.child("snapshot"));
00132 if (state.snapshot.child("variables") != NULL){
00133 state.set_variables(*state.snapshot.child("variables"));
00134 }
00135 state.set_menu_items(state.snapshot.get_children("menu_item"));
00136
00137
00138
00139 const config::child_list& snapshot_sides = state.snapshot.get_children("side");
00140 const config::child_list& level_sides = level.get_children("side");
00141 for(config::child_list::const_iterator side = snapshot_sides.begin(); side != snapshot_sides.end(); ++side) {
00142 for(config::child_list::const_iterator lside = level_sides.begin(); lside != level_sides.end(); ++lside) {
00143 if ( ((**side)["side"] == (**lside)["side"])
00144 && ((**side)["current_player"] != (**lside)["current_player"]) ){
00145 (**side)["current_player"] = (**lside)["current_player"];
00146 (**side)["id"] = (**lside)["id"];
00147 (**side)["save_id"] = (**lside)["save_id"];
00148 (**side)["controller"] = (**lside)["controller"];
00149 break;
00150 }
00151 }
00152 }
00153 }
00154 if(state.get_variables().empty()) {
00155 LOG_NG << "No variables were found for the game_state." << std::endl;
00156 } else {
00157 LOG_NG << "Variables found and loaded into game_state:" << std::endl;
00158 LOG_NG << state.get_variables();
00159 }
00160 }
00161
00162 std::string get_colour_string(int id)
00163 {
00164 std::string prefix = team::get_side_highlight(id);
00165 std::stringstream side_id;
00166 side_id << (id + 1);
00167 std::map<std::string, t_string>::iterator name = game_config::team_rgb_name.find(side_id.str());
00168 if(name != game_config::team_rgb_name.end()){
00169 return prefix + name->second;
00170 }else{
00171 return prefix + _("Invalid Color");
00172 }
00173 }
00174
00175 chat::chat()
00176 {
00177 }
00178
00179 void chat::add_message(const time_t& time, const std::string& user,
00180 const std::string& message)
00181 {
00182 message_history_.push_back(msg(time, user, message));
00183
00184 while (message_history_.size() > 1024) {
00185 message_history_.pop_front();
00186
00187 if (last_update_ > 0)
00188 last_update_--;
00189 }
00190 }
00191
00192 void chat::init_textbox(gui::textbox& textbox)
00193 {
00194 std::string s;
00195
00196 for(msg_hist::const_iterator itor = message_history_.begin();
00197 itor != message_history_.end(); ++itor) {
00198 s.append(format_message(*itor));
00199 }
00200
00201 textbox.set_text(s);
00202 last_update_ = message_history_.size();
00203 textbox.scroll_to_bottom();
00204 }
00205
00206 void chat::update_textbox(gui::textbox& textbox)
00207 {
00208 std::string s;
00209
00210 for(msg_hist::const_iterator itor = message_history_.begin() + last_update_;
00211 itor != message_history_.end(); ++itor) {
00212 s.append(format_message(*itor));
00213 }
00214
00215 textbox.append_text(s,true);
00216
00217 last_update_ = message_history_.size();
00218 }
00219
00220 std::string chat::format_message(const msg& message)
00221 {
00222 if(message.message.substr(0,3) == "/me") {
00223 return preferences::get_chat_timestamp(message.time) + "<" + message.user
00224 + message.message.substr(3) + ">\n";
00225 } else {
00226 return preferences::get_chat_timestamp(message.time) + "<" + message.user
00227 + "> " + message.message + "\n";
00228 }
00229 }
00230
00231 ui::ui(game_display& disp, const std::string& title, const config& cfg, chat& c, config& gamelist) :
00232 gui::widget(disp.video()),
00233 disp_(disp),
00234 initialized_(false),
00235 gamelist_initialized_(false),
00236
00237 hotkey_handler_(&disp),
00238 disp_manager_(&disp),
00239
00240 game_config_(cfg),
00241 chat_(c),
00242 gamelist_(gamelist),
00243
00244 #ifdef USE_TINY_GUI
00245 title_(disp.video(), title, font::SIZE_SMALL, font::TITLE_COLOUR),
00246 #else
00247 title_(disp.video(), title, font::SIZE_LARGE, font::TITLE_COLOUR),
00248 entry_textbox_(disp.video(), 100),
00249 #endif
00250 chat_textbox_(disp.video(), 100, "", false),
00251 users_menu_(disp.video(), std::vector<std::string>(), false, -1, -1, NULL, &umenu_style),
00252
00253 selected_game_(""),
00254
00255 result_(CONTINUE),
00256 gamelist_refresh_(false),
00257 lobby_clock_(0)
00258 {
00259 const SDL_Rect area = { 0, 0, disp.video().getx(), disp.video().gety() };
00260 users_menu_.set_numeric_keypress_selection(false);
00261 set_location(area);
00262 }
00263
00264 void ui::process_network()
00265 {
00266 config data;
00267 try {
00268 const network::connection sock = network::receive_data(data);
00269
00270 if(sock) {
00271 process_network_data(data, sock);
00272 }
00273 } catch(network::error& e) {
00274 process_network_error(e);
00275 }
00276
00277
00278 if(gamelist_refresh_ && SDL_GetTicks() - lobby_clock_ > game_config::lobby_refresh)
00279 {
00280 const cursor::setter cursor_setter(cursor::WAIT);
00281 gamelist_updated(false);
00282 gamelist_refresh_ = false;
00283 lobby_clock_ = SDL_GetTicks();
00284 }
00285
00286 if (accept_connections()) {
00287 network::connection sock = network::accept_connection();
00288 if(sock) {
00289 LOG_NW << "Received connection\n";
00290
00291 process_network_connection(sock);
00292 }
00293 }
00294 }
00295
00296 ui::result ui::get_result()
00297 {
00298 return result_;
00299 }
00300
00301 ui::result ui::set_result(ui::result res)
00302 {
00303 result_ = res;
00304 return res;
00305 }
00306
00307 const int ui::xscale_base = 1024;
00308 const int ui::yscale_base = 768;
00309
00310 int ui::xscale(int x) const
00311 {
00312 return (x * width())/ui::xscale_base;
00313 }
00314
00315 int ui::yscale(int y) const
00316 {
00317 return (y * height())/ui::yscale_base;
00318 }
00319
00320 SDL_Rect ui::client_area() const
00321 {
00322 SDL_Rect res;
00323
00324 res.x = xscale(10) + 10;
00325 res.y = yscale(38) + 10;
00326 res.w = xscale(828) > 12 ? xscale(828) - 12 : 0;
00327 res.h = yscale(520) > 12 ? yscale(520) - 12 : 0;
00328
00329 return res;
00330 }
00331
00332 const config& ui::game_config() const
00333 {
00334 return game_config_;
00335 }
00336
00337 void ui::draw_contents()
00338 {
00339 hide_children();
00340
00341 #ifdef USE_TINY_GUI
00342 surface background(image::get_image("misc/lobby_tiny.png"));
00343 #else
00344 surface background(image::get_image("misc/lobby.png"));
00345 #endif
00346 background = scale_surface(background, video().getx(), video().gety());
00347 if(background == NULL)
00348 return;
00349 SDL_BlitSurface(background, NULL, video().getSurface(), NULL);
00350 update_whole_screen();
00351
00352 hide_children(false);
00353 }
00354
00355 void ui::set_location(const SDL_Rect& rect)
00356 {
00357 hide_children();
00358 widget::set_location(rect);
00359 layout_children(rect);
00360 if(!initialized_) {
00361 chat_textbox_.set_wrap(true);
00362 chat_.init_textbox(chat_textbox_);
00363 initialized_ = true;
00364 }
00365 hide_children(false);
00366 }
00367
00368 void ui::process_event()
00369 {
00370 }
00371
00372 void ui::handle_event(const SDL_Event& event)
00373 {
00374 if(event.type == SDL_KEYDOWN) {
00375 handle_key_event(event.key);
00376 }
00377 if(users_menu_.double_clicked()) {
00378 std::string usr_text = user_list_[users_menu_.selection()];
00379 std::string caption = _("Send a private message to ") + usr_text;
00380 gui::dialog d(disp(), _("Whisper"), caption, gui::OK_CANCEL);
00381 d.set_textbox( _("Message: "));
00382 Uint32 show_time = SDL_GetTicks();
00383 if (!(d.show() || d.textbox_text().empty())) {
00384 std::stringstream msg;
00385 msg << "/msg " << usr_text << ' ' << d.textbox_text();
00386 chat_handler::do_speak(msg.str());
00387 }
00388 if(show_time + 60000 < SDL_GetTicks()) {
00389
00390 config request;
00391 request.add_child("refresh_lobby");
00392 network::send_data(request, 0, true);
00393 }
00394 }
00395 }
00396
00397 void ui::add_chat_message(const time_t& time, const std::string& speaker, int , const std::string& message, game_display::MESSAGE_TYPE )
00398 {
00399 chat_.add_message(time, speaker, message);
00400 chat_.update_textbox(chat_textbox_);
00401 }
00402
00403 void ui::send_chat_message(const std::string& message, bool )
00404 {
00405 config data, msg;
00406 msg["message"] = message;
00407 msg["sender"] = preferences::login();
00408 data.add_child("message", msg);
00409
00410 add_chat_message(time(NULL), preferences::login(),0, message);
00411 network::send_data(data, 0, true);
00412 }
00413
00414
00415 void ui::handle_key_event(const SDL_KeyboardEvent& event)
00416 {
00417 #ifndef USE_TINY_GUI
00418
00419 if((event.keysym.sym == SDLK_RETURN || event.keysym.sym == SDLK_KP_ENTER) && !entry_textbox_.text().empty()) {
00420
00421 chat_handler::do_speak(entry_textbox_.text());
00422 entry_textbox_.clear();
00423
00424 } else if(event.keysym.sym == SDLK_TAB ) {
00425 std::string text = entry_textbox_.text();
00426 std::vector<std::string> matches = user_list_;
00427
00428 matches.erase(std::remove(matches.begin(), matches.end(),
00429 preferences::login()), matches.end());
00430 const bool line_start = utils::word_completion(text, matches);
00431
00432 if (matches.empty()) return;
00433
00434 if (matches.size() == 1) {
00435 text.append(line_start ? ": " : " ");
00436 } else {
00437 std::string completion_list = utils::join(matches, ' ');
00438 chat_.add_message(time(NULL), "", completion_list);
00439 chat_.update_textbox(chat_textbox_);
00440 }
00441 entry_textbox_.set_text(text);
00442 }
00443 #endif
00444 }
00445
00446 void ui::process_message(const config& msg, const bool whisper) {
00447 const std::string& sender = msg["sender"];
00448 const std::string& message = msg["message"];
00449 if (!preferences::show_lobby_join(sender, message)) return;
00450 if (preferences::is_ignored(sender)) return;
00451
00452 if (whisper || utils::word_match(message, preferences::login())) {
00453 sound::play_UI_sound(game_config::sounds::receive_message_highlight);
00454 } else if (preferences::is_friend(sender)) {
00455 sound::play_UI_sound(game_config::sounds::receive_message_friend);
00456 } else if (sender == "server") {
00457 sound::play_UI_sound(game_config::sounds::receive_message_server);
00458 } else {
00459 sound::play_UI_sound(game_config::sounds::receive_message);
00460 }
00461 chat_.add_message(time(NULL), (whisper ? "whisper: " : "") + msg["sender"],
00462 msg["message"]);
00463 chat_.update_textbox(chat_textbox_);
00464 }
00465
00466 void ui::process_network_data(const config& data, const network::connection )
00467 {
00468 if(data.child("error")) {
00469 throw network::error((*data.child("error"))["message"]);
00470 } else if (data.child("message")) {
00471 process_message(*data.child("message"));
00472 } else if(data.child("whisper")){
00473 process_message(*data.child("whisper"), true);
00474 } else if(data.child("gamelist")) {
00475 const cursor::setter cursor_setter(cursor::WAIT);
00476 gamelist_initialized_ = true;
00477 gamelist_ = data;
00478 gamelist_updated(false);
00479 gamelist_refresh_ = false;
00480 lobby_clock_ = SDL_GetTicks();
00481 } else if(data.child("gamelist_diff")) {
00482 if(gamelist_initialized_) {
00483 try {
00484 gamelist_.apply_diff(*data.child("gamelist_diff"));
00485 } catch(config::error& e) {
00486 ERR_CF << "Error while applying the gamelist diff: '"
00487 << e.message << "' Getting a new gamelist.\n";
00488 network::send_data(config("refresh_lobby"), 0, true);
00489 }
00490 gamelist_refresh_ = true;
00491 }
00492 }
00493 }
00494
00495 void ui::process_network_error(network::error& error)
00496 {
00497 ERR_NW << "Caught networking error: " << error.message << "\n";
00498
00499
00500 throw error;
00501 }
00502
00503 void ui::process_network_connection(const network::connection )
00504 {
00505 LOG_NW << "Caught network connection.\n";
00506 }
00507
00508 void ui::hide_children(bool hide)
00509 {
00510 title_.hide(hide);
00511 chat_textbox_.hide(hide);
00512 #ifndef USE_TINY_GUI
00513 entry_textbox_.hide(hide);
00514 #endif
00515 users_menu_.hide(hide);
00516 }
00517
00518 void ui::layout_children(const SDL_Rect& )
00519 {
00520 title_.set_location(xscale(12) + 8, yscale(38) + 8);
00521 umenu_style.set_width(xscale(159));
00522 users_menu_.set_width(xscale(159));
00523 users_menu_.set_max_width(xscale(159));
00524 users_menu_.set_location(xscale(856), yscale(42));
00525 users_menu_.set_height(yscale(715));
00526 users_menu_.set_max_height(yscale(715));
00527 #ifdef USE_TINY_GUI
00528 chat_textbox_.set_location(xscale(11) + 4, yscale(625) + 4);
00529 chat_textbox_.set_measurements(xscale(833) - 8, yscale(143) - 8);
00530
00531 #else
00532 chat_textbox_.set_location(xscale(11) + 4, yscale(573) + 4);
00533 chat_textbox_.set_measurements(xscale(833) - 8, yscale(143) - 8);
00534 entry_textbox_.set_location(xscale(11) + 4, yscale(732));
00535 entry_textbox_.set_width(xscale(833) - 8);
00536 #endif
00537 }
00538
00539 bool ui::user_info::operator> (const user_info& b) const {
00540 user_info const& a = *this;
00541
00542
00543 if (a.relation == ME) {
00544 return true;
00545 }
00546 if (b.relation == ME) {
00547 return false;
00548 }
00549
00550
00551 if ((a.relation == FRIEND) && (b.relation == FRIEND)) {
00552 if (a.state != b.state) {
00553 return a.state < b.state;
00554 }
00555 return a.name < b.name;
00556 }
00557 if (a.relation == FRIEND) {
00558 return true;
00559 }
00560 if (b.relation == FRIEND) {
00561 return false;
00562 }
00563
00564
00565 if ((a.state == SEL_GAME) && (b.state == SEL_GAME)) {
00566 if (a.relation != b.relation) {
00567 return a.relation < b.relation;
00568 }
00569 return a.name < b.name;
00570 }
00571 if (a.state == SEL_GAME) {
00572 return true;
00573 }
00574 if (b.state == SEL_GAME) {
00575 return false;
00576 }
00577
00578
00579 if (a.relation != b.relation) {
00580 return a.relation < b.relation;
00581 }
00582 if (a.state != b.state) {
00583 return a.state < b.state;
00584 }
00585 return a.name < b.name;
00586 }
00587
00588 void ui::gamelist_updated(bool silent)
00589 {
00590 config::child_list users = gamelist_.get_children("user");
00591 config::child_iterator user;
00592 std::list<user_info> u_list;
00593
00594 for (user = users.begin(); user != users.end(); ++user) {
00595 user_info u_elem;
00596 u_elem.name = (**user)["name"];
00597 u_elem.game_id = "";
00598 u_elem.location = "";
00599 u_elem.state = (**user)["available"] == "no" ? GAME : LOBBY;
00600 if(!(**user)["game_id"].empty()) {
00601 u_elem.game_id = (**user)["game_id"];
00602 if (u_elem.game_id == selected_game_) {
00603 u_elem.state = SEL_GAME;
00604 }
00605 }
00606 if(!(**user)["location"].empty()) {
00607 u_elem.location = (**user)["location"];
00608 }
00609 std::vector<std::string> friends = utils::split(preferences::get("friends"));
00610 std::vector<std::string> ignores = utils::split(preferences::get("ignores"));
00611 if (u_elem.name == preferences::login()) {
00612 u_elem.relation = ME;
00613 } else if (std::find(ignores.begin(), ignores.end(), u_elem.name) != ignores.end()) {
00614 u_elem.relation = IGNORED;
00615 } else if (std::find(friends.begin(), friends.end(), u_elem.name) != friends.end()) {
00616 u_elem.relation = FRIEND;
00617 } else {
00618 u_elem.relation = NEUTRAL;
00619 }
00620 u_list.push_back(u_elem);
00621 }
00622
00623 if (preferences::sort_list()) {
00624 u_list.sort(std::greater<user_info>());
00625 }
00626
00627
00628
00629 const std::string lobby_color_tag = "";
00630 const std::string ingame_color_tag = "#";
00631 const std::string selgame_color_tag = "<0,191,255>";
00632
00633 std::string const imgpre = IMAGE_PREFIX + std::string("misc/status-");
00634 std::vector<std::string> user_strings;
00635 std::vector<std::string> menu_strings;
00636
00637 std::list<user_info>::const_iterator u_itor = u_list.begin();
00638 while (u_itor != u_list.end()) {
00639 const std::string name_str = u_itor->name +
00640 ((u_itor->state == LOBBY) ? "" : " (" + u_itor->location + ")");
00641 std::string img_str = "";
00642 std::string color_str = "";
00643 switch (u_itor->state) {
00644 case LOBBY: color_str = lobby_color_tag; break;
00645 case GAME: color_str = ingame_color_tag; break;
00646 case SEL_GAME: color_str = selgame_color_tag; break;
00647 }
00648 if (preferences::iconize_list()) {
00649 switch (u_itor->relation) {
00650 case NEUTRAL: img_str = imgpre + "neutral.png" + IMG_TEXT_SEPARATOR; break;
00651 case IGNORED: img_str = imgpre + "ignore.png" + IMG_TEXT_SEPARATOR; break;
00652 case FRIEND: img_str = imgpre + "friend.png" + IMG_TEXT_SEPARATOR; break;
00653 case ME: img_str = imgpre + "self.png" + IMG_TEXT_SEPARATOR; break;
00654 }
00655 }
00656 user_strings.push_back(u_itor->name);
00657 menu_strings.push_back(img_str + color_str + name_str + HELP_STRING_SEPARATOR + name_str);
00658 u_itor++;
00659 }
00660
00661 set_user_list(user_strings, silent);
00662 set_user_menu_items(menu_strings);
00663 }
00664
00665 void ui::set_selected_game(const std::string game_id)
00666 {
00667
00668 if (preferences::sort_list() && (selected_game_ != game_id)) {
00669 users_menu_.move_selection(0);
00670 }
00671 selected_game_ = game_id;
00672 }
00673
00674 void ui::set_user_menu_items(const std::vector<std::string>& list)
00675 {
00676 users_menu_.set_items(list,true,true);
00677 }
00678
00679 void ui::set_user_list(const std::vector<std::string>& list, bool silent)
00680 {
00681 if(!silent) {
00682 if(list.size() < user_list_.size()) {
00683 sound::play_UI_sound(game_config::sounds::user_leave);
00684 } else if(list.size() > user_list_.size()) {
00685 sound::play_UI_sound(game_config::sounds::user_arrive);
00686 }
00687 }
00688
00689 user_list_ = list;
00690 }
00691
00692 void ui::append_to_title(const std::string& text) {
00693 title_.set_text(title_.get_text() + text);
00694 }
00695
00696 const gui::label& ui::title() const
00697 {
00698 return title_;
00699 }
00700
00701
00702 }