playmp_controller.cpp

Go to the documentation of this file.
00001 /* $Id: playmp_controller.cpp 52644 2012-01-18 00:18:09Z crab $ */
00002 /*
00003    Copyright (C) 2006 - 2012 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
00004    wesnoth playlevel Copyright (C) 2003 by David White <dave@whitevine.net>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
00015 */
00016 
00017 #include "playmp_controller.hpp"
00018 
00019 #include "dialogs.hpp"
00020 #include "foreach.hpp"
00021 #include "game_end_exceptions.hpp"
00022 #include "gettext.hpp"
00023 #include "log.hpp"
00024 #include "playturn.hpp"
00025 #include "preferences.hpp"
00026 #include "resources.hpp"
00027 #include "savegame.hpp"
00028 #include "sound.hpp"
00029 #include "formula_string_utils.hpp"
00030 #include "whiteboard/manager.hpp"
00031 
00032 static lg::log_domain log_engine("engine");
00033 #define LOG_NG LOG_STREAM(info, log_engine)
00034 
00035 unsigned int playmp_controller::replay_last_turn_ = 0;
00036 
00037 playmp_controller::playmp_controller(const config& level,
00038         game_state& state_of_game, const int ticks,
00039         const int num_turns, const config& game_config, CVideo& video,
00040         bool skip_replay, bool is_host) :
00041     playsingle_controller(level, state_of_game, ticks, num_turns,
00042         game_config, video, skip_replay),
00043     turn_data_(NULL),
00044     beep_warning_time_(0),
00045     network_processing_stopped_(false)
00046 {
00047     is_host_ = is_host;
00048     // We stop quick replay if play isn't yet past turn 1
00049     if ( replay_last_turn_ <= 1)
00050     {
00051         skip_replay_ = false;
00052     }
00053 }
00054 
00055 playmp_controller::~playmp_controller() {
00056     //halt and cancel the countdown timer
00057     if(beep_warning_time_ < 0) {
00058         sound::stop_bell();
00059     }
00060 }
00061 
00062 void playmp_controller::set_replay_last_turn(unsigned int turn){
00063      replay_last_turn_ = turn;
00064 }
00065 
00066 void playmp_controller::speak(){
00067     menu_handler_.speak();
00068 }
00069 
00070 void playmp_controller::whisper(){
00071     menu_handler_.whisper();
00072 }
00073 
00074 void playmp_controller::shout(){
00075     menu_handler_.shout();
00076 }
00077 
00078 void playmp_controller::start_network(){
00079     network_processing_stopped_ = false;
00080     LOG_NG << "network processing activated again";
00081 }
00082 
00083 void playmp_controller::stop_network(){
00084     network_processing_stopped_ = true;
00085     LOG_NG << "network processing stopped";
00086 }
00087 
00088 void playmp_controller::play_side(const unsigned int side_number, bool save)
00089 {
00090     utils::string_map player;
00091     player["name"] = current_team().current_player();
00092     std::string turn_notification_msg = _("$name has taken control");
00093     turn_notification_msg = utils::interpolate_variables_into_string(turn_notification_msg, &player);
00094     gui_->send_notification(_("Turn changed"), turn_notification_msg);
00095 
00096     do {
00097         player_type_changed_ = false;
00098         if (!skip_next_turn_)
00099             end_turn_ = false;
00100 
00101         statistics::reset_turn_stats(teams_[side_number-1].save_id());
00102 
00103         // we can't call playsingle_controller::play_side because
00104         // we need to catch exception here
00105         if(current_team().is_human()) {
00106             LOG_NG << "is human...\n";
00107 
00108 
00109             try{
00110                 before_human_turn(save);
00111                 play_human_turn();
00112                 after_human_turn();
00113             } catch(end_turn_exception& end_turn) {
00114                 if (end_turn.redo == side_number) {
00115                     player_type_changed_ = true;
00116                     // if new controller is not human,
00117                     // reset gui to prev human one
00118                     if (!teams_[side_number-1].is_human()) {
00119                         browse_ = true;
00120                         int t = find_human_team_before(side_number);
00121 
00122                         if (t <= 0)
00123                             t = gui_->playing_side();
00124 
00125                         update_gui_to_player(t-1);
00126                     }
00127                 } else {
00128                     after_human_turn();
00129                 }
00130             }
00131             LOG_NG << "human finished turn...\n";
00132         } else if(current_team().is_ai()) {
00133             play_ai_turn();
00134         } else if(current_team().is_network()) {
00135             play_network_turn();
00136         }
00137     } while (player_type_changed_);
00138     //keep looping if the type of a team (human/ai/networked) has changed mid-turn
00139 
00140     skip_next_turn_ = false;
00141 }
00142 
00143 void playmp_controller::before_human_turn(bool save){
00144     LOG_NG << "playmp::before_human_turn...\n";
00145     playsingle_controller::before_human_turn(save);
00146 
00147     init_turn_data();
00148 }
00149 
00150 bool playmp_controller::counting_down() {
00151     return beep_warning_time_ > 0;
00152 }
00153 
00154 namespace {
00155     const int WARNTIME = 20000; //start beeping when 20 seconds are left (20,000ms)
00156     unsigned timer_refresh = 0;
00157     const unsigned timer_refresh_rate = 50; // prevents calling SDL_GetTicks() too frequently
00158 }
00159 
00160 //make sure we think about countdown even while dialogs are open
00161 void playmp_controller::process(events::pump_info &info) {
00162     if(playmp_controller::counting_down()) {
00163         if(info.ticks(&timer_refresh, timer_refresh_rate)) {
00164             playmp_controller::think_about_countdown(info.ticks());
00165         }
00166     }
00167 }
00168 
00169 void playmp_controller::reset_countdown()
00170 {
00171     if (beep_warning_time_ < 0)
00172         sound::stop_bell();
00173     beep_warning_time_ = 0;
00174 }
00175 
00176 
00177 //check if it is time to start playing the timer warning
00178 void playmp_controller::think_about_countdown(int ticks) {
00179     if(ticks >= beep_warning_time_) {
00180         const bool bell_on = preferences::turn_bell();
00181         if(bell_on || preferences::sound_on() || preferences::UI_sound_on()) {
00182             const int loop_ticks = WARNTIME - (ticks - beep_warning_time_);
00183             const int fadein_ticks = (loop_ticks > WARNTIME / 2) ? loop_ticks - WARNTIME / 2 : 0;
00184             sound::play_timer(game_config::sounds::timer_bell, loop_ticks, fadein_ticks);
00185             beep_warning_time_ = -1;
00186         }
00187     }
00188 }
00189 
00190 namespace {
00191     struct command_disabled_resetter
00192     {
00193         command_disabled_resetter() : val_(events::commands_disabled) {}
00194         ~command_disabled_resetter() { events::commands_disabled = val_; }
00195     private:
00196         int val_;
00197     };
00198 }
00199 
00200 void playmp_controller::play_human_turn(){
00201     LOG_NG << "playmp::play_human_turn...\n";
00202     command_disabled_resetter reset_commands;
00203     int cur_ticks = SDL_GetTicks();
00204     show_turn_dialog();
00205     execute_gotos();
00206 
00207     if ((!linger_) || (is_host_))
00208         gui_->enable_menu("endturn", true);
00209     while(!end_turn_) {
00210 
00211         try {
00212             config cfg;
00213             const network::connection res = network::receive_data(cfg);
00214             std::deque<config> backlog;
00215 
00216             if(res != network::null_connection) {
00217                 if (turn_data_->process_network_data(cfg, res, backlog, skip_replay_) == turn_info::PROCESS_RESTART_TURN)
00218                 {
00219                     // Clean undo stack if turn has to be restarted (losing control)
00220                     if (!undo_stack_.empty())
00221                     {
00222                         font::floating_label flabel(_("Undoing moves not yet transmitted to the server."));
00223 
00224                         SDL_Color color = {255,255,255,255};
00225                         flabel.set_color(color);
00226                         SDL_Rect rect = gui_->map_area();
00227                         flabel.set_position(rect.w/2, rect.h/2);
00228                         flabel.set_lifetime(150);
00229                         flabel.set_clip_rect(rect);
00230 
00231                         font::add_floating_label(flabel);
00232                     }
00233 
00234                     while(!undo_stack_.empty())
00235                         menu_handler_.undo(gui_->playing_side());
00236                     throw end_turn_exception(gui_->playing_side());
00237                 }
00238             }
00239 
00240             play_slice();
00241             check_end_level();
00242             // give a chance to the whiteboard to continue an execute_all_actions
00243             resources::whiteboard->continue_execute_all();
00244         } catch(const end_level_exception&) {
00245             turn_data_->send_data();
00246             throw;
00247         }
00248 
00249         if (!linger_ && (current_team().countdown_time() > 0) && gamestate_.mp_settings().mp_countdown) {
00250             SDL_Delay(1);
00251             const int ticks = SDL_GetTicks();
00252             int new_time = current_team().countdown_time()-std::max<int>(1,(ticks - cur_ticks));
00253             if (new_time > 0 ){
00254                 current_team().set_countdown_time(new_time);
00255                 cur_ticks = ticks;
00256                 if(current_team().is_human() && !beep_warning_time_) {
00257                     beep_warning_time_ = new_time - WARNTIME + ticks;
00258                 }
00259                 if(counting_down()) {
00260                     think_about_countdown(ticks);
00261                 }
00262             } else {
00263                 // Clock time ended
00264                 // If no turn bonus or action bonus -> defeat
00265                 const int action_increment = gamestate_.mp_settings().mp_countdown_action_bonus;
00266                 if ( (gamestate_.mp_settings().mp_countdown_turn_bonus == 0 )
00267                     && (action_increment == 0 || current_team().action_bonus_count() == 0)) {
00268                     // Not possible to end level in MP with throw end_level_exception(DEFEAT);
00269                     // because remote players only notice network disconnection
00270                     // Current solution end remaining turns automatically
00271                     current_team().set_countdown_time(10);
00272                 } else {
00273                     const int maxtime = gamestate_.mp_settings().mp_countdown_reservoir_time;
00274                     int secs = gamestate_.mp_settings().mp_countdown_turn_bonus;
00275                     secs += action_increment  * current_team().action_bonus_count();
00276                     current_team().set_action_bonus_count(0);
00277                     secs = (secs > maxtime) ? maxtime : secs;
00278                     current_team().set_countdown_time(1000 * secs);
00279                 }
00280                 turn_data_->send_data();
00281 
00282                 if (!rand_rng::has_new_seed_callback()) {
00283                     throw end_turn_exception();
00284                 }
00285             }
00286         }
00287 
00288         gui_->draw();
00289 
00290         turn_data_->send_data();
00291     }
00292     menu_handler_.clear_undo_stack(player_number_);
00293 }
00294 
00295 void playmp_controller::set_end_scenario_button()
00296 {
00297     // Modify the end-turn button
00298     if (! is_host_) {
00299         gui::button* btn_end = gui_->find_button("button-endturn");
00300         btn_end->enable(false);
00301     }
00302     gui_->get_theme().refresh_title2("button-endturn", "title2");
00303     gui_->invalidate_theme();
00304     gui_->redraw_everything();
00305 }
00306 
00307 void playmp_controller::reset_end_scenario_button()
00308 {
00309     // revert the end-turn button text to its normal label
00310     gui_->get_theme().refresh_title2("button-endturn", "title");
00311     gui_->invalidate_theme();
00312     gui_->redraw_everything();
00313     gui_->set_game_mode(game_display::RUNNING);
00314 }
00315 
00316 void playmp_controller::linger()
00317 {
00318     LOG_NG << "beginning end-of-scenario linger\n";
00319     browse_ = true;
00320     linger_ = true;
00321     // If we need to set the status depending on the completion state
00322     // we're needed here.
00323     gui_->set_game_mode(game_display::LINGER_MP);
00324 
00325     // this is actually for after linger mode is over -- we don't want to
00326     // stay stuck in linger state when the *next* scenario is over.
00327     gamestate_.classification().completion = "running";
00328     // End all unit moves
00329     foreach (unit &u, units_) {
00330         u.set_user_end_turn(true);
00331     }
00332     //current_team().set_countdown_time(0);
00333     //halt and cancel the countdown timer
00334     reset_countdown();
00335 
00336     set_end_scenario_button();
00337 
00338     if ( get_end_level_data_const().reveal_map ) {
00339         // Change the view of all players and observers
00340         // to see the whole map regardless of shroud and fog.
00341         update_gui_to_player(gui_->viewing_team(), true);
00342     }
00343     bool quit;
00344     do {
00345         quit = true;
00346         try {
00347             // reimplement parts of play_side()
00348             player_number_ = first_player_;
00349             init_turn_data();
00350 
00351             end_turn_ = false;
00352             play_human_turn();
00353             turn_over_ = true;  // We don't want to linger mode to add end_turn to replay
00354             after_human_turn();
00355             LOG_NG << "finished human turn" << std::endl;
00356         } catch (game::load_game_exception&) {
00357             LOG_NG << "caught load-game-exception" << std::endl;
00358             // this should not happen, the option to load a game is disabled
00359             throw;
00360         } catch (end_level_exception&) {
00361             // thrown if the host ends the scenario and let us advance
00362             // to the next level
00363             LOG_NG << "caught end-level-exception" << std::endl;
00364             reset_end_scenario_button();
00365             throw;
00366         } catch (end_turn_exception&) {
00367             // thrown if the host leaves the game (sends [leave_game]), we need
00368             // to stay in this loop to stay in linger mode, otherwise the game
00369             // gets aborted
00370             LOG_NG << "caught end-turn-exception" << std::endl;
00371             quit = false;
00372         } catch (network::error&) {
00373             LOG_NG << "caught network-error-exception" << std::endl;
00374             quit = false;
00375         }
00376     } while (!quit);
00377 
00378     reset_end_scenario_button();
00379 
00380     LOG_NG << "ending end-of-scenario linger\n";
00381 }
00382 
00383 void playmp_controller::wait_for_upload()
00384 {
00385     // If the host is here we'll never leave since we wait for the host to
00386     // upload the next scenario.
00387     assert(!is_host_);
00388 
00389     const bool set_turn_data = (turn_data_ == 0);
00390     if(set_turn_data) {
00391         init_turn_data();
00392     }
00393 
00394     while(true) {
00395         try {
00396             config cfg;
00397             const network::connection res = dialogs::network_receive_dialog(
00398                 *gui_, _("Waiting for next scenario..."), cfg);
00399 
00400             std::deque<config> backlog;
00401             if(res != network::null_connection) {
00402                 if (turn_data_->process_network_data(cfg, res, backlog, skip_replay_)
00403                         == turn_info::PROCESS_END_LINGER) {
00404                     break;
00405                 }
00406             }
00407 
00408         } catch(const end_level_exception&) {
00409             turn_data_->send_data();
00410             throw;
00411         }
00412     }
00413 
00414     if(set_turn_data) {
00415         delete turn_data_;
00416         turn_data_ = 0;
00417     }
00418 }
00419 
00420 void playmp_controller::after_human_turn(){
00421     if ( gamestate_.mp_settings().mp_countdown ){
00422         const int action_increment = gamestate_.mp_settings().mp_countdown_action_bonus;
00423         const int maxtime = gamestate_.mp_settings().mp_countdown_reservoir_time;
00424         int secs = (current_team().countdown_time() / 1000) + gamestate_.mp_settings().mp_countdown_turn_bonus;
00425         secs += action_increment  * current_team().action_bonus_count();
00426         current_team().set_action_bonus_count(0);
00427         secs = (secs > maxtime) ? maxtime : secs;
00428         current_team().set_countdown_time(1000 * secs);
00429         recorder.add_countdown_update(current_team().countdown_time(),player_number_);
00430     }
00431     LOG_NG << "playmp::after_human_turn...\n";
00432     end_turn_record();
00433 
00434     //ensure that turn_data_ is constructed before it is used.
00435     if (turn_data_ == NULL) init_turn_data();
00436 
00437     //send one more time to make sure network is up-to-date.
00438     turn_data_->send_data();
00439     playsingle_controller::after_human_turn();
00440     if (turn_data_ != NULL){
00441         turn_data_->host_transfer().detach_handler(this);
00442         delete turn_data_;
00443         turn_data_ = NULL;
00444     }
00445 
00446 }
00447 
00448 void playmp_controller::finish_side_turn(){
00449     play_controller::finish_side_turn();
00450 
00451     //just in case due to an exception turn_data_ has not been deleted in after_human_turn
00452     delete turn_data_;
00453     turn_data_ = NULL;
00454 
00455     //halt and cancel the countdown timer
00456     reset_countdown();
00457 
00458     // avoid callback getting called in the wrong turn
00459     rand_rng::clear_new_seed_callback();
00460 }
00461 
00462 void playmp_controller::play_network_turn(){
00463     LOG_NG << "is networked...\n";
00464 
00465     gui_->enable_menu("endturn", false);
00466     turn_info turn_data(player_number_, replay_sender_);
00467     turn_data.host_transfer().attach_handler(this);
00468 
00469     for(;;) {
00470 
00471         if (!network_processing_stopped_){
00472             bool have_data = false;
00473             config cfg;
00474 
00475             network::connection from = network::null_connection;
00476 
00477             if(data_backlog_.empty() == false) {
00478                 have_data = true;
00479                 cfg = data_backlog_.front();
00480                 data_backlog_.pop_front();
00481             } else {
00482                 from = network::receive_data(cfg);
00483                 have_data = from != network::null_connection;
00484             }
00485 
00486             if(have_data) {
00487                 if (skip_replay_ && replay_last_turn_ <= turn()){
00488                         skip_replay_ = false;
00489                 }
00490                 const turn_info::PROCESS_DATA_RESULT result = turn_data.process_network_data(cfg, from, data_backlog_, skip_replay_);
00491                 if (result == turn_info::PROCESS_RESTART_TURN) {
00492                     update_gui_to_player(player_number_ - 1);
00493                     player_type_changed_ = true;
00494                     return;
00495                 } else if (result == turn_info::PROCESS_END_TURN) {
00496                     break;
00497                 }
00498             }
00499         }
00500 
00501         play_slice();
00502         check_end_level();
00503 
00504         if (!network_processing_stopped_){
00505             turn_data.send_data();
00506         }
00507 
00508         gui_->draw();
00509     }
00510 
00511     turn_data.host_transfer().detach_handler(this);
00512     LOG_NG << "finished networked...\n";
00513     return;
00514 }
00515 
00516 void playmp_controller::init_turn_data() {
00517     turn_data_ = new turn_info(player_number_, replay_sender_);
00518     turn_data_->host_transfer().attach_handler(this);
00519 }
00520 
00521 void playmp_controller::process_oos(const std::string& err_msg) const {
00522     // Notify the server of the oos error.
00523     config cfg;
00524     config& info = cfg.add_child("info");
00525     info["type"] = "termination";
00526     info["condition"] = "out of sync";
00527     network::send_data(cfg, 0);
00528 
00529     std::stringstream temp_buf;
00530     std::vector<std::string> err_lines = utils::split(err_msg,'\n');
00531     temp_buf << _("The game is out of sync, and cannot continue. There are a number of reasons this could happen: this can occur if you or another player have modified their game settings. This may mean one of the players is attempting to cheat. It could also be due to a bug in the game, but this is less likely.\n\nDo you want to save an error log of your game?");
00532     if(!err_msg.empty()) {
00533         temp_buf << " \n \n"; //and now the "Details:"
00534         for(std::vector<std::string>::iterator i=err_lines.begin(); i!=err_lines.end(); ++i)
00535         {
00536             temp_buf << *i << '\n';
00537         }
00538         temp_buf << " \n";
00539     }
00540 
00541     savegame::oos_savegame save(to_config());
00542     save.save_game_interactive(resources::screen->video(), temp_buf.str(), gui::YES_NO);
00543 }
00544 
00545 void playmp_controller::handle_generic_event(const std::string& name){
00546     turn_info turn_data(player_number_, replay_sender_);
00547 
00548     if (name == "ai_user_interact"){
00549         playsingle_controller::handle_generic_event(name);
00550         turn_data.send_data();
00551     }
00552     else if ((name == "ai_gamestate_changed") || (name == "ai_sync_network")){
00553         turn_data.sync_network();
00554     }
00555     else if (name == "host_transfer"){
00556         is_host_ = true;
00557         if (linger_){
00558             gui::button* btn_end = gui_->find_button("button-endturn");
00559             btn_end->enable(true);
00560             gui_->invalidate_theme();
00561         }
00562     }
00563     if (end_turn_) {
00564         throw end_turn_exception();
00565     }
00566 }
00567 
00568 bool playmp_controller::can_execute_command(hotkey::HOTKEY_COMMAND command, int index) const
00569 {
00570     bool res = true;
00571     switch (command){
00572         case hotkey::HOTKEY_SPEAK:
00573         case hotkey::HOTKEY_SPEAK_ALLY:
00574         case hotkey::HOTKEY_SPEAK_ALL:
00575             res = network::nconnections() > 0;
00576             break;
00577         case hotkey::HOTKEY_START_NETWORK:
00578         case hotkey::HOTKEY_STOP_NETWORK:
00579             res = is_observer();
00580             break;
00581         case hotkey::HOTKEY_STOP_REPLAY:
00582             if (is_observer()){
00583                 network_processing_stopped_ = true;
00584                 LOG_NG << "network processing stopped";
00585             }
00586             break;
00587         default:
00588             return playsingle_controller::can_execute_command(command, index);
00589     }
00590     return res;
00591 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

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